diff --git a/res/drawable/conversation_item_received_shape.xml b/res/drawable/conversation_item_received_shape.xml
deleted file mode 100644
index 6a9a81d796..0000000000
--- a/res/drawable/conversation_item_received_shape.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/drawable/conversation_item_received_shape_dark.xml b/res/drawable/conversation_item_received_shape_dark.xml
deleted file mode 100644
index 4b77765f64..0000000000
--- a/res/drawable/conversation_item_received_shape_dark.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/drawable/conversation_item_received_triangle_shape.xml b/res/drawable/conversation_item_received_triangle_shape.xml
deleted file mode 100644
index 3ba9878d95..0000000000
--- a/res/drawable/conversation_item_received_triangle_shape.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
- -
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/drawable/conversation_item_received_triangle_shape_dark.xml b/res/drawable/conversation_item_received_triangle_shape_dark.xml
deleted file mode 100644
index afa9b598ef..0000000000
--- a/res/drawable/conversation_item_received_triangle_shape_dark.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
- -
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/drawable/conversation_item_sent_indicator_text_shape.xml b/res/drawable/conversation_item_sent_indicator_text_shape.xml
index 48de08ba1e..0a523d189f 100644
--- a/res/drawable/conversation_item_sent_indicator_text_shape.xml
+++ b/res/drawable/conversation_item_sent_indicator_text_shape.xml
@@ -4,14 +4,14 @@
-
-
+
- -
+
-
-
+
diff --git a/res/drawable/conversation_item_sent_indicator_text_shape_dark.xml b/res/drawable/conversation_item_sent_indicator_text_shape_dark.xml
index 9d1445bada..4e55dfe701 100644
--- a/res/drawable/conversation_item_sent_indicator_text_shape_dark.xml
+++ b/res/drawable/conversation_item_sent_indicator_text_shape_dark.xml
@@ -4,14 +4,14 @@
-
-
+
- -
+
-
-
+
diff --git a/res/drawable/conversation_item_sent_pending_shape.xml b/res/drawable/conversation_item_sent_pending_shape.xml
deleted file mode 100644
index 1c94b51dde..0000000000
--- a/res/drawable/conversation_item_sent_pending_shape.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
diff --git a/res/drawable/conversation_item_sent_pending_shape_dark.xml b/res/drawable/conversation_item_sent_pending_shape_dark.xml
deleted file mode 100644
index 122b0963d1..0000000000
--- a/res/drawable/conversation_item_sent_pending_shape_dark.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
diff --git a/res/drawable/conversation_item_sent_pending_triangle_shape.xml b/res/drawable/conversation_item_sent_pending_triangle_shape.xml
deleted file mode 100644
index 279720e4b2..0000000000
--- a/res/drawable/conversation_item_sent_pending_triangle_shape.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
- -
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/drawable/conversation_item_sent_pending_triangle_shape_dark.xml b/res/drawable/conversation_item_sent_pending_triangle_shape_dark.xml
deleted file mode 100644
index c29b2e29ea..0000000000
--- a/res/drawable/conversation_item_sent_pending_triangle_shape_dark.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
- -
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/drawable/conversation_item_sent_push_pending_shape.xml b/res/drawable/conversation_item_sent_push_pending_shape.xml
deleted file mode 100644
index 79a9930466..0000000000
--- a/res/drawable/conversation_item_sent_push_pending_shape.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
diff --git a/res/drawable/conversation_item_sent_push_pending_shape_dark.xml b/res/drawable/conversation_item_sent_push_pending_shape_dark.xml
deleted file mode 100644
index 358e828798..0000000000
--- a/res/drawable/conversation_item_sent_push_pending_shape_dark.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
diff --git a/res/drawable/conversation_item_sent_push_pending_triangle_shape.xml b/res/drawable/conversation_item_sent_push_pending_triangle_shape.xml
deleted file mode 100644
index 8cf5b43ba2..0000000000
--- a/res/drawable/conversation_item_sent_push_pending_triangle_shape.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
- -
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/drawable/conversation_item_sent_push_pending_triangle_shape_dark.xml b/res/drawable/conversation_item_sent_push_pending_triangle_shape_dark.xml
deleted file mode 100644
index 0992b4e378..0000000000
--- a/res/drawable/conversation_item_sent_push_pending_triangle_shape_dark.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
- -
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/drawable/conversation_item_sent_push_shape.xml b/res/drawable/conversation_item_sent_push_shape.xml
deleted file mode 100644
index d88015c76c..0000000000
--- a/res/drawable/conversation_item_sent_push_shape.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
diff --git a/res/drawable/conversation_item_sent_push_shape_dark.xml b/res/drawable/conversation_item_sent_push_shape_dark.xml
deleted file mode 100644
index 0e37a4d814..0000000000
--- a/res/drawable/conversation_item_sent_push_shape_dark.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
diff --git a/res/drawable/conversation_item_sent_push_triangle_shape.xml b/res/drawable/conversation_item_sent_push_triangle_shape.xml
deleted file mode 100644
index 17b89b6e6a..0000000000
--- a/res/drawable/conversation_item_sent_push_triangle_shape.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- -
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/drawable/conversation_item_sent_push_triangle_shape_dark.xml b/res/drawable/conversation_item_sent_push_triangle_shape_dark.xml
deleted file mode 100644
index 26bc4058f1..0000000000
--- a/res/drawable/conversation_item_sent_push_triangle_shape_dark.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
- -
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/drawable/conversation_item_sent_shape.xml b/res/drawable/conversation_item_sent_shape.xml
deleted file mode 100644
index f7878f67e7..0000000000
--- a/res/drawable/conversation_item_sent_shape.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
diff --git a/res/drawable/conversation_item_sent_shape_dark.xml b/res/drawable/conversation_item_sent_shape_dark.xml
deleted file mode 100644
index 44a303120d..0000000000
--- a/res/drawable/conversation_item_sent_shape_dark.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/drawable/conversation_item_sent_triangle_shape.xml b/res/drawable/conversation_item_sent_triangle_shape.xml
deleted file mode 100644
index bb80a5ea92..0000000000
--- a/res/drawable/conversation_item_sent_triangle_shape.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
- -
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/drawable/conversation_item_sent_triangle_shape_dark.xml b/res/drawable/conversation_item_sent_triangle_shape_dark.xml
deleted file mode 100644
index bceba9587a..0000000000
--- a/res/drawable/conversation_item_sent_triangle_shape_dark.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
- -
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/drawable/import_export_item_background_dark.xml b/res/drawable/import_export_item_background_dark.xml
index 5f2de2d757..e540e52ed9 100644
--- a/res/drawable/import_export_item_background_dark.xml
+++ b/res/drawable/import_export_item_background_dark.xml
@@ -4,14 +4,14 @@
-
-
+
- -
+
-
-
+
diff --git a/res/drawable/import_export_item_background_light.xml b/res/drawable/import_export_item_background_light.xml
index 97c2b3ee3a..87fa2dd8ed 100644
--- a/res/drawable/import_export_item_background_light.xml
+++ b/res/drawable/import_export_item_background_light.xml
@@ -4,14 +4,14 @@
-
-
+
- -
+
-
-
+
diff --git a/res/drawable/triangle_tick_incoming_dark.xml b/res/drawable/triangle_tick_incoming_dark.xml
new file mode 100644
index 0000000000..14bbf0f8ee
--- /dev/null
+++ b/res/drawable/triangle_tick_incoming_dark.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/triangle_tick_incoming_light.xml b/res/drawable/triangle_tick_incoming_light.xml
new file mode 100644
index 0000000000..a738f898cf
--- /dev/null
+++ b/res/drawable/triangle_tick_incoming_light.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/triangle_tick_outgoing_pending_push_dark.xml b/res/drawable/triangle_tick_outgoing_pending_push_dark.xml
new file mode 100644
index 0000000000..e360878584
--- /dev/null
+++ b/res/drawable/triangle_tick_outgoing_pending_push_dark.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/triangle_tick_outgoing_pending_push_light.xml b/res/drawable/triangle_tick_outgoing_pending_push_light.xml
new file mode 100644
index 0000000000..056df3823c
--- /dev/null
+++ b/res/drawable/triangle_tick_outgoing_pending_push_light.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/triangle_tick_outgoing_pending_sms_dark.xml b/res/drawable/triangle_tick_outgoing_pending_sms_dark.xml
new file mode 100644
index 0000000000..97cf746c5f
--- /dev/null
+++ b/res/drawable/triangle_tick_outgoing_pending_sms_dark.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/triangle_tick_outgoing_pending_sms_light.xml b/res/drawable/triangle_tick_outgoing_pending_sms_light.xml
new file mode 100644
index 0000000000..dfe7b6a946
--- /dev/null
+++ b/res/drawable/triangle_tick_outgoing_pending_sms_light.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/triangle_tick_outgoing_sent_push_dark.xml b/res/drawable/triangle_tick_outgoing_sent_push_dark.xml
new file mode 100644
index 0000000000..fa237ea7fb
--- /dev/null
+++ b/res/drawable/triangle_tick_outgoing_sent_push_dark.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/triangle_tick_outgoing_sent_push_light.xml b/res/drawable/triangle_tick_outgoing_sent_push_light.xml
new file mode 100644
index 0000000000..d0f22ac0db
--- /dev/null
+++ b/res/drawable/triangle_tick_outgoing_sent_push_light.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/triangle_tick_outgoing_sent_sms_dark.xml b/res/drawable/triangle_tick_outgoing_sent_sms_dark.xml
new file mode 100644
index 0000000000..45abd07706
--- /dev/null
+++ b/res/drawable/triangle_tick_outgoing_sent_sms_dark.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/triangle_tick_outgoing_sent_sms_light.xml b/res/drawable/triangle_tick_outgoing_sent_sms_light.xml
new file mode 100644
index 0000000000..e85fedc804
--- /dev/null
+++ b/res/drawable/triangle_tick_outgoing_sent_sms_light.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/conversation_bubble_incoming.xml b/res/layout/conversation_bubble_incoming.xml
new file mode 100644
index 0000000000..42e0047002
--- /dev/null
+++ b/res/layout/conversation_bubble_incoming.xml
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/conversation_bubble_outgoing.xml b/res/layout/conversation_bubble_outgoing.xml
new file mode 100644
index 0000000000..e391f45d2d
--- /dev/null
+++ b/res/layout/conversation_bubble_outgoing.xml
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/conversation_item_received.xml b/res/layout/conversation_item_received.xml
index 578436bfdf..0ca454c25e 100644
--- a/res/layout/conversation_item_received.xml
+++ b/res/layout/conversation_item_received.xml
@@ -6,28 +6,27 @@
android:orientation="vertical"
android:background="?conversation_item_background"
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="15dp"
+ android:layout_marginTop="5dp"
+ android:fontFamily="sans-serif-light"
+ android:textSize="13sp"
+ android:textColor="?attr/conversation_group_member_name"
+ android:visibility="gone" />
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_marginLeft="29dp" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_height="wrap_content" />
-
+
diff --git a/res/layout/media_preview_activity.xml b/res/layout/media_preview_activity.xml
index a1b2202ad6..a84a746818 100644
--- a/res/layout/media_preview_activity.xml
+++ b/res/layout/media_preview_activity.xml
@@ -1,7 +1,6 @@
@@ -12,16 +11,6 @@
android:contentDescription="@string/media_preview_activity__image_content_description"
android:visibility="gone"/>
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 9d07bbc1e0..3ea096a0a4 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -35,9 +35,9 @@
#5564A926
#55284e0a
#ff2090ea
- #ff213b77
- #ff528ff2
- #55213b77
+ #ff183b7a
+ #ff5cace6
+ #ff122d5e
#fff3f3f3
#ff333333
#ffefefef
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 520956e0aa..c926c1f826 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -9,13 +9,18 @@
200sp
0dp
1dp
- 4dp
- 1.5dp
50dp
230dp
8dp
12dp
+ 50dp
+ 4dp
+ 1.5dp
+ 210dp
+ 3dp
+
+
3
10dp
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 04441c003f..1a29d2e96d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -479,6 +479,7 @@
Attachment Thumbnail
+ Media message downloading
Media message
Play button
Secure message
diff --git a/res/values/themes.xml b/res/values/themes.xml
index d3b6f58ff0..6bdcaccf17 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -55,20 +55,23 @@
- @drawable/ic_ime_dark
- @drawable/conversation_item_background
- - @drawable/conversation_item_received_shape
- - @drawable/conversation_item_received_triangle_shape
- - @drawable/conversation_item_sent_shape
- - @drawable/conversation_item_sent_triangle_shape
- - @drawable/conversation_item_sent_push_shape
- - @drawable/conversation_item_sent_push_triangle_shape
+ - @color/conversation_item_received_background_light
+ - @color/conversation_item_sent_background_light
+ - @color/conversation_item_sent_push_background_light
+ - @color/conversation_item_sent_pending_background_light
+ - @color/conversation_item_sent_push_pending_background_light
- @drawable/conversation_item_sent_indicator_text_shape
+ - @color/conversation_item_received_shadow_light
+ - #99ffffff
+
+ - @drawable/triangle_tick_incoming_light
+ - @drawable/triangle_tick_outgoing_sent_sms_light
+ - @drawable/triangle_tick_outgoing_sent_push_light
+ - @drawable/triangle_tick_outgoing_pending_sms_light
+ - @drawable/triangle_tick_outgoing_pending_push_light
- @drawable/ic_dialog_info_light
- @drawable/ic_dialog_alert_light
- - @drawable/conversation_item_sent_pending_shape
- - @drawable/conversation_item_sent_pending_triangle_shape
- - @drawable/conversation_item_sent_push_pending_shape
- - @drawable/conversation_item_sent_push_pending_triangle_shape
- @color/import_export_item_background_light
- @color/import_export_item_background_shadow_light
@@ -142,19 +145,22 @@
- #66eeeeee
- @drawable/conversation_item_background_dark
- - @drawable/conversation_item_received_shape_dark
- - @drawable/conversation_item_received_triangle_shape_dark
- - @drawable/conversation_item_sent_shape_dark
- - @drawable/conversation_item_sent_triangle_shape_dark
- - @drawable/conversation_item_sent_push_shape_dark
- - @drawable/conversation_item_sent_push_triangle_shape_dark
- - @drawable/conversation_item_sent_indicator_text_shape_dark
+ - @color/conversation_item_received_background_dark
+ - @color/conversation_item_sent_background_dark
+ - @color/conversation_item_sent_push_background_dark
+ - @color/conversation_item_sent_pending_background_dark
+ - @color/conversation_item_sent_push_pending_background_dark
+ - @color/conversation_item_received_shadow_dark
+ - #99000000
+
+ - @drawable/triangle_tick_incoming_dark
+ - @drawable/triangle_tick_outgoing_sent_sms_dark
+ - @drawable/triangle_tick_outgoing_sent_push_dark
+ - @drawable/triangle_tick_outgoing_pending_sms_dark
+ - @drawable/triangle_tick_outgoing_pending_push_dark
+
- @drawable/ic_dialog_info_dark
- @drawable/ic_dialog_alert_dark
- - @drawable/conversation_item_sent_pending_shape_dark
- - @drawable/conversation_item_sent_pending_triangle_shape_dark
- - @drawable/conversation_item_sent_push_pending_shape_dark
- - @drawable/conversation_item_sent_push_pending_triangle_shape_dark
- @color/import_export_item_background_dark
- @color/import_export_item_background_shadow_dark
diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
index 597930e046..e6e7131334 100644
--- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
+++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
@@ -47,6 +47,7 @@ import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
+import org.thoughtcrime.securesms.util.ResUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.api.TextSecureAccountManager;
@@ -286,7 +287,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
public boolean onPreferenceChange(final Preference preference, Object newValue) {
if (((CheckBoxPreference)preference).isChecked()) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setIcon(Dialogs.resolveIcon(getActivity(), R.attr.dialog_info_icon));
+ builder.setIcon(ResUtil.getDrawable(getActivity(), R.attr.dialog_info_icon));
builder.setTitle(R.string.ApplicationPreferencesActivity_disable_push_messages);
builder.setMessage(R.string.ApplicationPreferencesActivity_this_will_disable_push_messages);
builder.setNegativeButton(android.R.string.cancel, null);
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index 5ce3c4f9c5..c582df010b 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -96,6 +96,7 @@ import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.Emoji;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.MemoryCleaner;
+import org.thoughtcrime.securesms.util.ResUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libaxolotl.InvalidMessageException;
@@ -358,7 +359,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void handleAbortSecureSession() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.ConversationActivity_abort_secure_session_confirmation);
- builder.setIcon(Dialogs.resolveIcon(this, R.attr.dialog_alert_icon));
+ builder.setIcon(ResUtil.getDrawable(this, R.attr.dialog_alert_icon));
builder.setCancelable(true);
builder.setMessage(R.string.ConversationActivity_are_you_sure_that_you_want_to_abort_this_secure_session_question);
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@@ -405,7 +406,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.ConversationActivity_leave_group));
- builder.setIcon(Dialogs.resolveIcon(this, R.attr.dialog_info_icon));
+ builder.setIcon(ResUtil.getDrawable(this, R.attr.dialog_info_icon));
builder.setCancelable(true);
builder.setMessage(getString(R.string.ConversationActivity_are_you_sure_you_want_to_leave_this_group));
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@@ -498,7 +499,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void handleDeleteThread() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.ConversationActivity_delete_thread_confirmation);
- builder.setIcon(Dialogs.resolveIcon(this, R.attr.dialog_alert_icon));
+ builder.setIcon(ResUtil.getDrawable(this, R.attr.dialog_alert_icon));
builder.setCancelable(true);
builder.setMessage(R.string.ConversationActivity_are_you_sure_that_you_want_to_permanently_delete_this_conversation_question);
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
diff --git a/src/org/thoughtcrime/securesms/ConversationAdapter.java b/src/org/thoughtcrime/securesms/ConversationAdapter.java
index 015f51582e..64f56ef21e 100644
--- a/src/org/thoughtcrime/securesms/ConversationAdapter.java
+++ b/src/org/thoughtcrime/securesms/ConversationAdapter.java
@@ -116,7 +116,6 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re
default: throw new IllegalArgumentException("unsupported item view type given to ConversationAdapter");
}
- bindView(view, context, cursor);
return view;
}
diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java
index 4f61ff9bdc..e309300883 100644
--- a/src/org/thoughtcrime/securesms/ConversationFragment.java
+++ b/src/org/thoughtcrime/securesms/ConversationFragment.java
@@ -40,6 +40,7 @@ import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
+import org.thoughtcrime.securesms.util.ResUtil;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
@@ -192,7 +193,7 @@ public class ConversationFragment extends ListFragment
private void handleDeleteMessages(final List messageRecords) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.ConversationFragment_confirm_message_delete);
- builder.setIcon(Dialogs.resolveIcon(getActivity(), R.attr.dialog_alert_icon));
+ builder.setIcon(ResUtil.getDrawable(getActivity(), R.attr.dialog_alert_icon));
builder.setCancelable(true);
builder.setMessage(R.string.ConversationFragment_are_you_sure_you_want_to_permanently_delete_all_selected_messages);
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java
index 7747059fa1..12766eed3b 100644
--- a/src/org/thoughtcrime/securesms/ConversationItem.java
+++ b/src/org/thoughtcrime/securesms/ConversationItem.java
@@ -23,15 +23,18 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
import android.os.Handler;
-import android.os.Message;
+import android.os.Looper;
import android.provider.ContactsContract;
import android.provider.ContactsContract.QuickContact;
import android.support.annotation.NonNull;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.Pair;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
@@ -40,6 +43,7 @@ import android.widget.TextView;
import android.widget.Toast;
import org.thoughtcrime.securesms.ConversationFragment.SelectionClickListener;
+import org.thoughtcrime.securesms.components.ForegroundImageView;
import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
@@ -56,11 +60,12 @@ import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.components.BubbleContainer;
import org.thoughtcrime.securesms.util.DateUtils;
-import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.Emoji;
import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
+import org.thoughtcrime.securesms.util.ResUtil;
import java.util.Set;
@@ -75,57 +80,39 @@ import java.util.Set;
public class ConversationItem extends LinearLayout {
private final static String TAG = ConversationItem.class.getSimpleName();
- private final int STYLE_ATTRIBUTES[] = new int[]{R.attr.conversation_item_sent_push_background,
- R.attr.conversation_item_sent_push_triangle_background,
- R.attr.conversation_item_sent_background,
- R.attr.conversation_item_sent_triangle_background,
- R.attr.conversation_item_sent_pending_background,
- R.attr.conversation_item_sent_pending_triangle_background,
- R.attr.conversation_item_sent_push_pending_background,
- R.attr.conversation_item_sent_push_pending_triangle_background};
-
- private final static int SENT_PUSH = 0;
- private final static int SENT_PUSH_TRIANGLE = 1;
- private final static int SENT_SMS = 2;
- private final static int SENT_SMS_TRIANGLE = 3;
- private final static int SENT_SMS_PENDING = 4;
- private final static int SENT_SMS_PENDING_TRIANGLE = 5;
- private final static int SENT_PUSH_PENDING = 6;
- private final static int SENT_PUSH_PENDING_TRIANGLE = 7;
-
- private Handler failedIconHandler;
private MessageRecord messageRecord;
private MasterSecret masterSecret;
private boolean groupThread;
private boolean pushDestination;
- private View conversationParent;
- private TextView bodyText;
- private TextView dateText;
- private TextView indicatorText;
- private TextView groupStatusText;
- private ImageView secureImage;
- private ImageView failedImage;
- private ImageView contactPhoto;
- private ImageView deliveryImage;
- private View triangleTick;
- private ImageView pendingIndicator;
+ private View bodyBubble;
+ private TextView bodyText;
+ private TextView dateText;
+ private TextView indicatorText;
+ private TextView groupStatusText;
+ private ImageView secureImage;
+ private ImageView failedImage;
+ private ImageView contactPhoto;
+ private ImageView deliveryImage;
+ private ImageView pendingIndicator;
+ private BubbleContainer bubbleContainer;
- private Set batchSelected;
- private SelectionClickListener selectionClickListener;
- private View mmsContainer;
- private ImageView mmsThumbnail;
- private Button mmsDownloadButton;
- private TextView mmsDownloadingLabel;
- private ListenableFutureTask slideDeck;
- private FutureTaskListener slideDeckListener;
- private TypedArray backgroundDrawables;
+ private Set batchSelected;
+ private SelectionClickListener selectionClickListener;
+ private ForegroundImageView mediaThumbnail;
+ private Button mmsDownloadButton;
+ private TextView mmsDownloadingLabel;
+
+ private ListenableFutureTask slideDeckFuture;
+ private ListenableFutureTask> thumbnailFuture;
+ private SlideDeckListener slideDeckListener;
+ private ThumbnailListener thumbnailListener;
+ private Handler handler;
private final MmsDownloadClickListener mmsDownloadClickListener = new MmsDownloadClickListener();
private final MmsPreferencesClickListener mmsPreferencesClickListener = new MmsPreferencesClickListener();
private final ClickListener clickListener = new ClickListener();
- private final Handler handler = new Handler();
- private final Context context;
+ private final Context context;
public ConversationItem(Context context) {
super(context);
@@ -147,20 +134,21 @@ public class ConversationItem extends LinearLayout {
this.groupStatusText = (TextView) findViewById(R.id.group_message_status);
this.secureImage = (ImageView)findViewById(R.id.sms_secure_indicator);
this.failedImage = (ImageView)findViewById(R.id.sms_failed_indicator);
- this.mmsContainer = findViewById(R.id.mms_view);
- this.mmsThumbnail = (ImageView)findViewById(R.id.image_view);
this.mmsDownloadButton = (Button) findViewById(R.id.mms_download_button);
this.mmsDownloadingLabel = (TextView) findViewById(R.id.mms_label_downloading);
this.contactPhoto = (ImageView)findViewById(R.id.contact_photo);
this.deliveryImage = (ImageView)findViewById(R.id.delivered_indicator);
- this.conversationParent = findViewById(R.id.conversation_item_parent);
- this.triangleTick = findViewById(R.id.triangle_tick);
+ this.bodyBubble = findViewById(R.id.body_bubble);
this.pendingIndicator = (ImageView)findViewById(R.id.pending_approval_indicator);
- this.backgroundDrawables = context.obtainStyledAttributes(STYLE_ATTRIBUTES);
+ this.bubbleContainer = (BubbleContainer)findViewById(R.id.bubble);
+ this.mediaThumbnail = (ForegroundImageView)findViewById(R.id.image_view);
+
+ slideDeckListener = new SlideDeckListener();
+ handler = new Handler(Looper.getMainLooper());
setOnClickListener(clickListener);
if (mmsDownloadButton != null) mmsDownloadButton.setOnClickListener(mmsDownloadClickListener);
- if (mmsThumbnail != null) mmsThumbnail.setOnLongClickListener(new MultiSelectLongClickListener());
+ if (mediaThumbnail != null) mediaThumbnail.setOnLongClickListener(new MultiSelectLongClickListener());
}
public void set(@NonNull MasterSecret masterSecret,
@@ -176,73 +164,65 @@ public class ConversationItem extends LinearLayout {
this.groupThread = groupThread;
this.pushDestination = pushDestination;
- setConversationBackgroundDrawables(messageRecord);
setSelectionBackgroundDrawables(messageRecord);
setBodyText(messageRecord);
- if (!messageRecord.isGroupAction()) {
+ if (hasConversationBubble(messageRecord)) {
+ setBubbleState(messageRecord);
setStatusIcons(messageRecord);
setContactPhoto(messageRecord);
setGroupMessageStatus(messageRecord);
setEvents(messageRecord);
setMinimumWidth();
-
- if (messageRecord.isMmsNotification()) {
- setNotificationMmsAttributes((NotificationMmsMessageRecord)messageRecord);
- } else if (messageRecord.isMms()) {
- setMediaMmsAttributes((MediaMmsMessageRecord)messageRecord);
- }
+ setMediaAttributes(messageRecord);
}
}
public void unbind() {
- if (slideDeck != null && slideDeckListener != null)
- slideDeck.removeListener(slideDeckListener);
+ if (slideDeckFuture != null && slideDeckListener != null) {
+ slideDeckFuture.removeListener(slideDeckListener);
+ }
+
+ if (thumbnailFuture != null && thumbnailListener != null) {
+ thumbnailFuture.removeListener(thumbnailListener);
+ }
}
public MessageRecord getMessageRecord() {
return messageRecord;
}
- public void setHandler(Handler failedIconHandler) {
- this.failedIconHandler = failedIconHandler;
- }
-
- public static void setViewBackgroundWithoutResettingPadding(final View v, final int backgroundResId) {
- final int paddingBottom = v.getPaddingBottom();
- final int paddingLeft = v.getPaddingLeft();
- final int paddingRight = v.getPaddingRight();
- final int paddingTop = v.getPaddingTop();
- v.setBackgroundResource(backgroundResId);
- v.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
- }
-
/// MessageRecord Attribute Parsers
- private void setConversationBackgroundDrawables(MessageRecord messageRecord) {
- if (conversationParent != null && backgroundDrawables != null) {
- if (messageRecord.isOutgoing()) {
- final int background;
- final int triangleBackground;
- if ((messageRecord.isPending() || messageRecord.isFailed()) && pushDestination && !messageRecord.isForcedSms()) {
- background = SENT_PUSH_PENDING;
- triangleBackground = SENT_PUSH_PENDING_TRIANGLE;
- } else if (messageRecord.isPending() || messageRecord.isFailed() || messageRecord.isPendingInsecureSmsFallback()) {
- background = SENT_SMS_PENDING;
- triangleBackground = SENT_SMS_PENDING_TRIANGLE;
- } else if (messageRecord.isPush()) {
- background = SENT_PUSH;
- triangleBackground = SENT_PUSH_TRIANGLE;
- } else {
- background = SENT_SMS;
- triangleBackground = SENT_SMS_TRIANGLE;
- }
-
- setViewBackgroundWithoutResettingPadding(conversationParent, backgroundDrawables.getResourceId(background, -1));
- setViewBackgroundWithoutResettingPadding(triangleTick, backgroundDrawables.getResourceId(triangleBackground, -1));
- }
+ private void setBubbleState(MessageRecord messageRecord) {
+ final int transportationState;
+ if ((messageRecord.isPending() || messageRecord.isFailed()) &&
+ pushDestination &&
+ !messageRecord.isForcedSms())
+ {
+ transportationState = BubbleContainer.TRANSPORT_STATE_PUSH_PENDING;
+ } else if (messageRecord.isPending() ||
+ messageRecord.isFailed() ||
+ messageRecord.isPendingInsecureSmsFallback())
+ {
+ transportationState = BubbleContainer.TRANSPORT_STATE_SMS_PENDING;
+ } else if (messageRecord.isPush()) {
+ transportationState = BubbleContainer.TRANSPORT_STATE_PUSH_SENT;
+ } else {
+ transportationState = BubbleContainer.TRANSPORT_STATE_SMS_SENT;
}
- }
+
+ final int mediaCaptionState;
+ if (!hasMedia(messageRecord)) {
+ mediaCaptionState = BubbleContainer.MEDIA_STATE_NO_MEDIA;
+ } else if (isCaptionlessMms(messageRecord)) {
+ mediaCaptionState = BubbleContainer.MEDIA_STATE_CAPTIONLESS;
+ } else {
+ mediaCaptionState = BubbleContainer.MEDIA_STATE_CAPTIONED;
+ }
+
+ bubbleContainer.setState(transportationState, mediaCaptionState);
+}
private void setSelectionBackgroundDrawables(MessageRecord messageRecord) {
int[] attributes = new int[]{R.attr.conversation_list_item_background_selected,
@@ -259,12 +239,30 @@ public class ConversationItem extends LinearLayout {
drawables.recycle();
}
+ private boolean hasConversationBubble(MessageRecord messageRecord) {
+ return !messageRecord.isGroupAction();
+ }
+
+ private boolean isCaptionlessMms(MessageRecord messageRecord) {
+ return TextUtils.isEmpty(messageRecord.getDisplayBody()) && messageRecord.isMms();
+ }
+
+ private boolean hasMedia(MessageRecord messageRecord) {
+ return messageRecord.isMms() && ((MediaMmsMessageRecord)messageRecord).getPartCount() > 0;
+ }
+
private void setBodyText(MessageRecord messageRecord) {
bodyText.setClickable(false);
bodyText.setFocusable(false);
- bodyText.setText(Emoji.getInstance(context).emojify(messageRecord.getDisplayBody(),
- new Emoji.InvalidatingPageLoadedListener(bodyText)),
- TextView.BufferType.SPANNABLE);
+
+ if (isCaptionlessMms(messageRecord)) {
+ bodyText.setVisibility(View.GONE);
+ } else {
+ bodyText.setText(Emoji.getInstance(context).emojify(messageRecord.getDisplayBody(),
+ new Emoji.InvalidatingPageLoadedListener(bodyText)),
+ TextView.BufferType.SPANNABLE);
+ bodyText.setVisibility(View.VISIBLE);
+ }
if (bodyText.isClickable() && bodyText.isFocusable()) {
bodyText.setOnLongClickListener(new MultiSelectLongClickListener());
@@ -272,6 +270,14 @@ public class ConversationItem extends LinearLayout {
}
}
+ private void setMediaAttributes(MessageRecord messageRecord) {
+ if (messageRecord.isMmsNotification()) {
+ setNotificationMmsAttributes((NotificationMmsMessageRecord) messageRecord);
+ } else if (messageRecord.isMms()) {
+ resolveMedia((MediaMmsMessageRecord) messageRecord);
+ }
+ }
+
private void setContactPhoto(MessageRecord messageRecord) {
if (! messageRecord.isOutgoing()) {
setContactPhotoForRecipient(messageRecord.getIndividualRecipient());
@@ -287,7 +293,6 @@ public class ConversationItem extends LinearLayout {
bodyText.setCompoundDrawablesWithIntrinsicBounds(0, 0, messageRecord.isKeyExchange() ? R.drawable.ic_menu_login : 0, 0);
deliveryImage.setVisibility(!messageRecord.isKeyExchange() && messageRecord.isDelivered() ? View.VISIBLE : View.GONE);
- mmsThumbnail.setVisibility(View.GONE);
mmsDownloadButton.setVisibility(View.GONE);
mmsDownloadingLabel.setVisibility(View.GONE);
@@ -299,7 +304,6 @@ public class ConversationItem extends LinearLayout {
private void setSentStatusIcons() {
final long timestamp;
-
if (messageRecord.isPush()) timestamp = messageRecord.getDateSent();
else timestamp = messageRecord.getDateReceived();
@@ -321,9 +325,9 @@ public class ConversationItem extends LinearLayout {
private void setMinimumWidth() {
if (indicatorText != null && indicatorText.getVisibility() == View.VISIBLE && indicatorText.getText() != null) {
final float density = getResources().getDisplayMetrics().density;
- conversationParent.setMinimumWidth(indicatorText.getText().length() * (int)(6.5 * density));
+ bodyBubble.setMinimumWidth(indicatorText.getText().length() * (int) (6.5 * density));
} else {
- conversationParent.setMinimumWidth(0);
+ bodyBubble.setMinimumWidth(0);
}
}
@@ -367,44 +371,11 @@ public class ConversationItem extends LinearLayout {
}
}
- private void setMediaMmsAttributes(MediaMmsMessageRecord messageRecord) {
- if (messageRecord.getPartCount() > 0) {
- mmsThumbnail.setVisibility(View.VISIBLE);
- mmsContainer.setVisibility(View.VISIBLE);
- mmsThumbnail.setImageDrawable(new ColorDrawable(Color.TRANSPARENT));
- } else {
- mmsThumbnail.setVisibility(View.GONE);
- mmsContainer.setVisibility(View.GONE);
+ private void resolveMedia(MediaMmsMessageRecord messageRecord) {
+ if (hasMedia(messageRecord)) {
+ slideDeckFuture = messageRecord.getSlideDeckFuture();
+ slideDeckFuture.addListener(slideDeckListener);
}
-
- slideDeck = messageRecord.getSlideDeckFuture();
- slideDeckListener = new FutureTaskListener() {
- @Override
- public void onSuccess(final SlideDeck result) {
- if (result == null)
- return;
-
- handler.post(new Runnable() {
- @Override
- public void run() {
- for (Slide slide : result.getSlides()) {
- if (slide.hasImage()) {
- slide.setThumbnailOn(context, mmsThumbnail);
- mmsThumbnail.setOnClickListener(new ThumbnailClickListener(slide));
- mmsThumbnail.setVisibility(View.VISIBLE);
- return;
- }
- }
-
- mmsThumbnail.setVisibility(View.GONE);
- }
- });
- }
-
- @Override
- public void onFailure(Throwable error) {}
- };
- slideDeck.addListener(slideDeckListener);
}
/// Helper Methods
@@ -486,11 +457,16 @@ public class ConversationItem extends LinearLayout {
intent.putExtra(MediaPreviewActivity.MASTER_SECRET_EXTRA, masterSecret);
if (!messageRecord.isOutgoing()) intent.putExtra(MediaPreviewActivity.RECIPIENT_EXTRA, messageRecord.getIndividualRecipient().getRecipientId());
intent.putExtra(MediaPreviewActivity.DATE_EXTRA, messageRecord.getDateReceived());
- context.startActivity(intent);
+
+ if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
+ context.startActivity(intent, mediaThumbnail.getThumbnailTransition().toBundle());
+ } else {
+ context.startActivity(intent);
+ }
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.ConversationItem_view_secure_media_question);
- builder.setIcon(Dialogs.resolveIcon(context, R.attr.dialog_alert_icon));
+ builder.setIcon(ResUtil.getDrawable(context, R.attr.dialog_alert_icon));
builder.setCancelable(true);
builder.setMessage(R.string.ConversationItem_this_media_has_been_stored_in_an_encrypted_database_external_viewer_warning);
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@@ -528,16 +504,6 @@ public class ConversationItem extends LinearLayout {
}
}
- private class FailedIconClickListener implements View.OnClickListener {
- public void onClick(View v) {
- if (failedIconHandler != null && !messageRecord.isKeyExchange()) {
- Message message = Message.obtain();
- message.obj = messageRecord.getBody().getBody();
- failedIconHandler.dispatchMessage(message);
- }
- }
- }
-
private class ClickListener implements View.OnClickListener {
public void onClick(View v) {
if (messageRecord.isFailed()) {
@@ -624,4 +590,58 @@ public class ConversationItem extends LinearLayout {
});
builder.show();
}
+
+ private class ThumbnailListener implements FutureTaskListener> {
+ private final Object tag;
+
+ public ThumbnailListener(Object tag) {
+ this.tag = tag;
+ }
+
+ @Override
+ public void onSuccess(final Pair result) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mediaThumbnail.getTag() == tag) {
+ Log.w(TAG, "displaying media thumbnail");
+ mediaThumbnail.show(result.first, result.second);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onFailure(Throwable error) {
+ Log.w(TAG, error);
+ mediaThumbnail.setVisibility(View.GONE);
+ }
+ }
+
+ private class SlideDeckListener implements FutureTaskListener {
+ @Override
+ public void onSuccess(final SlideDeck slideDeck) {
+ if (slideDeck == null) return;
+
+ Slide slide = slideDeck.getThumbnailSlide(context);
+ if (slide != null) {
+ thumbnailFuture = slide.getThumbnail(context);
+ if (thumbnailFuture != null) {
+ Object tag = new Object();
+ mediaThumbnail.setTag(tag);
+ thumbnailListener = new ThumbnailListener(tag);
+ thumbnailFuture.addListener(thumbnailListener);
+ mediaThumbnail.setOnClickListener(new ThumbnailClickListener(slide));
+ return;
+ }
+ }
+ mediaThumbnail.hide();
+ }
+
+ @Override
+ public void onFailure(Throwable error) {
+ Log.w(TAG, error);
+ mediaThumbnail.hide();
+ }
+ }
}
diff --git a/src/org/thoughtcrime/securesms/ConversationListFragment.java b/src/org/thoughtcrime/securesms/ConversationListFragment.java
index 43bbd77191..416048cf71 100644
--- a/src/org/thoughtcrime/securesms/ConversationListFragment.java
+++ b/src/org/thoughtcrime/securesms/ConversationListFragment.java
@@ -53,7 +53,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.loaders.ConversationListLoader;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipients;
-import org.thoughtcrime.securesms.util.Dialogs;
+import org.thoughtcrime.securesms.util.ResUtil;
import java.util.Set;
@@ -197,7 +197,7 @@ public class ConversationListFragment extends ListFragment
private void handleDeleteAllSelected() {
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
- alert.setIcon(Dialogs.resolveIcon(getActivity(), R.attr.dialog_alert_icon));
+ alert.setIcon(ResUtil.getDrawable(getActivity(), R.attr.dialog_alert_icon));
alert.setTitle(R.string.ConversationListFragment_delete_threads_question);
alert.setMessage(R.string.ConversationListFragment_are_you_sure_you_wish_to_delete_all_selected_conversation_threads);
alert.setCancelable(true);
diff --git a/src/org/thoughtcrime/securesms/ExportFragment.java b/src/org/thoughtcrime/securesms/ExportFragment.java
index f6d353fff9..0b1ae3eba9 100644
--- a/src/org/thoughtcrime/securesms/ExportFragment.java
+++ b/src/org/thoughtcrime/securesms/ExportFragment.java
@@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.NoExternalStorageException;
import org.thoughtcrime.securesms.database.PlaintextBackupExporter;
import org.thoughtcrime.securesms.util.Dialogs;
+import org.thoughtcrime.securesms.util.ResUtil;
import java.io.IOException;
@@ -59,7 +60,7 @@ public class ExportFragment extends Fragment {
// private void handleExportEncryptedBackup() {
// AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
-// builder.setIcon(Dialogs.resolveIcon(getActivity(), R.attr.dialog_info_icon));
+// builder.setIcon(Dialogs.getDrawable(getActivity(), R.attr.dialog_info_icon));
// builder.setTitle(getActivity().getString(R.string.ExportFragment_export_to_sd_card));
// builder.setMessage(getActivity().getString(R.string.ExportFragment_this_will_export_your_encrypted_keys_settings_and_messages));
// builder.setPositiveButton(getActivity().getString(R.string.ExportFragment_export), new Dialog.OnClickListener() {
@@ -74,7 +75,7 @@ public class ExportFragment extends Fragment {
private void handleExportPlaintextBackup() {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setIcon(Dialogs.resolveIcon(getActivity(), R.attr.dialog_alert_icon));
+ builder.setIcon(ResUtil.getDrawable(getActivity(), R.attr.dialog_alert_icon));
builder.setTitle(getActivity().getString(R.string.ExportFragment_export_plaintext_to_sd_card));
builder.setMessage(getActivity().getString(R.string.ExportFragment_warning_this_will_export_the_plaintext_contents));
builder.setPositiveButton(getActivity().getString(R.string.ExportFragment_export), new Dialog.OnClickListener() {
diff --git a/src/org/thoughtcrime/securesms/ImageMediaAdapter.java b/src/org/thoughtcrime/securesms/ImageMediaAdapter.java
index fd74610bb9..9e428b99d6 100644
--- a/src/org/thoughtcrime/securesms/ImageMediaAdapter.java
+++ b/src/org/thoughtcrime/securesms/ImageMediaAdapter.java
@@ -19,24 +19,25 @@ package org.thoughtcrime.securesms;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
-import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
-import android.widget.ImageView;
import org.thoughtcrime.securesms.ImageMediaAdapter.ViewHolder;
+import org.thoughtcrime.securesms.components.ForegroundImageView;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
import org.thoughtcrime.securesms.database.PartDatabase.ImageRecord;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
-import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
+import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.MediaUtil;
import ws.com.google.android.mms.pdu.PduPart;
@@ -48,11 +49,11 @@ public class ImageMediaAdapter extends CursorRecyclerViewAdapter {
private final int gridSize;
public static class ViewHolder extends RecyclerView.ViewHolder {
- public ImageView imageView;
+ public ForegroundImageView imageView;
public ViewHolder(View v) {
super(v);
- imageView = (ImageView) v.findViewById(R.id.image);
+ imageView = (ForegroundImageView) v.findViewById(R.id.image);
}
}
@@ -70,8 +71,8 @@ public class ImageMediaAdapter extends CursorRecyclerViewAdapter {
@Override
public void onBindViewHolder(final ViewHolder viewHolder, final Cursor cursor) {
- final ImageView imageView = viewHolder.imageView;
- final ImageRecord imageRecord = ImageRecord.from(cursor);
+ final ForegroundImageView imageView = viewHolder.imageView;
+ final ImageRecord imageRecord = ImageRecord.from(cursor);
PduPart part = new PduPart();
@@ -79,8 +80,26 @@ public class ImageMediaAdapter extends CursorRecyclerViewAdapter {
part.setContentType(imageRecord.getContentType().getBytes());
part.setId(imageRecord.getPartId());
+ imageView.setVisibility(View.INVISIBLE);
Slide slide = MediaUtil.getSlideForPart(getContext(), masterSecret, part, imageRecord.getContentType());
- if (slide != null) slide.setThumbnailOn(getContext(), imageView, gridSize, gridSize, new ColorDrawable(0x11ffffff));
+ if (slide != null) {
+ slide.getThumbnail(getContext()).addListener(new FutureTaskListener>() {
+ @Override
+ public void onSuccess(final Pair result) {
+ imageView.post(new Runnable() {
+ @Override
+ public void run() {
+ imageView.show(result.first, false);
+ }
+ });
+ }
+
+ @Override
+ public void onFailure(Throwable error) {
+ Log.w(TAG, error);
+ }
+ });
+ }
imageView.setOnClickListener(new OnMediaClickListener(imageRecord));
}
diff --git a/src/org/thoughtcrime/securesms/ImportFragment.java b/src/org/thoughtcrime/securesms/ImportFragment.java
index 560fd61049..3b9fb8b71e 100644
--- a/src/org/thoughtcrime/securesms/ImportFragment.java
+++ b/src/org/thoughtcrime/securesms/ImportFragment.java
@@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.database.PlaintextBackupImporter;
import org.thoughtcrime.securesms.service.ApplicationMigrationService;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.Dialogs;
+import org.thoughtcrime.securesms.util.ResUtil;
import java.io.IOException;
@@ -82,7 +83,7 @@ public class ImportFragment extends Fragment {
private void handleImportSms() {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setIcon(Dialogs.resolveIcon(getActivity(), R.attr.dialog_info_icon));
+ builder.setIcon(ResUtil.getDrawable(getActivity(), R.attr.dialog_info_icon));
builder.setTitle(getActivity().getString(R.string.ImportFragment_import_system_sms_database));
builder.setMessage(getActivity().getString(R.string.ImportFragment_this_will_import_messages_from_the_system));
builder.setPositiveButton(getActivity().getString(R.string.ImportFragment_import), new AlertDialog.OnClickListener() {
@@ -108,7 +109,7 @@ public class ImportFragment extends Fragment {
private void handleImportEncryptedBackup() {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setIcon(Dialogs.resolveIcon(getActivity(), R.attr.dialog_alert_icon));
+ builder.setIcon(ResUtil.getDrawable(getActivity(), R.attr.dialog_alert_icon));
builder.setTitle(getActivity().getString(R.string.ImportFragment_restore_encrypted_backup));
builder.setMessage(getActivity().getString(R.string.ImportFragment_restoring_an_encrypted_backup_will_completely_replace_your_existing_keys));
builder.setPositiveButton(getActivity().getString(R.string.ImportFragment_restore), new AlertDialog.OnClickListener() {
@@ -123,7 +124,7 @@ public class ImportFragment extends Fragment {
private void handleImportPlaintextBackup() {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setIcon(Dialogs.resolveIcon(getActivity(), R.attr.dialog_alert_icon));
+ builder.setIcon(ResUtil.getDrawable(getActivity(), R.attr.dialog_alert_icon));
builder.setTitle(getActivity().getString(R.string.ImportFragment_import_plaintext_backup));
builder.setMessage(getActivity().getString(R.string.ImportFragment_this_will_import_messages_from_a_plaintext_backup));
builder.setPositiveButton(getActivity().getString(R.string.ImportFragment_import), new AlertDialog.OnClickListener() {
diff --git a/src/org/thoughtcrime/securesms/MediaPreviewActivity.java b/src/org/thoughtcrime/securesms/MediaPreviewActivity.java
index 0b5f2047e7..11ed698a62 100644
--- a/src/org/thoughtcrime/securesms/MediaPreviewActivity.java
+++ b/src/org/thoughtcrime/securesms/MediaPreviewActivity.java
@@ -20,6 +20,7 @@ import android.annotation.TargetApi;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.net.Uri;
import android.opengl.GLES20;
import android.os.AsyncTask;
@@ -31,12 +32,14 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
@@ -66,7 +69,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
private MasterSecret masterSecret;
private boolean paused;
- private View loadingView;
private TextView errorText;
private Bitmap bitmap;
private ImageView image;
@@ -82,6 +84,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
dynamicLanguage.onCreate(this);
super.onCreate(bundle);
+
setFullscreenIfPossible();
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
@@ -146,7 +149,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
}
private void initializeViews() {
- loadingView = findViewById(R.id.loading_indicator);
errorText = (TextView) findViewById(R.id.error);
image = (ImageView) findViewById(R.id.image);
imageAttacher = new PhotoViewAttacher(image);
@@ -191,6 +193,13 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
}
private void displayImage() {
+ try {
+ image.setImageBitmap(BitmapFactory.decodeStream(PartAuthority.getThumbnail(this, masterSecret, mediaUri)));
+ image.setVisibility(View.VISIBLE);
+ } catch (IOException fnfe) {
+ Log.w(TAG, "tried to render thumbnail, but it wasn't found. carrying on.");
+ }
+
new AsyncTask() {
@Override
protected Bitmap doInBackground(Void... params) {
@@ -206,11 +215,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
}
}
- @Override
- protected void onPreExecute() {
- loadingView.setVisibility(View.VISIBLE);
- }
-
@Override
protected void onPostExecute(Bitmap bitmap) {
if (paused) {
@@ -218,7 +222,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
return;
}
- loadingView.setVisibility(View.GONE);
if (bitmap == null) {
errorText.setText(R.string.MediaPreviewActivity_cant_display);
errorText.setVisibility(View.VISIBLE);
diff --git a/src/org/thoughtcrime/securesms/components/BubbleContainer.java b/src/org/thoughtcrime/securesms/components/BubbleContainer.java
new file mode 100644
index 0000000000..c63cdc67b0
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/BubbleContainer.java
@@ -0,0 +1,168 @@
+/**
+ * Copyright (C) 2015 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.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build.VERSION_CODES;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.IntDef;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.RelativeLayout;
+
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.util.ResUtil;
+import org.thoughtcrime.securesms.util.ViewUtil;
+
+public abstract class BubbleContainer extends RelativeLayout {
+ @SuppressWarnings("unused")
+ private static final String TAG = BubbleContainer.class.getSimpleName();
+
+ public static final int TRANSPORT_STATE_PUSH_SENT = 0;
+ public static final int TRANSPORT_STATE_SMS_SENT = 1;
+ public static final int TRANSPORT_STATE_SMS_PENDING = 2;
+ public static final int TRANSPORT_STATE_PUSH_PENDING = 3;
+
+ public static final int MEDIA_STATE_NO_MEDIA = 0;
+ public static final int MEDIA_STATE_CAPTIONLESS = 1;
+ public static final int MEDIA_STATE_CAPTIONED = 2;
+
+ @IntDef({TRANSPORT_STATE_PUSH_SENT, TRANSPORT_STATE_PUSH_PENDING, TRANSPORT_STATE_SMS_SENT, TRANSPORT_STATE_SMS_PENDING})
+ public @interface TransportState {}
+
+ @IntDef({MEDIA_STATE_NO_MEDIA, MEDIA_STATE_CAPTIONLESS, MEDIA_STATE_CAPTIONED})
+ public @interface MediaState {}
+
+ private View bodyBubble;
+ private View triangleTick;
+ private ForegroundImageView media;
+ private int shadowColor;
+ private int mmsPendingOverlayColor;
+
+ public BubbleContainer(Context context) {
+ super(context);
+ initialize();
+ }
+
+ public BubbleContainer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initialize();
+ }
+
+ public BubbleContainer(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initialize();
+ }
+
+ @TargetApi(VERSION_CODES.LOLLIPOP)
+ public BubbleContainer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ initialize();
+ }
+
+ protected abstract void onCreateView();
+ protected abstract int getForegroundColor(@TransportState int transportState);
+ protected abstract boolean[] getMessageCorners(@MediaState int mediaState);
+ protected abstract boolean[] getMediaCorners(@MediaState int mediaState);
+ protected abstract int getTriangleTickRes(@TransportState int transportState);
+
+ protected void initialize() {
+ onCreateView();
+ this.bodyBubble = findViewById(R.id.body_bubble );
+ this.triangleTick = findViewById(R.id.triangle_tick);
+ this.media = (ForegroundImageView) findViewById(R.id.image_view);
+
+ this.shadowColor = ResUtil.getColor(getContext(), R.attr.conversation_item_shadow);
+ this.mmsPendingOverlayColor = ResUtil.getColor(getContext(), R.attr.conversation_item_mms_pending_mask);
+ }
+
+ public void setState(@TransportState int transportState, @MediaState int mediaState) {
+ updateBodyBubble(transportState, mediaState);
+ if (isMediaPresent(mediaState)) {
+ updateMediaBubble(transportState, mediaState);
+ }
+ setMediaVisibility(mediaState);
+ setAlignment(mediaState);
+ setMediaPendingMask(transportState);
+ }
+
+ private void updateBodyBubble(@TransportState int transportState, @MediaState int mediaState) {
+ final boolean hasShadow = mediaState == MEDIA_STATE_CAPTIONED || mediaState == MEDIA_STATE_NO_MEDIA;
+ final BubbleDrawableBuilder builder = new BubbleDrawableBuilder();
+ final int color = getForegroundColor(transportState);
+
+ final Drawable bodyDrawable = builder.setColor(color)
+ .setShadowColor(shadowColor)
+ .setCorners(getMessageCorners(mediaState))
+ .setHasShadow(hasShadow)
+ .create(getContext());
+ ViewUtil.setBackgroundSavingPadding(triangleTick, getTriangleTickRes(transportState));
+ ViewUtil.setBackgroundSavingPadding(bodyBubble, bodyDrawable);
+ }
+
+ private void updateMediaBubble(@TransportState int transportState, @MediaState int mediaState) {
+ final int foregroundColor = getForegroundColor(transportState);
+ final BubbleDrawableBuilder builder = new BubbleDrawableBuilder();
+
+ final Drawable mediaDrawable = builder.setColor(foregroundColor)
+ .setShadowColor(shadowColor)
+ .setCorners(getMediaCorners(mediaState))
+ .setHasShadow(false)
+ .create(getContext());
+ ViewUtil.setBackgroundSavingPadding(media, mediaDrawable);
+ media.setBorderColor(foregroundColor);
+ }
+
+ private void setMediaVisibility(@MediaState int mediaState) {
+ media.reset();
+ if (!isMediaPresent(mediaState)) {
+ media.hide();
+ }
+ }
+
+ private void setMediaPendingMask(@TransportState int transportState) {
+ if (isPending(transportState)) {
+ media.setForeground(new ColorDrawable(mmsPendingOverlayColor));
+ } else {
+ media.setForeground(new ColorDrawable(Color.TRANSPARENT));
+ }
+ }
+
+ private void setAlignment(@MediaState int mediaState) {
+ RelativeLayout.LayoutParams parentParams = (RelativeLayout.LayoutParams) bodyBubble.getLayoutParams();
+ if (mediaState != MEDIA_STATE_CAPTIONED) {
+ parentParams.addRule(RelativeLayout.BELOW, 0);
+ parentParams.addRule(RelativeLayout.ALIGN_BOTTOM, R.id.image_view);
+ } else {
+ parentParams.addRule(RelativeLayout.BELOW, R.id.image_view);
+ parentParams.addRule(RelativeLayout.ALIGN_BOTTOM, 0);
+ }
+ bodyBubble.setLayoutParams(parentParams);
+ }
+
+ private boolean isMediaPresent(@MediaState int mediaState) {
+ return mediaState != MEDIA_STATE_NO_MEDIA;
+ }
+
+ private boolean isPending(@TransportState int transportState) {
+ return transportState == TRANSPORT_STATE_PUSH_PENDING || transportState == TRANSPORT_STATE_SMS_PENDING;
+ }
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/components/BubbleDrawableBuilder.java b/src/org/thoughtcrime/securesms/components/BubbleDrawableBuilder.java
new file mode 100644
index 0000000000..7284a426dd
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/BubbleDrawableBuilder.java
@@ -0,0 +1,76 @@
+package org.thoughtcrime.securesms.components;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
+
+import org.thoughtcrime.securesms.R;
+
+public class BubbleDrawableBuilder {
+ private int color;
+ private int shadowColor;
+ private boolean hasShadow = true;
+ private boolean[] corners = new boolean[]{true,true,true,true};
+
+ protected BubbleDrawableBuilder() { }
+
+ public BubbleDrawableBuilder setColor(int color) {
+ this.color = color;
+ return this;
+ }
+
+ public BubbleDrawableBuilder setShadowColor(int shadowColor) {
+ this.shadowColor = shadowColor;
+ return this;
+ }
+
+ public BubbleDrawableBuilder setHasShadow(boolean hasShadow) {
+ this.hasShadow = hasShadow;
+ return this;
+ }
+
+ public BubbleDrawableBuilder setCorners(boolean[] corners) {
+ this.corners = corners;
+ return this;
+ }
+
+ public Drawable create(Context context) {
+ final GradientDrawable bubble = new GradientDrawable();
+ final int radius = context.getResources().getDimensionPixelSize(R.dimen.message_bubble_corner_radius);
+ final float[] radii = cornerBooleansToRadii(corners, radius);
+
+ bubble.setColor(color);
+ bubble.setCornerRadii(radii);
+
+ if (!hasShadow) {
+ return bubble;
+ } else {
+ final GradientDrawable shadow = new GradientDrawable();
+ final int distance = context.getResources().getDimensionPixelSize(R.dimen.message_bubble_shadow_distance);
+
+ shadow.setColor(shadowColor);
+ shadow.setCornerRadii(radii);
+
+ final LayerDrawable layers = new LayerDrawable(new Drawable[]{shadow, bubble});
+ layers.setLayerInset(1, 0, 0, 0, distance);
+ return layers;
+ }
+ }
+
+ private float[] cornerBooleansToRadii(boolean[] corners, int radius) {
+ if (corners == null || corners.length != 4) {
+ throw new AssertionError("there are four corners in a rectangle, silly");
+ }
+
+ float[] radii = new float[8];
+ int i = 0;
+ for (boolean corner : corners) {
+ radii[i] = radii[i+1] = corner ? radius : 0;
+ i += 2;
+ }
+
+ return radii;
+ }
+
+}
diff --git a/src/org/thoughtcrime/securesms/components/ForegroundImageView.java b/src/org/thoughtcrime/securesms/components/ForegroundImageView.java
index 123d52edb7..f1cf42a462 100644
--- a/src/org/thoughtcrime/securesms/components/ForegroundImageView.java
+++ b/src/org/thoughtcrime/securesms/components/ForegroundImageView.java
@@ -16,21 +16,28 @@
package org.thoughtcrime.securesms.components;
+import android.annotation.TargetApi;
+import android.app.ActivityOptions;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
import android.view.Gravity;
-import android.widget.ImageView;
+import android.view.View;
+import android.view.animation.AlphaAnimation;
+
+import com.makeramen.RoundedImageView;
import org.thoughtcrime.securesms.R;
/**
* https://gist.github.com/chrisbanes/9091754
*/
-public class ForegroundImageView extends ImageView {
+public class ForegroundImageView extends RoundedImageView {
private Drawable mForeground;
@@ -111,12 +118,66 @@ public class ForegroundImageView extends ImageView {
}
}
+ @TargetApi(VERSION_CODES.JELLY_BEAN)
+ public ActivityOptions getThumbnailTransition() {
+ return ActivityOptions.makeScaleUpAnimation(this, 0, 0, getWidth(), getHeight());
+ }
+
+ public void show(Drawable drawable, boolean instantaneous) {
+ setImageDrawable(drawable);
+ if (drawable.getIntrinsicHeight() < (getHeight() * 0.75f) &&
+ drawable.getIntrinsicWidth() < (getHeight() * 0.75f))
+ {
+ setScaleType(ScaleType.CENTER_INSIDE);
+ } else {
+ setScaleType(ScaleType.CENTER_CROP);
+ }
+ fadeIn(instantaneous ? 0 : 200);
+ }
+
+ public void reset() {
+ cancelAnimations();
+ setImageDrawable(null);
+ setVisibility(View.INVISIBLE);
+ }
+
+ public void hide() {
+ setVisibility(View.GONE);
+ }
+
+ private void fadeIn(final long millis) {
+ if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) fadeInModern(millis);
+ else fadeInLegacy(millis);
+ setVisibility(View.VISIBLE);
+ }
+
+ private void fadeInLegacy(final long millis) {
+ final AlphaAnimation alpha = new AlphaAnimation(0f, 1f);
+ alpha.setDuration(millis);
+ alpha.setFillAfter(true);
+ startAnimation(alpha);
+ }
+
+ @TargetApi(VERSION_CODES.JELLY_BEAN)
+ private void fadeInModern(final long millis) {
+ setAlpha(0f);
+ animate().alpha(1f).setDuration(millis);
+ }
+
+ private void cancelAnimations() {
+ if (getAnimation() != null) {
+ getAnimation().cancel();
+ clearAnimation();
+ }
+ }
+
@Override
protected boolean verifyDrawable(Drawable who) {
return super.verifyDrawable(who) || (who == mForeground);
}
@Override
+ @TargetApi(VERSION_CODES.HONEYCOMB)
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
if (mForeground != null) mForeground.jumpToCurrentState();
diff --git a/src/org/thoughtcrime/securesms/components/IncomingBubbleContainer.java b/src/org/thoughtcrime/securesms/components/IncomingBubbleContainer.java
new file mode 100644
index 0000000000..d6f054edd2
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/IncomingBubbleContainer.java
@@ -0,0 +1,87 @@
+/**
+ * Copyright (C) 2015 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.content.Context;
+import android.support.annotation.DrawableRes;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+
+import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.util.ResUtil;
+
+public class IncomingBubbleContainer extends BubbleContainer {
+ private static final String TAG = IncomingBubbleContainer.class.getSimpleName();
+
+ private static final boolean[] CORNERS_MESSAGE_CAPTIONED = new boolean[]{false, true, true, true };
+ private static final boolean[] CORNERS_MEDIA_CAPTIONED = new boolean[]{true, true, true, false};
+ private static final boolean[] CORNERS_ROUNDED = new boolean[]{true, true, true, true };
+
+ private int foregroundColor;
+ private int triangleTickRes;
+
+ @SuppressWarnings("UnusedDeclaration")
+ public IncomingBubbleContainer(Context context) {
+ super(context);
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ public IncomingBubbleContainer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ public IncomingBubbleContainer(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ public IncomingBubbleContainer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onCreateView() {
+ Log.w(TAG, "onCreateView()");
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ inflater.inflate(R.layout.conversation_bubble_incoming, this, true);
+
+ this.foregroundColor = ResUtil.getColor(getContext(), R.attr.conversation_item_received_background);
+ this.triangleTickRes = ResUtil.getDrawableRes(getContext(), R.attr.triangle_tick_incoming);
+ }
+
+ @Override
+ protected int getForegroundColor(@TransportState int transportState) {
+ return foregroundColor;
+ }
+
+ @Override
+ protected boolean[] getMessageCorners(@MediaState int mediaState) {
+ return mediaState == MEDIA_STATE_CAPTIONED ? CORNERS_MESSAGE_CAPTIONED : CORNERS_ROUNDED;
+ }
+
+ @Override
+ protected boolean[] getMediaCorners(@MediaState int mediaState) {
+ return mediaState == MEDIA_STATE_CAPTIONED ? CORNERS_MEDIA_CAPTIONED : CORNERS_ROUNDED;
+ }
+
+ @Override
+ protected int getTriangleTickRes(@TransportState int transportState) {
+ return triangleTickRes;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/components/OutgoingBubbleContainer.java b/src/org/thoughtcrime/securesms/components/OutgoingBubbleContainer.java
new file mode 100644
index 0000000000..61e94a32e2
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/components/OutgoingBubbleContainer.java
@@ -0,0 +1,105 @@
+/**
+ * Copyright (C) 2015 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.content.Context;
+import android.content.res.TypedArray;
+import android.support.annotation.DrawableRes;
+import android.util.AttributeSet;
+import android.util.SparseIntArray;
+import android.view.LayoutInflater;
+
+import org.thoughtcrime.securesms.R;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class OutgoingBubbleContainer extends BubbleContainer {
+ private static final boolean[] CORNERS_MESSAGE_CAPTIONED = new boolean[]{true, false, true, true};
+ private static final boolean[] CORNERS_MEDIA_CAPTIONED = new boolean[]{true, true, false, true};
+ private static final boolean[] CORNERS_ROUNDED = new boolean[]{true, true, true, true};
+
+ private static final int[] TRANSPORT_STYLE_ATTRIBUTES = new int[]{R.attr.conversation_item_sent_push_background,
+ R.attr.conversation_item_sent_background,
+ R.attr.conversation_item_sent_pending_background,
+ R.attr.conversation_item_sent_push_pending_background};
+
+ private static final int[] TRIANGLE_TICK_ATTRIBUTES = new int[]{R.attr.triangle_tick_outgoing_sent_push,
+ R.attr.triangle_tick_outgoing_sent_sms,
+ R.attr.triangle_tick_outgoing_pending_sms,
+ R.attr.triangle_tick_outgoing_pending_push};
+
+ private static final SparseIntArray TRANSPORT_STYLE_MAP = new SparseIntArray(TRANSPORT_STYLE_ATTRIBUTES.length) {{
+ put(TRANSPORT_STATE_PUSH_SENT, 0);
+ put(TRANSPORT_STATE_SMS_SENT, 1);
+ put(TRANSPORT_STATE_SMS_PENDING, 2);
+ put(TRANSPORT_STATE_PUSH_PENDING, 3);
+ }};
+
+ private TypedArray styledDrawables;
+ private TypedArray triangleDrawables;
+
+ @SuppressWarnings("UnusedDeclaration")
+ public OutgoingBubbleContainer(Context context) {
+ super(context);
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ public OutgoingBubbleContainer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ public OutgoingBubbleContainer(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ public OutgoingBubbleContainer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onCreateView() {
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ inflater.inflate(R.layout.conversation_bubble_outgoing, this, true);
+
+ this.styledDrawables = getContext().obtainStyledAttributes(TRANSPORT_STYLE_ATTRIBUTES);
+ this.triangleDrawables = getContext().obtainStyledAttributes(TRIANGLE_TICK_ATTRIBUTES );
+ }
+
+ @Override
+ protected int getForegroundColor(@TransportState int transportState) {
+ return styledDrawables.getColor(TRANSPORT_STYLE_MAP.get(transportState), -1);
+ }
+
+ @Override
+ protected boolean[] getMessageCorners(@MediaState int mediaState) {
+ return mediaState == MEDIA_STATE_CAPTIONED ? CORNERS_MESSAGE_CAPTIONED : CORNERS_ROUNDED;
+ }
+
+ @Override
+ protected boolean[] getMediaCorners(@MediaState int mediaState) {
+ return mediaState == MEDIA_STATE_CAPTIONED ? CORNERS_MEDIA_CAPTIONED : CORNERS_ROUNDED;
+ }
+
+ @Override
+ protected int getTriangleTickRes(@TransportState int transportState) {
+ return triangleDrawables.getResourceId(TRANSPORT_STYLE_MAP.get(transportState), -1);
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
index 25951162d2..38e70072b0 100644
--- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
+++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java
@@ -26,14 +26,15 @@ import android.os.AsyncTask;
import android.os.Build;
import android.util.Log;
import android.provider.ContactsContract;
+import android.util.Pair;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;
import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
+import org.thoughtcrime.securesms.util.FutureTaskListener;
import java.io.IOException;
@@ -79,20 +80,25 @@ public class AttachmentManager {
public void setMedia(final Slide slide, final int thumbnailWidth, final int thumbnailHeight) {
slideDeck.clear();
slideDeck.addSlide(slide);
- new AsyncTask() {
-
+ slide.getThumbnail(context).addListener(new FutureTaskListener>() {
@Override
- protected Drawable doInBackground(Void... params) {
- return slide.getThumbnail(context, thumbnailWidth, thumbnailHeight);
+ public void onSuccess(final Pair result) {
+ thumbnail.post(new Runnable() {
+ @Override
+ public void run() {
+ thumbnail.setImageDrawable(result.first);
+ attachmentView.setVisibility(View.VISIBLE);
+ attachmentListener.onAttachmentChanged();
+ }
+ });
}
@Override
- protected void onPostExecute(Drawable drawable) {
- thumbnail.setImageDrawable(drawable);
- attachmentView.setVisibility(View.VISIBLE);
- attachmentListener.onAttachmentChanged();
+ public void onFailure(Throwable error) {
+ Log.w(TAG, error);
+ slideDeck.clear();
}
- }.execute();
+ });
}
public void setMedia(Slide slide) {
diff --git a/src/org/thoughtcrime/securesms/mms/AudioSlide.java b/src/org/thoughtcrime/securesms/mms/AudioSlide.java
index e25adf3bcf..1ee52f8a33 100644
--- a/src/org/thoughtcrime/securesms/mms/AudioSlide.java
+++ b/src/org/thoughtcrime/securesms/mms/AudioSlide.java
@@ -20,20 +20,20 @@ import java.io.IOException;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.util.ListenableFutureTask;
+import org.thoughtcrime.securesms.util.ResUtil;
import org.thoughtcrime.securesms.util.SmilUtil;
-import org.thoughtcrime.securesms.util.ThemeUtil;
import org.w3c.dom.smil.SMILDocument;
import org.w3c.dom.smil.SMILMediaElement;
import org.w3c.dom.smil.SMILRegionElement;
-import org.w3c.dom.smil.SMILRegionMediaElement;
import ws.com.google.android.mms.pdu.PduPart;
import android.content.Context;
-import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.MediaStore.Audio;
+import android.util.Pair;
public class AudioSlide extends Slide {
@@ -66,8 +66,8 @@ public class AudioSlide extends Slide {
}
@Override
- public Drawable getThumbnail(Context context, int maxWidth, int maxHeight) {
- return ThemeUtil.resolveIcon(context, R.attr.conversation_icon_attach_audio);
+ public ListenableFutureTask> getThumbnail(Context context) {
+ return new ListenableFutureTask<>(new Pair<>(ResUtil.getDrawable(context, R.attr.conversation_icon_attach_audio), true));
}
public static PduPart constructPartFromUri(Context context, Uri uri) throws IOException, MediaTooLargeException {
diff --git a/src/org/thoughtcrime/securesms/mms/ImageSlide.java b/src/org/thoughtcrime/securesms/mms/ImageSlide.java
index 3533b58c47..46b9a048d4 100644
--- a/src/org/thoughtcrime/securesms/mms/ImageSlide.java
+++ b/src/org/thoughtcrime/securesms/mms/ImageSlide.java
@@ -18,38 +18,32 @@ package org.thoughtcrime.securesms.mms;
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Color;
-import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.TransitionDrawable;
import android.net.Uri;
-import android.os.Handler;
+import android.os.AsyncTask;
import android.util.Log;
-import android.widget.ImageView;
+import android.util.Pair;
import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.LRUCache;
+import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.MediaUtil;
-import org.thoughtcrime.securesms.util.MediaUtil.ThumbnailData;
+import org.thoughtcrime.securesms.util.ResUtil;
import org.thoughtcrime.securesms.util.SmilUtil;
-import org.thoughtcrime.securesms.util.Util;
import org.w3c.dom.smil.SMILDocument;
import org.w3c.dom.smil.SMILMediaElement;
import org.w3c.dom.smil.SMILRegionElement;
import org.thoughtcrime.securesms.crypto.MasterSecret;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.Map;
+import java.util.concurrent.Callable;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.pdu.PduPart;
@@ -70,106 +64,50 @@ public class ImageSlide extends Slide {
}
@Override
- public Drawable getThumbnail(Context context, int maxWidth, int maxHeight) {
+ public ListenableFutureTask> getThumbnail(Context context) {
+ if (getPart().isPendingPush()) {
+ return new ListenableFutureTask<>(new Pair<>(context.getResources().getDrawable(R.drawable.stat_sys_download), true));
+ }
+
Drawable thumbnail = getCachedThumbnail();
-
if (thumbnail != null) {
- return thumbnail;
+ Log.w(TAG, "getThumbnail() returning cached thumbnail");
+ return new ListenableFutureTask<>(new Pair<>(thumbnail, true));
}
- if (part.isPendingPush()) {
- return context.getResources().getDrawable(R.drawable.stat_sys_download);
- }
-
- try {
- Bitmap thumbnailBitmap;
- long startDecode = System.currentTimeMillis();
-
- if (part.getDataUri() != null && part.getId() > -1) {
- thumbnailBitmap = BitmapFactory.decodeStream(DatabaseFactory.getPartDatabase(context)
- .getThumbnailStream(masterSecret, part.getId()));
- } else if (part.getDataUri() != null) {
- Log.w(TAG, "generating thumbnail for new part");
- ThumbnailData thumbnailData = MediaUtil.generateThumbnail(context, masterSecret,
- part.getDataUri(), Util.toIsoString(part.getContentType()));
- thumbnailBitmap = thumbnailData.getBitmap();
- part.setThumbnail(thumbnailBitmap);
- } else {
- throw new FileNotFoundException("no data location specified");
- }
-
- Log.w(TAG, "thumbnail decode/generate time: " + (System.currentTimeMillis() - startDecode) + "ms");
-
- thumbnail = new BitmapDrawable(context.getResources(), thumbnailBitmap);
- thumbnailCache.put(part.getDataUri(), new SoftReference<>(thumbnail));
-
- return thumbnail;
- } catch (IOException | BitmapDecodingException e) {
- Log.w(TAG, e);
- return context.getResources().getDrawable(R.drawable.ic_missing_thumbnail_picture);
- }
+ Log.w(TAG, "getThumbnail() resolving thumbnail, as it wasn't cached");
+ return resolveThumbnail(context);
}
- @Override
- public void setThumbnailOn(Context context, ImageView imageView) {
- setThumbnailOn(context, imageView, imageView.getWidth(), imageView.getHeight(), new ColorDrawable(Color.TRANSPARENT));
- }
+ private ListenableFutureTask> resolveThumbnail(Context context) {
+ final WeakReference weakContext = new WeakReference<>(context);
- @Override
- public void setThumbnailOn(Context context, ImageView imageView, final int width, final int height, final Drawable placeholder) {
- Drawable thumbnail = getCachedThumbnail();
-
- if (thumbnail != null) {
- Log.w("ImageSlide", "Setting cached thumbnail...");
- setThumbnailOn(imageView, thumbnail, true);
- return;
- }
-
- final WeakReference weakContext = new WeakReference<>(context);
- final WeakReference weakImageView = new WeakReference<>(imageView);
- final Handler handler = new Handler();
-
- imageView.setImageDrawable(placeholder);
-
- if (width == 0 || height == 0)
- return;
-
- MmsDatabase.slideResolver.execute(new Runnable() {
+ Callable> slideCallable = new Callable>() {
@Override
- public void run() {
+ public Pair call() throws Exception {
final Context context = weakContext.get();
if (context == null) {
Log.w(TAG, "context SoftReference was null, leaving");
- return;
+ return null;
}
- final Drawable bitmap = getThumbnail(context, width, height);
- final ImageView destination = weakImageView.get();
+ try {
+ final long startDecode = System.currentTimeMillis();
+ final Bitmap thumbnailBitmap = MediaUtil.getOrGenerateThumbnail(context, masterSecret, part);
+ final Drawable thumbnail = new BitmapDrawable(context.getResources(), thumbnailBitmap);
+ Log.w(TAG, "thumbnail decode/generate time: " + (System.currentTimeMillis() - startDecode) + "ms");
- Log.w(TAG, "slide resolved, destination available? " + (destination == null));
- if (destination != null && destination.getDrawable() == placeholder) {
- handler.post(new Runnable() {
- @Override
- public void run() {
- setThumbnailOn(destination, bitmap, false);
- }
- });
+ thumbnailCache.put(part.getDataUri(), new SoftReference<>(thumbnail));
+ return new Pair<>(thumbnail, false);
+ } catch (IOException | BitmapDecodingException e) {
+ Log.w(TAG, e);
+ return new Pair<>(context.getResources().getDrawable(R.drawable.ic_missing_thumbnail_picture), false);
}
}
- });
- }
-
- private void setThumbnailOn(ImageView imageView, Drawable thumbnail, boolean fromMemory) {
- if (fromMemory) {
- imageView.setImageDrawable(thumbnail);
- } else if (thumbnail instanceof AnimationDrawable) {
- imageView.setImageDrawable(thumbnail);
- ((AnimationDrawable)imageView.getDrawable()).start();
- } else {
- TransitionDrawable fadingResult = new TransitionDrawable(new Drawable[]{imageView.getDrawable(), thumbnail});
- imageView.setImageDrawable(fadingResult);
- fadingResult.startTransition(300);
- }
+ };
+ ListenableFutureTask> futureTask = new ListenableFutureTask<>(slideCallable);
+ MmsDatabase.slideResolver.execute(futureTask);
+ return futureTask;
}
private Drawable getCachedThumbnail() {
diff --git a/src/org/thoughtcrime/securesms/mms/PartAuthority.java b/src/org/thoughtcrime/securesms/mms/PartAuthority.java
index 123c45faf3..e6b5105d7a 100644
--- a/src/org/thoughtcrime/securesms/mms/PartAuthority.java
+++ b/src/org/thoughtcrime/securesms/mms/PartAuthority.java
@@ -43,6 +43,18 @@ public class PartAuthority {
}
}
+ public static InputStream getThumbnail(Context context, MasterSecret masterSecret, Uri uri)
+ throws IOException
+ {
+ PartDatabase partDatabase = DatabaseFactory.getPartDatabase(context);
+ int match = uriMatcher.match(uri);
+
+ switch (match) {
+ case PART_ROW: return partDatabase.getThumbnailStream(masterSecret, ContentUris.parseId(uri));
+ default: return null;
+ }
+ }
+
public static Uri getPublicPartUri(Uri uri) {
return ContentUris.withAppendedId(PartProvider.CONTENT_URI, ContentUris.parseId(uri));
}
diff --git a/src/org/thoughtcrime/securesms/mms/Slide.java b/src/org/thoughtcrime/securesms/mms/Slide.java
index 4a1fd2076b..5947c940d7 100644
--- a/src/org/thoughtcrime/securesms/mms/Slide.java
+++ b/src/org/thoughtcrime/securesms/mms/Slide.java
@@ -19,6 +19,7 @@ package org.thoughtcrime.securesms.mms;
import java.io.IOException;
import java.io.InputStream;
+import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.Util;
import org.w3c.dom.smil.SMILDocument;
import org.w3c.dom.smil.SMILMediaElement;
@@ -30,8 +31,7 @@ import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.Log;
-import android.util.TypedValue;
-import android.widget.ImageView;
+import android.util.Pair;
import ws.com.google.android.mms.pdu.PduPart;
@@ -71,20 +71,10 @@ public abstract class Slide {
return part.getDataUri();
}
- public Drawable getThumbnail(Context context, int maxWidth, int maxHeight) {
+ public ListenableFutureTask> getThumbnail(Context context) {
throw new AssertionError("getThumbnail() called on non-thumbnail producing slide!");
}
- public void setThumbnailOn(Context context, ImageView imageView) {
- imageView.setImageDrawable(getThumbnail(context, imageView.getWidth(), imageView.getHeight()));
- }
-
- public void setThumbnailOn(Context context, ImageView imageView, int height, int width, Drawable placeholder) {
- imageView.setImageDrawable(getThumbnail(context, width, height));
- }
-
- public Bitmap getGeneratedThumbnail() { return null; }
-
public boolean hasImage() {
return false;
}
diff --git a/src/org/thoughtcrime/securesms/mms/SlideDeck.java b/src/org/thoughtcrime/securesms/mms/SlideDeck.java
index 3f664a6663..82793e1a0d 100644
--- a/src/org/thoughtcrime/securesms/mms/SlideDeck.java
+++ b/src/org/thoughtcrime/securesms/mms/SlideDeck.java
@@ -17,9 +17,13 @@
package org.thoughtcrime.securesms.mms;
import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.Pair;
+import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.dom.smil.parser.SmilXmlSerializer;
+import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.SmilUtil;
import org.thoughtcrime.securesms.util.Util;
@@ -90,8 +94,15 @@ public class SlideDeck {
return true;
}
}
-
return false;
}
+ public Slide getThumbnailSlide(Context context) {
+ for (Slide slide : slides) {
+ if (slide.hasImage()) {
+ return slide;
+ }
+ }
+ return null;
+ }
}
diff --git a/src/org/thoughtcrime/securesms/mms/VideoSlide.java b/src/org/thoughtcrime/securesms/mms/VideoSlide.java
index fd19f9493f..2e773ed131 100644
--- a/src/org/thoughtcrime/securesms/mms/VideoSlide.java
+++ b/src/org/thoughtcrime/securesms/mms/VideoSlide.java
@@ -20,8 +20,9 @@ import java.io.IOException;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.util.ListenableFutureTask;
+import org.thoughtcrime.securesms.util.ResUtil;
import org.thoughtcrime.securesms.util.SmilUtil;
-import org.thoughtcrime.securesms.util.ThemeUtil;
import org.w3c.dom.smil.SMILDocument;
import org.w3c.dom.smil.SMILMediaElement;
import org.w3c.dom.smil.SMILRegionElement;
@@ -29,12 +30,12 @@ import org.w3c.dom.smil.SMILRegionElement;
import ws.com.google.android.mms.pdu.PduPart;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Log;
+import android.util.Pair;
public class VideoSlide extends Slide {
@@ -47,8 +48,8 @@ public class VideoSlide extends Slide {
}
@Override
- public Drawable getThumbnail(Context context, int width, int height) {
- return ThemeUtil.resolveIcon(context, R.attr.conversation_icon_attach_video);
+ public ListenableFutureTask> getThumbnail(Context context) {
+ return new ListenableFutureTask<>(new Pair<>(ResUtil.getDrawable(context, R.attr.conversation_icon_attach_video), true));
}
@Override
diff --git a/src/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java b/src/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java
index a1870fb23b..e90bbd41e3 100644
--- a/src/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java
+++ b/src/org/thoughtcrime/securesms/preferences/AppProtectionPreferenceFragment.java
@@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.Dialogs;
+import org.thoughtcrime.securesms.util.ResUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.concurrent.TimeUnit;
@@ -123,7 +124,7 @@ public class AppProtectionPreferenceFragment extends PreferenceFragment {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.ApplicationPreferencesActivity_disable_storage_encryption);
builder.setMessage(R.string.ApplicationPreferencesActivity_warning_this_will_disable_storage_encryption_for_all_messages);
- builder.setIcon(Dialogs.resolveIcon(getActivity(), R.attr.dialog_alert_icon));
+ builder.setIcon(ResUtil.getDrawable(getActivity(), R.attr.dialog_alert_icon));
builder.setPositiveButton(R.string.ApplicationPreferencesActivity_disable, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
diff --git a/src/org/thoughtcrime/securesms/preferences/LedBlinkPatternListPreference.java b/src/org/thoughtcrime/securesms/preferences/LedBlinkPatternListPreference.java
index 01b6f9103c..a6e3745eb3 100644
--- a/src/org/thoughtcrime/securesms/preferences/LedBlinkPatternListPreference.java
+++ b/src/org/thoughtcrime/securesms/preferences/LedBlinkPatternListPreference.java
@@ -33,6 +33,7 @@ import android.widget.Toast;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.R;
+import org.thoughtcrime.securesms.util.ResUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Dialogs;
@@ -82,7 +83,7 @@ public class LedBlinkPatternListPreference extends ListPreference implements OnS
private void initializeDialog(View view) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setIcon(Dialogs.resolveIcon(context, R.attr.dialog_info_icon));
+ builder.setIcon(ResUtil.getDrawable(context, R.attr.dialog_info_icon));
builder.setTitle(R.string.preferences__pref_led_blink_custom_pattern_title);
builder.setView(view);
builder.setOnCancelListener(new CustomDialogCancelListener());
diff --git a/src/org/thoughtcrime/securesms/util/Dialogs.java b/src/org/thoughtcrime/securesms/util/Dialogs.java
index 44d40ce594..74a2985444 100644
--- a/src/org/thoughtcrime/securesms/util/Dialogs.java
+++ b/src/org/thoughtcrime/securesms/util/Dialogs.java
@@ -28,22 +28,17 @@ public class Dialogs {
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
dialog.setTitle(title);
dialog.setMessage(message);
- dialog.setIcon(resolveIcon(context, R.attr.dialog_alert_icon));
- dialog.setPositiveButton(android.R.string.ok, null);
- dialog.show();
- }
- public static void showInfoDialog(Context context, String title, String message) {
- AlertDialog.Builder dialog = new AlertDialog.Builder(context);
- dialog.setTitle(title);
- dialog.setMessage(message);
- dialog.setIcon(resolveIcon(context, R.attr.dialog_info_icon));
+ dialog.setIcon(ResUtil.getDrawable(context, R.attr.dialog_alert_icon));
dialog.setPositiveButton(android.R.string.ok, null);
dialog.show();
}
- public static Drawable resolveIcon(Context c, int iconAttr) {
- TypedValue out = new TypedValue();
- c.getTheme().resolveAttribute(iconAttr, out, true);
- return c.getResources().getDrawable(out.resourceId);
+ public static void showInfoDialog(Context context, String title, String message) {
+ AlertDialog.Builder dialog = new AlertDialog.Builder(context);
+ dialog.setTitle(title);
+ dialog.setMessage(message);
+ dialog.setIcon(ResUtil.getDrawable(context, R.attr.dialog_info_icon));
+ dialog.setPositiveButton(android.R.string.ok, null);
+ dialog.show();
}
}
diff --git a/src/org/thoughtcrime/securesms/util/ListenableFutureTask.java b/src/org/thoughtcrime/securesms/util/ListenableFutureTask.java
index a0fdd1bd9f..3fe7ba13ba 100644
--- a/src/org/thoughtcrime/securesms/util/ListenableFutureTask.java
+++ b/src/org/thoughtcrime/securesms/util/ListenableFutureTask.java
@@ -30,6 +30,16 @@ public class ListenableFutureTask extends FutureTask {
super(callable);
}
+ public ListenableFutureTask(final V result) {
+ super(new Callable() {
+ @Override
+ public V call() throws Exception {
+ return result;
+ }
+ });
+ this.run();
+ }
+
public synchronized void addListener(FutureTaskListener listener) {
if (this.isDone()) {
callback(listener);
diff --git a/src/org/thoughtcrime/securesms/util/MediaUtil.java b/src/org/thoughtcrime/securesms/util/MediaUtil.java
index 5bcf1137a5..0ac915788e 100644
--- a/src/org/thoughtcrime/securesms/util/MediaUtil.java
+++ b/src/org/thoughtcrime/securesms/util/MediaUtil.java
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.util;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Log;
@@ -50,6 +51,22 @@ public class MediaUtil {
return data;
}
+ public static Bitmap getOrGenerateThumbnail(Context context, MasterSecret masterSecret, PduPart part)
+ throws IOException, BitmapDecodingException
+ {
+ if (part.getDataUri() != null && part.getId() > -1) {
+ return BitmapFactory.decodeStream(DatabaseFactory.getPartDatabase(context)
+ .getThumbnailStream(masterSecret, part.getId()));
+ } else if (part.getDataUri() != null) {
+ Log.w(TAG, "generating thumbnail for new part");
+ Bitmap bitmap = MediaUtil.generateThumbnail(context, masterSecret, part.getDataUri(), Util.toIsoString(part.getContentType())).getBitmap();
+ part.setThumbnail(bitmap);
+ return bitmap;
+ } else {
+ throw new FileNotFoundException("no data location specified");
+ }
+ }
+
public static byte[] getPartData(Context context, MasterSecret masterSecret, PduPart part)
throws IOException
{
diff --git a/src/org/thoughtcrime/securesms/util/ThemeUtil.java b/src/org/thoughtcrime/securesms/util/ResUtil.java
similarity index 54%
rename from src/org/thoughtcrime/securesms/util/ThemeUtil.java
rename to src/org/thoughtcrime/securesms/util/ResUtil.java
index d48621916c..a63dace4e9 100644
--- a/src/org/thoughtcrime/securesms/util/ThemeUtil.java
+++ b/src/org/thoughtcrime/securesms/util/ResUtil.java
@@ -18,14 +18,27 @@
package org.thoughtcrime.securesms.util;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
+import android.support.annotation.AttrRes;
import android.util.TypedValue;
-public class ThemeUtil {
- public static Drawable resolveIcon(Context c, int iconAttr)
- {
- TypedValue out = new TypedValue();
- c.getTheme().resolveAttribute(iconAttr, out, true);
- return c.getResources().getDrawable(out.resourceId);
+public class ResUtil {
+
+ public static int getColor(Context context, @AttrRes int attr) {
+ final TypedArray styledAttributes = context.obtainStyledAttributes(new int[]{attr});
+ final int result = styledAttributes.getColor(0, -1);
+ styledAttributes.recycle();
+ return result;
+ }
+
+ public static int getDrawableRes(Context c, @AttrRes int attr) {
+ final TypedValue out = new TypedValue();
+ c.getTheme().resolveAttribute(attr, out, true);
+ return out.resourceId;
+ }
+
+ public static Drawable getDrawable(Context c, @AttrRes int attr) {
+ return c.getResources().getDrawable(getDrawableRes(c, attr));
}
}
diff --git a/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java b/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java
index e69639ac50..f63608a87f 100644
--- a/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java
+++ b/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.java
@@ -152,7 +152,7 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask.
+ */
+package org.thoughtcrime.securesms.util;
+
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.view.View;
+
+public class ViewUtil {
+ public static void setBackgroundSavingPadding(View v, Drawable drawable) {
+ final int paddingBottom = v.getPaddingBottom();
+ final int paddingLeft = v.getPaddingLeft();
+ final int paddingRight = v.getPaddingRight();
+ final int paddingTop = v.getPaddingTop();
+ v.setBackgroundDrawable(drawable);
+ v.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
+ }
+
+ public static void setBackgroundSavingPadding(View v, @DrawableRes int resId) {
+ final int paddingBottom = v.getPaddingBottom();
+ final int paddingLeft = v.getPaddingLeft();
+ final int paddingRight = v.getPaddingRight();
+ final int paddingTop = v.getPaddingTop();
+ v.setBackgroundResource(resId);
+ v.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
+ }
+}