add borderless thumbnails, "bubble" refactor

Closes #2430

// FREEBIE
This commit is contained in:
Jake McGinty 2015-01-29 20:37:01 -10:00 committed by Moxie Marlinspike
parent a4e18c515c
commit 4185006147
75 changed files with 1384 additions and 1001 deletions

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_received_shadow_light" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
<item android:bottom="@dimen/conversation_item_drop_shadow_dist">
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/conversation_item_received_background_light" />
<!--stroke android:width="0.5dp" android:color="#03000000" /-->
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
</layer-list>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_received_shadow_dark" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
<item android:bottom="@dimen/conversation_item_drop_shadow_dist">
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/conversation_item_received_background_dark" />
<!--stroke android:width="0.5dp" android:color="#03000000" /-->
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
</layer-list>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<rotate
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="110%"
android:pivotY="120%" >
<shape
android:shape="rectangle" >
<solid
android:color="@color/conversation_item_received_background_light" />
</shape>
</rotate>
</item>
</layer-list>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<rotate
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="110%"
android:pivotY="120%" >
<shape
android:shape="rectangle" >
<solid
android:color="@color/conversation_item_received_background_dark" />
</shape>
</rotate>
</item>
</layer-list>

View File

@ -4,14 +4,14 @@
<item>
<shape android:shape="rectangle">
<solid android:color="#09000000" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
<corners android:radius="@dimen/message_bubble_corner_radius" />
</shape>
</item>
<item android:bottom="@dimen/conversation_item_drop_shadow_dist">
<item android:bottom="@dimen/message_bubble_shadow_distance">
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/conversation_item_received_background_light" />
<corners android:bottomLeftRadius="@dimen/conversation_item_corner_radius" android:bottomRightRadius="@dimen/conversation_item_corner_radius" />
<corners android:bottomLeftRadius="@dimen/message_bubble_corner_radius" android:bottomRightRadius="@dimen/message_bubble_corner_radius" />
</shape>
</item>

View File

@ -4,14 +4,14 @@
<item>
<shape android:shape="rectangle">
<solid android:color="#09000000" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
<corners android:radius="@dimen/message_bubble_corner_radius" />
</shape>
</item>
<item android:bottom="@dimen/conversation_item_drop_shadow_dist">
<item android:bottom="@dimen/message_bubble_shadow_distance">
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/conversation_item_received_background_dark" />
<corners android:bottomLeftRadius="@dimen/conversation_item_corner_radius" android:bottomRightRadius="@dimen/conversation_item_corner_radius" />
<corners android:bottomLeftRadius="@dimen/message_bubble_corner_radius" android:bottomRightRadius="@dimen/message_bubble_corner_radius" />
</shape>
</item>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_sent_shadow_light" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
<item android:bottom="@dimen/conversation_item_drop_shadow_dist">
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/conversation_item_sent_pending_background_light" />
<!--stroke android:width="0.5dp" android:color="#03000000" /-->
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
</layer-list>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_sent_shadow_dark" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
<item android:bottom="@dimen/conversation_item_drop_shadow_dist">
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/conversation_item_sent_pending_background_dark" />
<!--stroke android:width="0.5dp" android:color="#03000000" /-->
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
</layer-list>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<rotate
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="0%"
android:pivotY="-30%" >
<shape
android:shape="rectangle" >
<solid
android:color="@color/conversation_item_sent_pending_background_light" />
</shape>
</rotate>
</item>
</layer-list>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<rotate
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="0%"
android:pivotY="-30%" >
<shape
android:shape="rectangle" >
<solid
android:color="@color/conversation_item_sent_pending_background_dark" />
</shape>
</rotate>
</item>
</layer-list>

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_sent_shadow_light" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
<item android:bottom="@dimen/conversation_item_drop_shadow_dist">
<shape>
<solid android:color="@color/conversation_item_sent_push_pending_background_light" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
</layer-list>

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_sent_shadow_dark" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
<item android:bottom="@dimen/conversation_item_drop_shadow_dist">
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/conversation_item_sent_push_pending_background_dark" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
</layer-list>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<rotate
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="0%"
android:pivotY="-30%" >
<shape
android:shape="rectangle" >
<solid
android:color="@color/conversation_item_sent_push_pending_background_light" />
</shape>
</rotate>
</item>
</layer-list>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<rotate
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="0%"
android:pivotY="-30%" >
<shape
android:shape="rectangle" >
<solid
android:color="@color/conversation_item_sent_push_pending_background_dark" />
</shape>
</rotate>
</item>
</layer-list>

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_sent_shadow_light" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
<item android:bottom="@dimen/conversation_item_drop_shadow_dist">
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/conversation_item_sent_push_background_light" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
</layer-list>

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_sent_shadow_dark" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
<item android:bottom="@dimen/conversation_item_drop_shadow_dist">
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/conversation_item_sent_push_background_dark" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
</layer-list>

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<rotate android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="0%"
android:pivotY="-30%">
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_sent_push_background_light" />
</shape>
</rotate>
</item>
</layer-list>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<rotate
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="0%"
android:pivotY="-30%" >
<shape
android:shape="rectangle" >
<solid
android:color="@color/conversation_item_sent_push_background_dark" />
</shape>
</rotate>
</item>
</layer-list>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_sent_shadow_light" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
<item android:bottom="@dimen/conversation_item_drop_shadow_dist">
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/conversation_item_sent_background_light" />
<!--stroke android:width="0.5dp" android:color="#03000000" /-->
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
</layer-list>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_sent_shadow_dark" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
<item android:bottom="@dimen/conversation_item_drop_shadow_dist">
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/conversation_item_sent_background_dark" />
<!--stroke android:width="0.5dp" android:color="#03000000" /-->
<corners android:radius="@dimen/conversation_item_corner_radius" />
</shape>
</item>
</layer-list>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<rotate
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="0%"
android:pivotY="-30%" >
<shape
android:shape="rectangle" >
<solid
android:color="@color/conversation_item_sent_background_light" />
</shape>
</rotate>
</item>
</layer-list>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<rotate
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="0%"
android:pivotY="-30%">
<shape
android:shape="rectangle" >
<solid
android:color="@color/conversation_item_sent_background_dark" />
</shape>
</rotate>
</item>
</layer-list>

View File

@ -4,14 +4,14 @@
<item>
<shape android:shape="rectangle">
<solid android:color="@color/import_export_item_background_shadow_dark" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
<corners android:radius="@dimen/message_bubble_corner_radius" />
</shape>
</item>
<item android:bottom="@dimen/conversation_item_drop_shadow_dist">
<item android:bottom="@dimen/message_bubble_shadow_distance">
<shape>
<solid android:color="@color/import_export_item_background_dark" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
<corners android:radius="@dimen/message_bubble_corner_radius" />
</shape>
</item>

View File

@ -4,14 +4,14 @@
<item>
<shape android:shape="rectangle">
<solid android:color="@color/import_export_item_background_shadow_light" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
<corners android:radius="@dimen/message_bubble_corner_radius" />
</shape>
</item>
<item android:bottom="@dimen/conversation_item_drop_shadow_dist">
<item android:bottom="@dimen/message_bubble_shadow_distance">
<shape>
<solid android:color="@color/import_export_item_background_light" />
<corners android:radius="@dimen/conversation_item_corner_radius" />
<corners android:radius="@dimen/message_bubble_corner_radius" />
</shape>
</item>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="110%"
android:pivotY="120%">
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_received_background_dark" />
</shape>
</rotate>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="110%"
android:pivotY="120%">
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_received_background_light" />
</shape>
</rotate>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="0%"
android:pivotY="-30%">
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_sent_push_pending_background_dark" />
</shape>
</rotate>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="0%"
android:pivotY="-30%">
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_sent_push_pending_background_light" />
</shape>
</rotate>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="0%"
android:pivotY="-30%">
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_sent_pending_background_dark" />
</shape>
</rotate>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="0%"
android:pivotY="-30%">
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_sent_pending_background_light" />
</shape>
</rotate>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="0%"
android:pivotY="-30%">
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_sent_push_background_dark" />
</shape>
</rotate>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="0%"
android:pivotY="-30%">
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_sent_push_background_light" />
</shape>
</rotate>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="0%"
android:pivotY="-30%">
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_sent_background_dark" />
</shape>
</rotate>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="45"
android:toDegrees="45"
android:pivotX="0%"
android:pivotY="-30%">
<shape android:shape="rectangle">
<solid android:color="@color/conversation_item_sent_background_light" />
</shape>
</rotate>

View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<View android:id="@+id/triangle_tick"
android:layout_width="12dp"
android:layout_height="15dp"
android:layout_marginTop="12dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<org.thoughtcrime.securesms.components.ForegroundImageView
android:id="@+id/image_view"
android:layout_width="wrap_content"
android:layout_height="@dimen/media_bubble_height"
android:layout_marginRight="@dimen/message_bubble_end_padding"
android:visibility="gone"
android:layout_toRightOf="@id/triangle_tick"
android:layout_toEndOf="@id/triangle_tick"
android:layout_gravity="center"
android:scaleType="centerCrop"
android:adjustViewBounds="true"
android:contentDescription="@string/conversation_item__mms_image_description"
app:riv_corner_radius="@dimen/message_bubble_corner_radius"
app:riv_border_width="@dimen/media_bubble_border_width"
tools:src="@drawable/ic_video_light" />
<LinearLayout android:id="@+id/body_bubble"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/triangle_tick"
android:layout_toEndOf="@id/triangle_tick"
android:layout_below="@id/image_view"
android:orientation="vertical">
<TextView android:id="@+id/conversation_item_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="5dip"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?conversation_received_text_primary_color"
android:textSize="16sp"
android:autoLink="all"
android:linksClickable="true" />
<LinearLayout android:id="@+id/mms_download_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button android:id="@+id/mms_download_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/conversation_item_received__download"
android:visibility="gone" />
<TextView android:id="@+id/mms_label_downloading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:gravity="center"
android:text="@string/conversation_item_received__downloading"
android:visibility="gone" />
</LinearLayout>
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="2dip"
android:paddingLeft="8dp"
android:paddingRight="5dp"
android:paddingBottom="5dp"
android:orientation="horizontal"
android:gravity="left">
<ImageView android:id="@+id/delivered_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="3dip"
android:src="?conversation_delivery_delivered"
android:contentDescription="@string/conversation_item_sent__delivered_description"
android:visibility="gone" />
<ImageView android:id="@+id/sms_secure_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingRight="4dp"
android:paddingTop="2dp"
android:src="?menu_lock_icon_small_received"
android:contentDescription="@string/conversation_item__secure_message_description"
android:visibility="gone" />
<TextView android:id="@+id/conversation_item_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:paddingTop="1dip"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?conversation_received_text_secondary_color"
android:textSize="@dimen/conversation_item_date_text_size"
android:fontFamily="sans-serif-light"
android:autoLink="none"
android:linksClickable="false" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>

View File

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<View android:id="@+id/triangle_tick"
android:layout_width="12dp"
android:layout_height="15dp"
android:layout_alignParentRight="true"
android:layout_marginTop="12dp" />
<org.thoughtcrime.securesms.components.ForegroundImageView
android:id="@+id/image_view"
android:layout_width="wrap_content"
android:layout_height="@dimen/media_bubble_height"
android:layout_marginLeft="@dimen/message_bubble_end_padding"
android:layout_toLeftOf="@id/triangle_tick"
android:layout_gravity="center"
android:scaleType="centerCrop"
android:adjustViewBounds="true"
android:contentDescription="@string/conversation_item__mms_image_description"
android:visibility="gone"
app:riv_corner_radius="@dimen/message_bubble_corner_radius"
app:riv_border_width="@dimen/media_bubble_border_width"
tools:src="@drawable/ic_video_light"
tools:visibility="visible" />
<LinearLayout android:id="@+id/body_bubble"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/image_view"
android:layout_alignParentRight="true"
android:layout_alignBottom="@id/image_view"
android:paddingRight="10dip"
android:paddingLeft="10dip"
android:layout_marginLeft="50dp"
android:layout_marginRight="12dp"
android:orientation="vertical">
<TextView android:id="@+id/conversation_item_body"
android:autoLink="all"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="5dip"
android:linksClickable="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?conversation_sent_text_primary_color"
android:textColorLink="?conversation_sent_text_primary_color"
android:textSize="16sp"
tools:text="Mango pickle lorem ipsum" />
<LinearLayout android:id="@+id/mms_download_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button android:id="@+id/mms_download_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/conversation_item_sent__download"
android:visibility="gone" />
<TextView android:id="@+id/mms_label_downloading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:gravity="center"
android:text="@string/conversation_item_sent__downloading"
android:visibility="gone" />
</LinearLayout>
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="0dip"
android:layout_gravity="right">
<TextView android:id="@+id/group_message_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:linksClickable="false"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_gravity="right"
android:textColor="?conversation_sent_text_secondary_color"
android:visibility="gone"
android:layout_marginRight="8dip"
android:paddingTop="1dip" />
<TextView android:id="@+id/conversation_item_date"
android:autoLink="none"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="15sp"
android:linksClickable="false"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_gravity="right"
android:fontFamily="sans-serif-light"
android:textColor="?conversation_sent_text_secondary_color"
android:textSize="@dimen/conversation_item_date_text_size"
android:paddingTop="1dip"
android:paddingBottom="5dp"
tools:text="30 mins" />
<ImageView android:id="@+id/delivered_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:src="?conversation_delivery_delivered"
android:paddingLeft="2dp"
android:paddingBottom="2dp"
android:visibility="gone"
android:contentDescription="@string/conversation_item_sent__delivered_description" />
<ImageView android:id="@+id/sms_secure_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="?menu_lock_icon_small"
android:visibility="gone"
android:layout_gravity="center_vertical|end"
android:paddingLeft="2dp"
android:paddingBottom="3dp"
android:contentDescription="@string/conversation_item__secure_message_description" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>

View File

@ -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">
<TextView android:id="@+id/group_message_status"
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_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" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginLeft="9dp"
android:layout_marginBottom="6dp"
android:layout_marginRight="0dp"
android:orientation="horizontal">
<RelativeLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginLeft="9dp"
android:layout_marginBottom="6dp"
android:layout_marginRight="0dp">
<com.makeramen.RoundedImageView
<org.thoughtcrime.securesms.components.ForegroundImageView
android:id="@+id/contact_photo"
android:foreground="@drawable/contact_photo_background"
android:layout_width="40dp"
@ -38,118 +37,12 @@
android:cropToPadding="true"
android:contentDescription="@string/conversation_item_received__contact_photo_description"
android:scaleType="centerCrop" />
<View android:id="@+id/triangle_tick"
android:layout_width="12dp"
android:layout_height="15dp"
android:layout_marginTop="12dp"
android:layout_alignRight="@id/contact_photo"
android:background="?conversation_item_received_triangle_background" />
<LinearLayout android:id="@+id/conversation_item_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/triangle_tick"
android:background="?conversation_item_received_background"
android:orientation="vertical">
<TextView android:id="@+id/conversation_item_body"
<org.thoughtcrime.securesms.components.IncomingBubbleContainer
android:id="@+id/bubble"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="5dip"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?conversation_received_text_primary_color"
android:textSize="16sp"
android:autoLink="all"
android:linksClickable="true" />
<FrameLayout android:id="@+id/mms_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone">
<ImageView android:id="@+id/image_view"
android:layout_width="230dip"
android:layout_height="174dip"
android:layout_gravity="center"
android:scaleType="centerInside"
android:adjustViewBounds="true"
android:contentDescription="@string/conversation_item__mms_image_description"
android:visibility="gone" />
<ImageButton android:id="@+id/play_slideshow_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/mms_play_btn"
android:contentDescription="@string/conversation_item__play_button_description"
android:visibility="gone" />
</FrameLayout>
<LinearLayout android:id="@+id/mms_download_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button android:id="@+id/mms_download_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/conversation_item_received__download"
android:visibility="gone" />
<TextView android:id="@+id/mms_label_downloading"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:gravity="center"
android:text="@string/conversation_item_received__downloading"
android:visibility="gone" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="2dip"
android:paddingLeft="8dp"
android:paddingRight="5dp"
android:paddingBottom="5dp"
android:orientation="horizontal"
android:gravity="left">
<ImageView android:id="@+id/delivered_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="3dip"
android:src="?conversation_delivery_delivered"
android:contentDescription="@string/conversation_item_sent__delivered_description"
android:visibility="gone" />
<ImageView android:id="@+id/sms_secure_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingRight="4dp"
android:paddingTop="2dp"
android:src="?menu_lock_icon_small_received"
android:contentDescription="@string/conversation_item__secure_message_description"
android:visibility="gone" />
<TextView android:id="@+id/conversation_item_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:paddingTop="1dip"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?conversation_received_text_secondary_color"
android:textSize="@dimen/conversation_item_date_text_size"
android:fontFamily="sans-serif-light"
android:autoLink="none"
android:linksClickable="false" />
</LinearLayout>
</LinearLayout>
android:layout_marginLeft="29dp" />
<LinearLayout android:id="@+id/indicators_parent"
android:layout_width="wrap_content"

View File

@ -2,6 +2,7 @@
<org.thoughtcrime.securesms.ConversationItem
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:id="@+id/conversation_item"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
@ -10,7 +11,6 @@
<RelativeLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="6dp"
android:layout_marginLeft="6dp"
android:layout_marginBottom="6dp"
@ -46,145 +46,15 @@
</LinearLayout>
<LinearLayout android:id="@+id/conversation_item_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:background="?conversation_item_sent_push_background"
android:paddingRight="10dip"
android:paddingLeft="10dip"
android:layout_marginLeft="50dp"
android:layout_marginRight="12dp"
android:orientation="vertical">
<TextView android:id="@+id/conversation_item_body"
android:autoLink="all"
<org.thoughtcrime.securesms.components.OutgoingBubbleContainer
android:id="@+id/bubble"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="5dip"
android:linksClickable="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?conversation_sent_text_primary_color"
android:textColorLink="?conversation_sent_text_primary_color"
android:textSize="16sp"
tools:text="Lorem ipsum mango dolor coconut papaya" />
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mms_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone">
<ImageView
android:id="@+id/image_view"
android:layout_width="230dip"
android:layout_height="174dip"
android:layout_gravity="center"
android:scaleType="centerInside"
android:adjustViewBounds="true"
android:visibility="gone"
android:contentDescription="@string/conversation_item__mms_image_description"/>
<ImageButton
android:id="@+id/play_slideshow_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/mms_play_btn"
android:layout_gravity="center"
android:visibility="gone"
android:contentDescription="@string/conversation_item__play_button_description"/>
</FrameLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mms_download_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button android:id="@+id/mms_download_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/conversation_item_sent__download"
android:visibility="gone" />
<TextView android:id="@+id/mms_label_downloading"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:gravity="center"
android:text="@string/conversation_item_sent__downloading"
android:visibility="gone" />
</LinearLayout>
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="0dip"
android:layout_gravity="right">
<TextView android:id="@+id/group_message_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:linksClickable="false"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_gravity="right"
android:textColor="?conversation_sent_text_secondary_color"
android:visibility="gone"
android:layout_marginRight="8dip"
android:paddingTop="1dip"/>
<TextView android:id="@+id/conversation_item_date"
android:autoLink="none"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="15sp"
android:linksClickable="false"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_gravity="right"
android:fontFamily="sans-serif-light"
android:textColor="?conversation_sent_text_secondary_color"
android:textSize="@dimen/conversation_item_date_text_size"
android:paddingTop="1dip"
android:paddingBottom="5dp" />
<ImageView android:id="@+id/delivered_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:src="?conversation_delivery_delivered"
android:paddingLeft="2dp"
android:paddingBottom="2dp"
android:visibility="gone"
android:contentDescription="@string/conversation_item_sent__delivered_description" />
<ImageView android:id="@+id/sms_secure_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="?menu_lock_icon_small"
android:visibility="gone"
android:layout_gravity="center_vertical|end"
android:paddingLeft="2dp"
android:paddingBottom="3dp"
android:contentDescription="@string/conversation_item__secure_message_description" />
</LinearLayout>
</LinearLayout>
<View android:id="@+id/triangle_tick"
android:layout_width="12dp"
android:layout_height="15dp"
android:layout_alignParentRight="true"
android:layout_marginTop="12dp"
android:background="?conversation_item_sent_triangle_background" />
android:layout_height="wrap_content" />
<TextView android:id="@+id/indicator_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/conversation_item_parent"
android:layout_below="@id/bubble"
android:layout_alignParentRight="true"
android:paddingRight="5dip"
android:paddingLeft="5dip"

View File

@ -6,10 +6,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:contentDescription="@string/media_preview_activity__image_content_description" />
<org.thoughtcrime.securesms.components.ForegroundImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:background="#11ffffff"
android:contentDescription="@string/media_preview_activity__image_content_description" />
</org.thoughtcrime.securesms.components.SquareLinearLayout>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/gray95">
@ -12,16 +11,6 @@
android:contentDescription="@string/media_preview_activity__image_content_description"
android:visibility="gone"/>
<TextView android:id="@+id/loading_indicator"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerInParent="true"
android:text="···"
android:textSize="30sp"
android:textColor="@color/gray10"
android:visibility="gone"
tools:ignore="HardcodedText" />
<TextView android:id="@+id/error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@ -39,18 +39,22 @@
<attr name="conversation_item_background" format="reference"/>
<attr name="conversation_item_received_background" format="reference" />
<attr name="conversation_item_received_triangle_background" format="reference" />
<attr name="conversation_item_sent_background" format="reference" />
<attr name="conversation_item_sent_triangle_background" format="reference" />
<attr name="conversation_item_sent_pending_background" format="reference" />
<attr name="conversation_item_sent_pending_triangle_background" format="reference" />
<attr name="conversation_item_sent_push_background" format="reference" />
<attr name="conversation_item_sent_push_triangle_background" format="reference" />
<attr name="conversation_item_sent_background" format="color" />
<attr name="conversation_item_sent_pending_background" format="color" />
<attr name="conversation_item_sent_push_background" format="color" />
<attr name="conversation_item_sent_indicator_text_background" format="reference" />
<attr name="conversation_item_sent_push_pending_background" format="reference" />
<attr name="conversation_item_shadow" format="color" />
<attr name="conversation_item_mms_pending_mask" format="color" />
<attr name="triangle_tick_incoming" format="reference" />
<attr name="triangle_tick_outgoing_sent_sms" format="reference" />
<attr name="triangle_tick_outgoing_sent_push" format="reference" />
<attr name="triangle_tick_outgoing_pending_sms" format="reference" />
<attr name="triangle_tick_outgoing_pending_push" format="reference" />
<attr name="dialog_info_icon" format="reference" />
<attr name="dialog_alert_icon" format="reference" />
<attr name="conversation_item_sent_push_pending_background" format="reference" />
<attr name="conversation_item_sent_push_pending_triangle_background" format="reference" />
<attr name="conversation_icon_attach_audio" format="reference"/>
<attr name="conversation_icon_attach_video" format="reference" />

View File

@ -35,9 +35,9 @@
<color name="conversation_item_sent_pending_background_light">#5564A926</color>
<color name="conversation_item_sent_pending_background_dark">#55284e0a</color>
<color name="conversation_item_sent_push_background_light">#ff2090ea</color>
<color name="conversation_item_sent_push_background_dark">#ff213b77</color>
<color name="conversation_item_sent_push_pending_background_light">#ff528ff2</color>
<color name="conversation_item_sent_push_pending_background_dark">#55213b77</color>
<color name="conversation_item_sent_push_background_dark">#ff183b7a</color>
<color name="conversation_item_sent_push_pending_background_light">#ff5cace6</color>
<color name="conversation_item_sent_push_pending_background_dark">#ff122d5e</color>
<color name="conversation_item_received_background_light">#fff3f3f3</color>
<color name="conversation_item_received_background_dark">#ff333333</color>
<color name="conversation_item_received_shadow_light">#ffefefef</color>

View File

@ -9,13 +9,18 @@
<dimen name="transport_selection_popup_width">200sp</dimen>
<dimen name="transport_selection_popup_xoff">0dp</dimen>
<dimen name="transport_selection_popup_yoff">1dp</dimen>
<dimen name="conversation_item_corner_radius">4dp</dimen>
<dimen name="conversation_item_drop_shadow_dist">1.5dp</dimen>
<dimen name="contact_selection_photo_size">50dp</dimen>
<dimen name="thumbnail_max_size">230dp</dimen>
<dimen name="preference_fragment_padding_side">8dp</dimen>
<dimen name="conversation_activity_compose_padding">12dp</dimen>
<dimen name="message_bubble_end_padding">50dp</dimen>
<dimen name="message_bubble_corner_radius">4dp</dimen>
<dimen name="message_bubble_shadow_distance">1.5dp</dimen>
<dimen name="media_bubble_height">210dp</dimen>
<dimen name="media_bubble_border_width">3dp</dimen>
<integer name="media_overview_cols">3</integer>
<dimen name="message_details_table_row_pad">10dp</dimen>
</resources>

View File

@ -479,6 +479,7 @@
<string name="conversation_activity__attachment_thumbnail">Attachment Thumbnail</string>
<!-- conversation_item -->
<string name="conversation_item__mms_downloading_description">Media message downloading</string>
<string name="conversation_item__mms_image_description">Media message</string>
<string name="conversation_item__play_button_description">Play button</string>
<string name="conversation_item__secure_message_description">Secure message</string>

View File

@ -55,20 +55,23 @@
<item name="conversation_keyboard_toggle">@drawable/ic_ime_dark</item>
<item name="conversation_item_background">@drawable/conversation_item_background</item>
<item name="conversation_item_received_background">@drawable/conversation_item_received_shape</item>
<item name="conversation_item_received_triangle_background">@drawable/conversation_item_received_triangle_shape</item>
<item name="conversation_item_sent_background">@drawable/conversation_item_sent_shape</item>
<item name="conversation_item_sent_triangle_background">@drawable/conversation_item_sent_triangle_shape</item>
<item name="conversation_item_sent_push_background">@drawable/conversation_item_sent_push_shape</item>
<item name="conversation_item_sent_push_triangle_background">@drawable/conversation_item_sent_push_triangle_shape</item>
<item name="conversation_item_received_background">@color/conversation_item_received_background_light</item>
<item name="conversation_item_sent_background">@color/conversation_item_sent_background_light</item>
<item name="conversation_item_sent_push_background">@color/conversation_item_sent_push_background_light</item>
<item name="conversation_item_sent_pending_background">@color/conversation_item_sent_pending_background_light</item>
<item name="conversation_item_sent_push_pending_background">@color/conversation_item_sent_push_pending_background_light</item>
<item name="conversation_item_sent_indicator_text_background">@drawable/conversation_item_sent_indicator_text_shape</item>
<item name="conversation_item_shadow">@color/conversation_item_received_shadow_light</item>
<item name="conversation_item_mms_pending_mask">#99ffffff</item>
<item name="triangle_tick_incoming">@drawable/triangle_tick_incoming_light</item>
<item name="triangle_tick_outgoing_sent_sms">@drawable/triangle_tick_outgoing_sent_sms_light</item>
<item name="triangle_tick_outgoing_sent_push">@drawable/triangle_tick_outgoing_sent_push_light</item>
<item name="triangle_tick_outgoing_pending_sms">@drawable/triangle_tick_outgoing_pending_sms_light</item>
<item name="triangle_tick_outgoing_pending_push">@drawable/triangle_tick_outgoing_pending_push_light</item>
<item name="dialog_info_icon">@drawable/ic_dialog_info_light</item>
<item name="dialog_alert_icon">@drawable/ic_dialog_alert_light</item>
<item name="conversation_item_sent_pending_background">@drawable/conversation_item_sent_pending_shape</item>
<item name="conversation_item_sent_pending_triangle_background">@drawable/conversation_item_sent_pending_triangle_shape</item>
<item name="conversation_item_sent_push_pending_background">@drawable/conversation_item_sent_push_pending_shape</item>
<item name="conversation_item_sent_push_pending_triangle_background">@drawable/conversation_item_sent_push_pending_triangle_shape</item>
<item name="import_export_item_background_color">@color/import_export_item_background_light</item>
<item name="import_export_item_background_shadow_color">@color/import_export_item_background_shadow_light</item>
@ -142,19 +145,22 @@
<item name="conversation_selection_header_text">#66eeeeee</item>
<item name="conversation_item_background">@drawable/conversation_item_background_dark</item>
<item name="conversation_item_received_background">@drawable/conversation_item_received_shape_dark</item>
<item name="conversation_item_received_triangle_background">@drawable/conversation_item_received_triangle_shape_dark</item>
<item name="conversation_item_sent_background">@drawable/conversation_item_sent_shape_dark</item>
<item name="conversation_item_sent_triangle_background">@drawable/conversation_item_sent_triangle_shape_dark</item>
<item name="conversation_item_sent_push_background">@drawable/conversation_item_sent_push_shape_dark</item>
<item name="conversation_item_sent_push_triangle_background">@drawable/conversation_item_sent_push_triangle_shape_dark</item>
<item name="conversation_item_sent_indicator_text_background">@drawable/conversation_item_sent_indicator_text_shape_dark</item>
<item name="conversation_item_received_background">@color/conversation_item_received_background_dark</item>
<item name="conversation_item_sent_background">@color/conversation_item_sent_background_dark</item>
<item name="conversation_item_sent_push_background">@color/conversation_item_sent_push_background_dark</item>
<item name="conversation_item_sent_pending_background">@color/conversation_item_sent_pending_background_dark</item>
<item name="conversation_item_sent_push_pending_background">@color/conversation_item_sent_push_pending_background_dark</item>
<item name="conversation_item_shadow">@color/conversation_item_received_shadow_dark</item>
<item name="conversation_item_mms_pending_mask">#99000000</item>
<item name="triangle_tick_incoming">@drawable/triangle_tick_incoming_dark</item>
<item name="triangle_tick_outgoing_sent_sms">@drawable/triangle_tick_outgoing_sent_sms_dark</item>
<item name="triangle_tick_outgoing_sent_push">@drawable/triangle_tick_outgoing_sent_push_dark</item>
<item name="triangle_tick_outgoing_pending_sms">@drawable/triangle_tick_outgoing_pending_sms_dark</item>
<item name="triangle_tick_outgoing_pending_push">@drawable/triangle_tick_outgoing_pending_push_dark</item>
<item name="dialog_info_icon">@drawable/ic_dialog_info_dark</item>
<item name="dialog_alert_icon">@drawable/ic_dialog_alert_dark</item>
<item name="conversation_item_sent_pending_background">@drawable/conversation_item_sent_pending_shape_dark</item>
<item name="conversation_item_sent_pending_triangle_background">@drawable/conversation_item_sent_pending_triangle_shape_dark</item>
<item name="conversation_item_sent_push_pending_background">@drawable/conversation_item_sent_push_pending_shape_dark</item>
<item name="conversation_item_sent_push_pending_triangle_background">@drawable/conversation_item_sent_push_pending_triangle_shape_dark</item>
<item name="import_export_item_background_color">@color/import_export_item_background_dark</item>
<item name="import_export_item_background_shadow_color">@color/import_export_item_background_shadow_dark</item>

View File

@ -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);

View File

@ -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() {

View File

@ -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;
}

View File

@ -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<MessageRecord> 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() {

View File

@ -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<MessageRecord> batchSelected;
private SelectionClickListener selectionClickListener;
private View mmsContainer;
private ImageView mmsThumbnail;
private Button mmsDownloadButton;
private TextView mmsDownloadingLabel;
private ListenableFutureTask<SlideDeck> slideDeck;
private FutureTaskListener<SlideDeck> slideDeckListener;
private TypedArray backgroundDrawables;
private Set<MessageRecord> batchSelected;
private SelectionClickListener selectionClickListener;
private ForegroundImageView mediaThumbnail;
private Button mmsDownloadButton;
private TextView mmsDownloadingLabel;
private ListenableFutureTask<SlideDeck> slideDeckFuture;
private ListenableFutureTask<Pair<Drawable, Boolean>> 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<SlideDeck>() {
@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<Pair<Drawable, Boolean>> {
private final Object tag;
public ThumbnailListener(Object tag) {
this.tag = tag;
}
@Override
public void onSuccess(final Pair<Drawable, Boolean> 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<SlideDeck> {
@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();
}
}
}

View File

@ -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);

View File

@ -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() {

View File

@ -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<ViewHolder> {
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<ViewHolder> {
@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<ViewHolder> {
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<Pair<Drawable, Boolean>>() {
@Override
public void onSuccess(final Pair<Drawable, Boolean> 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));
}

View File

@ -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() {

View File

@ -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<Void,Void,Bitmap>() {
@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);

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -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<Void,Void,Drawable>() {
slide.getThumbnail(context).addListener(new FutureTaskListener<Pair<Drawable, Boolean>>() {
@Override
protected Drawable doInBackground(Void... params) {
return slide.getThumbnail(context, thumbnailWidth, thumbnailHeight);
public void onSuccess(final Pair<Drawable, Boolean> 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) {

View File

@ -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<Pair<Drawable,Boolean>> 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 {

View File

@ -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<Pair<Drawable,Boolean>> 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<Pair<Drawable,Boolean>> resolveThumbnail(Context context) {
final WeakReference<Context> 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<Context> weakContext = new WeakReference<>(context);
final WeakReference<ImageView> weakImageView = new WeakReference<>(imageView);
final Handler handler = new Handler();
imageView.setImageDrawable(placeholder);
if (width == 0 || height == 0)
return;
MmsDatabase.slideResolver.execute(new Runnable() {
Callable<Pair<Drawable,Boolean>> slideCallable = new Callable<Pair<Drawable, Boolean>>() {
@Override
public void run() {
public Pair<Drawable, Boolean> 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<Pair<Drawable,Boolean>> futureTask = new ListenableFutureTask<>(slideCallable);
MmsDatabase.slideResolver.execute(futureTask);
return futureTask;
}
private Drawable getCachedThumbnail() {

View File

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

View File

@ -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<Pair<Drawable,Boolean>> 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;
}

View File

@ -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;
}
}

View File

@ -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<Pair<Drawable,Boolean>> getThumbnail(Context context) {
return new ListenableFutureTask<>(new Pair<>(ResUtil.getDrawable(context, R.attr.conversation_icon_attach_video), true));
}
@Override

View File

@ -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) {

View File

@ -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());

View File

@ -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();
}
}

View File

@ -30,6 +30,16 @@ public class ListenableFutureTask<V> extends FutureTask<V> {
super(callable);
}
public ListenableFutureTask(final V result) {
super(new Callable<V>() {
@Override
public V call() throws Exception {
return result;
}
});
this.run();
}
public synchronized void addListener(FutureTaskListener<V> listener) {
if (this.isDone()) {
callback(listener);

View File

@ -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
{

View File

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

View File

@ -152,7 +152,7 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
public static void showWarningDialog(Context context, OnClickListener onAcceptListener) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.ConversationFragment_save_to_sd_card);
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.ConversationFragment_this_media_has_been_stored_in_an_encrypted_database_warning);
builder.setPositiveButton(R.string.yes, onAcceptListener);

View File

@ -0,0 +1,41 @@
/**
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}