Update conversation item style.

1) Don't print sender/recipient name in message body.
2) Do the split conversation icon view.
3) Adjust font sizes and colors.
This commit is contained in:
Moxie Marlinspike 2012-07-19 19:23:49 -07:00
parent eeeeac126c
commit a7cc47d259
15 changed files with 705 additions and 477 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,19 +1,20 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="fill_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<ListView android:id="@android:id/list" <ListView android:id="@android:id/list"
android:layout_width="match_parent" style="?android:attr/listViewWhiteStyle"
android:layout_height="match_parent" android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1.0" android:layout_weight="1.0"
android:listSelector="@drawable/chat_history_selector" android:listSelector="@drawable/chat_history_selector"
android:drawSelectorOnTop="true" android:drawSelectorOnTop="true"
android:transcriptMode="alwaysScroll" android:transcriptMode="alwaysScroll"
android:scrollbarAlwaysDrawVerticalTrack="true" android:scrollbarAlwaysDrawVerticalTrack="false"
android:scrollbarStyle="insideInset" android:scrollbarStyle="insideOverlay"
android:stackFromBottom="true" android:stackFromBottom="true"
android:fadingEdge="none" android:fadingEdge="none"
android:layout_marginBottom="1dip"/> android:layout_marginBottom="1dip"/>

View File

@ -1,159 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--<org.thoughtcrime.securesms.ConversationItem-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/conversation_item"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="10px"
android:paddingTop="5px"
android:paddingRight="10px"
android:paddingBottom="10px"
android:orientation="horizontal">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<!-- <android.widget.QuickContactBadge android:id="@+id/contact_photo"-->
<!-- android:layout_width="60dp"-->
<!-- android:layout_height="60dp"-->
<!-- android:cropToPadding="true"-->
<!-- android:layout_marginRight="10px"-->
<!-- android:scaleType="centerCrop"-->
<!-- android:visibility="gone" />-->
<ImageView android:id="@+id/contact_photo"
android:layout_width="60dp"
android:layout_height="60dp"
android:cropToPadding="true"
android:layout_marginRight="10px"
android:scaleType="centerCrop"
android:visibility="gone" />
<LinearLayout android:id="@+id/conversation_item_parent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical" >
<TextView android:id="@+id/conversation_item_body"
android:autoLink="all"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:linksClickable="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="#ff000000"
android:textSize="18sp" />
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mms_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingTop="7dip"
android:paddingBottom="7dip">
<ImageView
android:id="@+id/image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:maxWidth="178dip"
android:maxHeight="178dip"
android:adjustViewBounds="true"
android:background="@android:drawable/picture_frame"
android:visibility="gone" />
<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" />
</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="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="Downloading"
android:visibility="gone" />
</LinearLayout>
<TextView android:id="@+id/conversation_item_date"
android:autoLink="all"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:linksClickable="false"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="#bf000000"
android:paddingTop="3px"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout android:id="@+id/indicators_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="vertical">
<ImageView
android:id="@+id/key_exchange_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/key"
android:visibility="gone" />
<ImageView
android:id="@+id/sms_pending_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_sms_mms_pending"
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:src="@drawable/ic_lock_small"
android:visibility="gone" />
<ImageView
android:id="@+id/sms_failed_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_sms_mms_not_delivered"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
<!--</org.thoughtcrime.securesms.ConversationItem>-->
</LinearLayout>

View File

@ -0,0 +1,147 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.ConversationItem
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/conversation_item"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingRight="10dip"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<view xmlns:android="http://schemas.android.com/apk/res/android"
class="org.thoughtcrime.securesms.components.ImageDivet"
position="right"
android:id="@id/contact_photo"
android:layout_alignParentLeft="true"
android:layout_width="60dp"
android:layout_height="60dp"
android:cropToPadding="true"
android:layout_marginRight="10dip"
android:scaleType="centerCrop" />
<LinearLayout android:id="@+id/conversation_item_parent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_toLeftOf="@+id/indicators_parent"
android:layout_toRightOf="@id/contact_photo"
android:orientation="vertical" >
<TextView android:id="@+id/conversation_item_body"
android:autoLink="all"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:linksClickable="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="#ff000000"
android:textSize="16sp" />
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mms_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingTop="7dip"
android:paddingBottom="7dip">
<ImageView
android:id="@+id/image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:maxWidth="178dip"
android:maxHeight="178dip"
android:adjustViewBounds="true"
android:background="@android:drawable/picture_frame"
android:visibility="gone" />
<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" />
</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="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="Downloading"
android:visibility="gone" />
</LinearLayout>
<TextView android:id="@+id/conversation_item_date"
android:autoLink="all"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:linksClickable="false"
android:textAppearance="?android:attr/textAppearanceSmall"
android:gravity="left"
android:textColor="#ffcccccc"
android:paddingTop="1dip"/>
</LinearLayout>
<LinearLayout android:id="@+id/indicators_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="vertical"
android:layout_alignParentRight="true">
<ImageView
android:id="@+id/key_exchange_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/key"
android:visibility="gone" />
<ImageView
android:id="@+id/sms_pending_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_sms_mms_pending"
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:src="@drawable/ic_lock_small"
android:visibility="gone" />
<ImageView
android:id="@+id/sms_failed_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_sms_mms_not_delivered"
android:visibility="gone" />
</LinearLayout>
</RelativeLayout>
</org.thoughtcrime.securesms.ConversationItem>

View File

@ -0,0 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.ConversationItem
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/conversation_item"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dip"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout android:id="@+id/indicators_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="vertical"
android:layout_alignParentLeft="true">
<ImageView
android:id="@+id/key_exchange_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/key"
android:visibility="gone" />
<ImageView
android:id="@+id/sms_pending_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_sms_mms_pending"
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:src="@drawable/ic_lock_small"
android:visibility="gone" />
<ImageView
android:id="@+id/sms_failed_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_sms_mms_not_delivered"
android:visibility="gone" />
</LinearLayout>
<LinearLayout android:id="@+id/conversation_item_parent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/indicators_parent"
android:layout_toLeftOf="@+id/contact_photo"
android:orientation="vertical" >
<TextView android:id="@+id/conversation_item_body"
android:autoLink="all"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:linksClickable="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:gravity="right"
android:textColor="#ff000000"
android:textSize="16sp" />
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mms_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingTop="7dip"
android:paddingBottom="7dip">
<ImageView
android:id="@+id/image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:maxWidth="178dip"
android:maxHeight="178dip"
android:adjustViewBounds="true"
android:background="@android:drawable/picture_frame"
android:visibility="gone" />
<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" />
</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="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="Downloading"
android:visibility="gone" />
</LinearLayout>
<TextView android:id="@+id/conversation_item_date"
android:autoLink="all"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:linksClickable="false"
android:textAppearance="?android:attr/textAppearanceSmall"
android:gravity="right"
android:textColor="#ffcccccc"
android:paddingTop="1dip"/>
</LinearLayout>
<view xmlns:android="http://schemas.android.com/apk/res/android"
class="org.thoughtcrime.securesms.components.ImageDivet"
position="left"
android:id="@id/contact_photo"
android:layout_alignParentRight="true"
android:layout_width="60dp"
android:layout_height="60dp"
android:cropToPadding="true"
android:layout_marginLeft="10dip"
android:layout_marginRight="0dip"
android:padding="0dip"
android:scaleType="centerCrop" />
</RelativeLayout>
</org.thoughtcrime.securesms.ConversationItem>

View File

@ -28,7 +28,7 @@
android:key="pref_all_mms" android:key="pref_all_mms"
android:summary="Use TextSecure for viewing and storing all incoming multimedia messages" android:summary="Use TextSecure for viewing and storing all incoming multimedia messages"
android:title="Use for all MMS" /> android:title="Use for all MMS" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="Input Settings"> <PreferenceCategory android:title="Input Settings">
@ -39,103 +39,84 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="Display Settings"> <PreferenceCategory android:title="Display Settings">
<Preference android:key="pref_choose_identity" <Preference android:key="pref_choose_identity"
android:title="Choose Identity" android:title="Choose Identity"
android:summary="Choose your contact entry from the contacts list."/> android:summary="Choose your contact entry from the contacts list."/>
<CheckBoxPreference android:defaultValue="true"
android:key="pref_dark_threads"
android:summary="Display dark background in thread list"
android:title="Dark Thread List Theme" />
<CheckBoxPreference android:defaultValue="false"
android:key="pref_dark_conversation"
android:summary="Display dark background in conversation view"
android:title="Dark Conversation Theme" />
<CheckBoxPreference android:defaultValue="true"
android:key="pref_conversation_list_icons"
android:summary="Show contact photo icons in list of conversation threads"
android:title="List Photos" />
<CheckBoxPreference android:defaultValue="true"
android:key="pref_conversation_icons"
android:summary="Show contact photo icons in conversation"
android:title="Conversation Photos" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="Encryption Settings"> <PreferenceCategory android:title="Encryption Settings">
<Preference android:key="pref_change_passphrase" <Preference android:key="pref_change_passphrase"
android:title="Change Passphrase" android:title="Change Passphrase"
android:summary="Change my passphrase"/> android:summary="Change my passphrase"/>
<CheckBoxPreference android:defaultValue="true" <CheckBoxPreference android:defaultValue="true"
android:key="pref_auto_complete_key_exchange" android:key="pref_auto_complete_key_exchange"
android:title="Complete Key Exchanges" android:title="Complete Key Exchanges"
android:summary="Automatically complete key exchanges for new sessions or for existing sessions with the same identity key" /> android:summary="Automatically complete key exchanges for new sessions or for existing sessions with the same identity key" />
<CheckBoxPreference android:defaultValue="true" <CheckBoxPreference android:defaultValue="true"
android:key="pref_key_tag_whitespace" android:key="pref_key_tag_whitespace"
android:summary="Include a whitespace tag at the end of every non-encrypted message" android:summary="Include a whitespace tag at the end of every non-encrypted message"
android:title="Include whitespace tag" /> android:title="Include whitespace tag" />
<CheckBoxPreference android:defaultValue="true" <CheckBoxPreference android:defaultValue="true"
android:key="pref_send_identity_key" android:key="pref_send_identity_key"
android:summary="Sign key exchange messages with identity key" android:summary="Sign key exchange messages with identity key"
android:title="Sign Key Exchange" /> android:title="Sign Key Exchange" />
<CheckBoxPreference android:defaultValue="false" <CheckBoxPreference android:defaultValue="false"
android:key="pref_timeout_passphrase" android:key="pref_timeout_passphrase"
android:summary="Forget passphrase from memory after some interval" android:summary="Forget passphrase from memory after some interval"
android:title="Timeout passphrase" /> android:title="Timeout passphrase" />
<org.thoughtcrime.securesms.preferences.PassphraseTimeoutPreference <org.thoughtcrime.securesms.preferences.PassphraseTimeoutPreference
android:key="pref_timeout_interval" android:key="pref_timeout_interval"
android:defaultValue="300" android:defaultValue="300"
android:title="Timeout interval" android:title="Timeout interval"
android:summary="The amount of time to wait before forgetting passphrase from memory" android:summary="The amount of time to wait before forgetting passphrase from memory"
android:dependency="pref_timeout_passphrase" android:dependency="pref_timeout_passphrase"
android:dialogTitle="Select Passphrase Timeout" /> android:dialogTitle="Select Passphrase Timeout" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="Identity Key Settings"> <PreferenceCategory android:title="Identity Key Settings">
<Preference android:key="pref_view_identity" <Preference android:key="pref_view_identity"
android:title="View My Identity Key" android:title="View My Identity Key"
android:summary="View my identity key"/> android:summary="View my identity key"/>
<Preference android:key="pref_export_identity" <Preference android:key="pref_export_identity"
android:title="Export My Identity Key" android:title="Export My Identity Key"
android:summary="Export my identity key"/> android:summary="Export my identity key"/>
<Preference android:key="pref_import_identity" <Preference android:key="pref_import_identity"
android:title="Import Contact's Key" android:title="Import Contact's Key"
android:summary="Import an identity key from a contact"/> android:summary="Import an identity key from a contact"/>
<Preference android:key="pref_manage_identity" <Preference android:key="pref_manage_identity"
android:title="Manage Identity Keys" android:title="Manage Identity Keys"
android:summary="Manage configured identity keys"/> android:summary="Manage configured identity keys"/>
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="Notification Settings"> <PreferenceCategory android:title="Notification Settings">
<CheckBoxPreference android:key="pref_key_enable_notifications" <CheckBoxPreference android:key="pref_key_enable_notifications"
android:title="Notifications" android:title="Notifications"
android:summary="Display message notifications in status bar" android:summary="Display message notifications in status bar"
android:defaultValue="true" /> android:defaultValue="true" />
<ListPreference <ListPreference
android:key="pref_led_color" android:key="pref_led_color"
android:defaultValue="green" android:defaultValue="green"
android:title="LED Color" android:title="LED Color"
android:dependency="pref_key_enable_notifications" android:dependency="pref_key_enable_notifications"
android:summary="Change notification LED color" android:summary="Change notification LED color"
android:entries="@array/pref_led_color_entries" android:entries="@array/pref_led_color_entries"
android:entryValues="@array/pref_led_color_values" android:entryValues="@array/pref_led_color_values"
android:dialogTitle="Select LED Color" /> android:dialogTitle="Select LED Color" />
<org.thoughtcrime.securesms.preferences.LedBlinkPatternListPreference <org.thoughtcrime.securesms.preferences.LedBlinkPatternListPreference
android:key="pref_led_blink" android:key="pref_led_blink"
android:defaultValue="500,2000" android:defaultValue="500,2000"
android:title="LED Blink Pattern" android:title="LED Blink Pattern"
@ -143,9 +124,9 @@
android:summary="Change notification LED blink pattern" android:summary="Change notification LED blink pattern"
android:entries="@array/pref_led_blink_pattern_entries" android:entries="@array/pref_led_blink_pattern_entries"
android:entryValues="@array/pref_led_blink_pattern_values" android:entryValues="@array/pref_led_blink_pattern_values"
android:dialogTitle="LED Blink Pattern" /> android:dialogTitle="LED Blink Pattern" />
<RingtonePreference android:layout="?android:attr/preferenceLayoutChild" <RingtonePreference android:layout="?android:attr/preferenceLayoutChild"
android:dependency="pref_key_enable_notifications" android:dependency="pref_key_enable_notifications"
android:key="pref_key_ringtone" android:key="pref_key_ringtone"

View File

@ -1,6 +1,6 @@
/** /**
* Copyright (C) 2011 Whisper Systems * Copyright (C) 2011 Whisper Systems
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
@ -10,12 +10,22 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.thoughtcrime.securesms; package org.thoughtcrime.securesms;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.widget.Toast;
import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.crypto.IdentityKey; import org.thoughtcrime.securesms.crypto.IdentityKey;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
@ -24,21 +34,9 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.Dialogs; import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.MemoryCleaner; import org.thoughtcrime.securesms.util.MemoryCleaner;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.widget.Toast;
/** /**
* The Activity for application preference display and management. * The Activity for application preference display and management.
* *
* @author Moxie Marlinspike * @author Moxie Marlinspike
* *
*/ */
@ -47,7 +45,7 @@ public class ApplicationPreferencesActivity extends PreferenceActivity {
private static final int PICK_IDENTITY_CONTACT = 1; private static final int PICK_IDENTITY_CONTACT = 1;
private static final int IMPORT_IDENTITY_ID = 2; private static final int IMPORT_IDENTITY_ID = 2;
public static final String WHITESPACE_PREF = "pref_key_tag_whitespace"; public static final String WHITESPACE_PREF = "pref_key_tag_whitespace";
public static final String RINGTONE_PREF = "pref_key_ringtone"; public static final String RINGTONE_PREF = "pref_key_ringtone";
public static final String VIBRATE_PREF = "pref_key_vibrate"; public static final String VIBRATE_PREF = "pref_key_vibrate";
@ -55,41 +53,24 @@ public class ApplicationPreferencesActivity extends PreferenceActivity {
public static final String LED_COLOR_PREF = "pref_led_color"; public static final String LED_COLOR_PREF = "pref_led_color";
public static final String LED_BLINK_PREF = "pref_led_blink"; public static final String LED_BLINK_PREF = "pref_led_blink";
public static final String LED_BLINK_PREF_CUSTOM = "pref_led_blink_custom"; public static final String LED_BLINK_PREF_CUSTOM = "pref_led_blink_custom";
public static final String DARK_THREADS_PREF = "pref_dark_threads";
public static final String DARK_CONVERSATION_PREF = "pref_dark_conversation";
public static final String CONVERSATION_ICONS_PREF = "pref_conversation_icons";
public static final String CONVERSATION_ICONS_LIST_PREF = "pref_conversation_list_icons";
public static final String IDENTITY_PREF = "pref_choose_identity"; public static final String IDENTITY_PREF = "pref_choose_identity";
public static final String SEND_IDENTITY_PREF = "pref_send_identity_key"; public static final String SEND_IDENTITY_PREF = "pref_send_identity_key";
public static final String ALL_MMS_PERF = "pref_all_mms"; public static final String ALL_MMS_PERF = "pref_all_mms";
public static final String PASSPHRASE_TIMEOUT_INTERVAL_PREF = "pref_timeout_interval"; public static final String PASSPHRASE_TIMEOUT_INTERVAL_PREF = "pref_timeout_interval";
public static final String PASSPHRASE_TIMEOUT_PREF = "pref_timeout_passphrase"; public static final String PASSPHRASE_TIMEOUT_PREF = "pref_timeout_passphrase";
public static final String AUTO_KEY_EXCHANGE_PREF = "pref_auto_complete_key_exchange"; public static final String AUTO_KEY_EXCHANGE_PREF = "pref_auto_complete_key_exchange";
private static final String VIEW_MY_IDENTITY_PREF = "pref_view_identity"; private static final String VIEW_MY_IDENTITY_PREF = "pref_view_identity";
private static final String EXPORT_MY_IDENTITY_PREF = "pref_export_identity"; private static final String EXPORT_MY_IDENTITY_PREF = "pref_export_identity";
private static final String IMPORT_CONTACT_IDENTITY_PREF = "pref_import_identity"; private static final String IMPORT_CONTACT_IDENTITY_PREF = "pref_import_identity";
private static final String MANAGE_IDENTITIES_PREF = "pref_manage_identity"; private static final String MANAGE_IDENTITIES_PREF = "pref_manage_identity";
private static final String CHANGE_PASSPHRASE_PREF = "pref_change_passphrase"; private static final String CHANGE_PASSPHRASE_PREF = "pref_change_passphrase";
@Override @Override
protected void onCreate(Bundle icicle) { protected void onCreate(Bundle icicle) {
super.onCreate(icicle); super.onCreate(icicle);
addPreferencesFromResource(R.xml.preferences); addPreferencesFromResource(R.xml.preferences);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.DONUT) {
CheckBoxPreference mmsPreference = (CheckBoxPreference)this.findPreference("pref_all_mms");
mmsPreference.setChecked(false);
mmsPreference.setEnabled(false);
mmsPreference.setSelectable(false);
CheckBoxPreference listIconPreference = (CheckBoxPreference)this.findPreference(CONVERSATION_ICONS_LIST_PREF);
listIconPreference.setDefaultValue(false);
CheckBoxPreference conversationIconPreference = (CheckBoxPreference)this.findPreference(CONVERSATION_ICONS_PREF);
conversationIconPreference.setDefaultValue(false);
}
this.findPreference(IDENTITY_PREF).setOnPreferenceClickListener(new IdentityPreferenceClickListener()); this.findPreference(IDENTITY_PREF).setOnPreferenceClickListener(new IdentityPreferenceClickListener());
this.findPreference(VIEW_MY_IDENTITY_PREF).setOnPreferenceClickListener(new ViewMyIdentityClickListener()); this.findPreference(VIEW_MY_IDENTITY_PREF).setOnPreferenceClickListener(new ViewMyIdentityClickListener());
this.findPreference(EXPORT_MY_IDENTITY_PREF).setOnPreferenceClickListener(new ExportMyIdentityClickListener()); this.findPreference(EXPORT_MY_IDENTITY_PREF).setOnPreferenceClickListener(new ExportMyIdentityClickListener());
@ -97,7 +78,7 @@ public class ApplicationPreferencesActivity extends PreferenceActivity {
this.findPreference(MANAGE_IDENTITIES_PREF).setOnPreferenceClickListener(new ManageIdentitiesClickListener()); this.findPreference(MANAGE_IDENTITIES_PREF).setOnPreferenceClickListener(new ManageIdentitiesClickListener());
this.findPreference(CHANGE_PASSPHRASE_PREF).setOnPreferenceClickListener(new ChangePassphraseClickListener()); this.findPreference(CHANGE_PASSPHRASE_PREF).setOnPreferenceClickListener(new ChangePassphraseClickListener());
} }
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
@ -105,7 +86,7 @@ public class ApplicationPreferencesActivity extends PreferenceActivity {
intent.setAction(KeyCachingService.ACTIVITY_START_EVENT); intent.setAction(KeyCachingService.ACTIVITY_START_EVENT);
startService(intent); startService(intent);
} }
@Override @Override
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
@ -113,7 +94,7 @@ public class ApplicationPreferencesActivity extends PreferenceActivity {
intent.setAction(KeyCachingService.ACTIVITY_STOP_EVENT); intent.setAction(KeyCachingService.ACTIVITY_STOP_EVENT);
startService(intent); startService(intent);
} }
@Override @Override
public void onActivityResult(int reqCode, int resultCode, Intent data) { public void onActivityResult(int reqCode, int resultCode, Intent data) {
super.onActivityResult(reqCode, resultCode, data); super.onActivityResult(reqCode, resultCode, data);
@ -125,47 +106,43 @@ public class ApplicationPreferencesActivity extends PreferenceActivity {
} }
} }
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
MemoryCleaner.clean((MasterSecret)getIntent().getParcelableExtra("master_secret")); MemoryCleaner.clean((MasterSecret)getIntent().getParcelableExtra("master_secret"));
super.onDestroy(); super.onDestroy();
} }
public static boolean showIcon() {
return Build.VERSION.SDK_INT > Build.VERSION_CODES.DONUT;
}
private void handleIdentitySelection(Intent data) { private void handleIdentitySelection(Intent data) {
Uri contactData = data.getData(); Uri contactData = data.getData();
if (contactData != null) if (contactData != null)
PreferenceManager.getDefaultSharedPreferences(this).edit().putString(IDENTITY_PREF, contactData.toString()).commit(); PreferenceManager.getDefaultSharedPreferences(this).edit().putString(IDENTITY_PREF, contactData.toString()).commit();
} }
private void importIdentityKey(Uri uri) { private void importIdentityKey(Uri uri) {
IdentityKey identityKey = ContactAccessor.getInstance().importIdentityKey(this, uri); IdentityKey identityKey = ContactAccessor.getInstance().importIdentityKey(this, uri);
String contactName = ContactAccessor.getInstance().getNameFromContact(this, uri); String contactName = ContactAccessor.getInstance().getNameFromContact(this, uri);
if (identityKey == null) { if (identityKey == null) {
Dialogs.displayAlert(this, "Not found!", "No valid identity key was found in the specified contact.", android.R.drawable.ic_dialog_alert); Dialogs.displayAlert(this, "Not found!", "No valid identity key was found in the specified contact.", android.R.drawable.ic_dialog_alert);
return; return;
} }
Intent verifyImportedKeyIntent = new Intent(this, VerifyImportedIdentityActivity.class); Intent verifyImportedKeyIntent = new Intent(this, VerifyImportedIdentityActivity.class);
verifyImportedKeyIntent.putExtra("master_secret", getIntent().getParcelableExtra("master_secret")); verifyImportedKeyIntent.putExtra("master_secret", getIntent().getParcelableExtra("master_secret"));
verifyImportedKeyIntent.putExtra("identity_key", identityKey); verifyImportedKeyIntent.putExtra("identity_key", identityKey);
verifyImportedKeyIntent.putExtra("contact_name", contactName); verifyImportedKeyIntent.putExtra("contact_name", contactName);
startActivity(verifyImportedKeyIntent); startActivity(verifyImportedKeyIntent);
} }
private class IdentityPreferenceClickListener implements Preference.OnPreferenceClickListener { private class IdentityPreferenceClickListener implements Preference.OnPreferenceClickListener {
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
Intent intent = ContactAccessor.getInstance().getIntentForContactSelection(); Intent intent = ContactAccessor.getInstance().getIntentForContactSelection();
startActivityForResult(intent, PICK_IDENTITY_CONTACT); startActivityForResult(intent, PICK_IDENTITY_CONTACT);
return true; return true;
} }
} }
private class ViewMyIdentityClickListener implements Preference.OnPreferenceClickListener { private class ViewMyIdentityClickListener implements Preference.OnPreferenceClickListener {
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
Intent viewIdentityIntent = new Intent(ApplicationPreferencesActivity.this, ViewIdentityActivity.class); Intent viewIdentityIntent = new Intent(ApplicationPreferencesActivity.this, ViewIdentityActivity.class);
@ -175,27 +152,27 @@ public class ApplicationPreferencesActivity extends PreferenceActivity {
return true; return true;
} }
} }
private class ExportMyIdentityClickListener implements Preference.OnPreferenceClickListener { private class ExportMyIdentityClickListener implements Preference.OnPreferenceClickListener {
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
String contactUri = PreferenceManager.getDefaultSharedPreferences(ApplicationPreferencesActivity.this) String contactUri = PreferenceManager.getDefaultSharedPreferences(ApplicationPreferencesActivity.this)
.getString(IDENTITY_PREF, null); .getString(IDENTITY_PREF, null);
if (!IdentityKeyUtil.hasIdentityKey(ApplicationPreferencesActivity.this)) { if (!IdentityKeyUtil.hasIdentityKey(ApplicationPreferencesActivity.this)) {
Toast.makeText(ApplicationPreferencesActivity.this, "You don't have an identity key!", Toast.LENGTH_LONG).show(); Toast.makeText(ApplicationPreferencesActivity.this, "You don't have an identity key!", Toast.LENGTH_LONG).show();
return true; return true;
} }
if (contactUri == null) { if (contactUri == null) {
Toast.makeText(ApplicationPreferencesActivity.this, Toast.makeText(ApplicationPreferencesActivity.this,
"You have not yet defined a contact for yourself! Select one in the Settings menu.", "You have not yet defined a contact for yourself! Select one in the Settings menu.",
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
return true; return true;
} }
ContactAccessor.getInstance().insertIdentityKey(ApplicationPreferencesActivity.this, Uri.parse(contactUri), ContactAccessor.getInstance().insertIdentityKey(ApplicationPreferencesActivity.this, Uri.parse(contactUri),
IdentityKeyUtil.getIdentityKey(ApplicationPreferencesActivity.this)); IdentityKeyUtil.getIdentityKey(ApplicationPreferencesActivity.this));
Toast.makeText(ApplicationPreferencesActivity.this, "Exported to contacts database!", Toast.LENGTH_LONG).show(); Toast.makeText(ApplicationPreferencesActivity.this, "Exported to contacts database!", Toast.LENGTH_LONG).show();
return true; return true;
@ -205,43 +182,43 @@ public class ApplicationPreferencesActivity extends PreferenceActivity {
private class ImportContactIdentityClickListener implements Preference.OnPreferenceClickListener { private class ImportContactIdentityClickListener implements Preference.OnPreferenceClickListener {
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
MasterSecret masterSecret = (MasterSecret)getIntent().getParcelableExtra("master_secret"); MasterSecret masterSecret = (MasterSecret)getIntent().getParcelableExtra("master_secret");
if (masterSecret != null) { if (masterSecret != null) {
Intent importIntent = ContactAccessor.getInstance().getIntentForContactSelection(); Intent importIntent = ContactAccessor.getInstance().getIntentForContactSelection();
startActivityForResult(importIntent, IMPORT_IDENTITY_ID); startActivityForResult(importIntent, IMPORT_IDENTITY_ID);
} else { } else {
Toast.makeText(ApplicationPreferencesActivity.this, Toast.makeText(ApplicationPreferencesActivity.this,
"You need to have entered your passphrase before importing keys...", "You need to have entered your passphrase before importing keys...",
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
} }
return true; return true;
} }
} }
private class ManageIdentitiesClickListener implements Preference.OnPreferenceClickListener { private class ManageIdentitiesClickListener implements Preference.OnPreferenceClickListener {
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
MasterSecret masterSecret = (MasterSecret)getIntent().getParcelableExtra("master_secret"); MasterSecret masterSecret = (MasterSecret)getIntent().getParcelableExtra("master_secret");
if (masterSecret != null) { if (masterSecret != null) {
Intent manageIntent = new Intent(ApplicationPreferencesActivity.this, ReviewIdentitiesActivity.class); Intent manageIntent = new Intent(ApplicationPreferencesActivity.this, ReviewIdentitiesActivity.class);
manageIntent.putExtra("master_secret", masterSecret); manageIntent.putExtra("master_secret", masterSecret);
startActivity(manageIntent); startActivity(manageIntent);
} else { } else {
Toast.makeText(ApplicationPreferencesActivity.this, Toast.makeText(ApplicationPreferencesActivity.this,
"You need to have entered your passphrase before managing keys...", "You need to have entered your passphrase before managing keys...",
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
} }
return true; return true;
} }
} }
private class ChangePassphraseClickListener implements Preference.OnPreferenceClickListener { private class ChangePassphraseClickListener implements Preference.OnPreferenceClickListener {
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
SharedPreferences settings = getSharedPreferences(KeyCachingService.PREFERENCES_NAME, 0); SharedPreferences settings = getSharedPreferences(KeyCachingService.PREFERENCES_NAME, 0);
if (settings.getBoolean("passphrase_initialized", false)) { if (settings.getBoolean("passphrase_initialized", false)) {
startActivity(new Intent(ApplicationPreferencesActivity.this, PassphraseChangeActivity.class)); startActivity(new Intent(ApplicationPreferencesActivity.this, PassphraseChangeActivity.class));
} else { } else {
Toast.makeText(ApplicationPreferencesActivity.this, "You haven't set a passphrase yet!", Toast.LENGTH_LONG).show(); Toast.makeText(ApplicationPreferencesActivity.this, "You haven't set a passphrase yet!", Toast.LENGTH_LONG).show();
@ -250,5 +227,5 @@ public class ApplicationPreferencesActivity extends PreferenceActivity {
return true; return true;
} }
} }
} }

View File

@ -20,6 +20,7 @@ import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.os.Handler; import android.os.Handler;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -63,6 +64,7 @@ public class ConversationAdapter extends CursorAdapter {
private final Recipients recipients; private final Recipients recipients;
private final MasterSecret masterSecret; private final MasterSecret masterSecret;
private final MasterCipher masterCipher; private final MasterCipher masterCipher;
private final LayoutInflater inflater;
private boolean dataChanged; private boolean dataChanged;
@ -76,6 +78,8 @@ public class ConversationAdapter extends CursorAdapter {
this.dataChanged = false; this.dataChanged = false;
this.failedIconClickHandler = failedIconClickHandler; this.failedIconClickHandler = failedIconClickHandler;
this.messageRecordCache = initializeCache(); this.messageRecordCache = initializeCache();
this.inflater = (LayoutInflater)context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
DatabaseFactory.getThreadDatabase(context).setRead(threadId); DatabaseFactory.getThreadDatabase(context).setRead(threadId);
MessageNotifier.updateNotification(context, false); MessageNotifier.updateNotification(context, false);
@ -107,12 +111,37 @@ public class ConversationAdapter extends CursorAdapter {
@Override @Override
public View newView(Context context, Cursor cursor, ViewGroup parent) { public View newView(Context context, Cursor cursor, ViewGroup parent) {
ConversationItem view = new ConversationItem(context); View view;
bindView(view, context, cursor);
int type = getItemViewType(cursor);
if (type == 0) view = inflater.inflate(R.layout.conversation_item_sent, parent, false);
else view = inflater.inflate(R.layout.conversation_item_received, parent, false);
bindView(view, context, cursor);
return view; return view;
} }
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
Cursor cursor = (Cursor)getItem(position);
return getItemViewType(cursor);
}
private int getItemViewType(Cursor cursor) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.ID));
String type = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.TRANSPORT));
MessageRecord messageRecord = getMessageRecord(id, cursor, type);
if (messageRecord.isOutgoing()) return 0;
else return 1;
}
private MessageRecord getNewMmsMessageRecord(long messageId, Cursor cursor) { private MessageRecord getNewMmsMessageRecord(long messageId, Cursor cursor) {
MessageRecord messageRecord = getNewSmsMessageRecord(messageId, cursor); MessageRecord messageRecord = getNewSmsMessageRecord(messageId, cursor);
long mmsType = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_TYPE)); long mmsType = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_TYPE));

View File

@ -1,6 +1,6 @@
/** /**
* Copyright (C) 2011 Whisper Systems * Copyright (C) 2011 Whisper Systems
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
@ -10,19 +10,36 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.thoughtcrime.securesms; package org.thoughtcrime.securesms;
import java.io.File; import android.app.AlertDialog;
import java.io.FileOutputStream; import android.app.ProgressDialog;
import java.io.IOException; import android.content.Context;
import java.io.InputStream; import android.content.DialogInterface;
import java.io.OutputStream; import android.content.Intent;
import java.util.Iterator; import android.media.MediaScannerConnection;
import java.util.List; import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.telephony.TelephonyManager;
import android.text.Spannable;
import android.text.format.DateUtils;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.MessageRecord; import org.thoughtcrime.securesms.database.MessageRecord;
@ -35,36 +52,18 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.service.SendReceiveService; import org.thoughtcrime.securesms.service.SendReceiveService;
import android.app.AlertDialog; import java.io.File;
import android.app.ProgressDialog; import java.io.FileOutputStream;
import android.content.Context; import java.io.IOException;
import android.content.DialogInterface; import java.io.InputStream;
import android.content.Intent; import java.io.OutputStream;
import android.graphics.Color; import java.util.Iterator;
import android.media.MediaScannerConnection; import java.util.List;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.telephony.TelephonyManager;
import android.text.Spannable;
import android.text.format.DateUtils;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
/** /**
* A view that displays an individual conversation item within a conversation * A view that displays an individual conversation item within a conversation
* thread. Used by ComposeMessageActivity's ListActivity via a ConversationAdapter. * thread. Used by ComposeMessageActivity's ListActivity via a ConversationAdapter.
* *
* @author Moxie Marlinspike * @author Moxie Marlinspike
* *
*/ */
@ -75,30 +74,37 @@ public class ConversationItem extends LinearLayout {
private MessageRecord messageRecord; private MessageRecord messageRecord;
private MasterSecret masterSecret; private MasterSecret masterSecret;
private final TextView bodyText; private TextView bodyText;
private final TextView dateText; private TextView dateText;
private final ImageView pendingImage; private ImageView pendingImage;
private final ImageView secureImage; private ImageView secureImage;
private final ImageView failedImage; private ImageView failedImage;
private final ImageView keyImage; private ImageView keyImage;
private final ImageView contactPhoto; private ImageView contactPhoto;
private final ImageView mmsThumbnail; private ImageView mmsThumbnail;
private final Button mmsDownloadButton; private Button mmsDownloadButton;
private final TextView mmsDownloadingLabel; private TextView mmsDownloadingLabel;
private final FailedIconClickListener failedIconClickListener = new FailedIconClickListener(); private final FailedIconClickListener failedIconClickListener = new FailedIconClickListener();
private final MmsDownloadClickListener mmsDownloadClickListener = new MmsDownloadClickListener(); private final MmsDownloadClickListener mmsDownloadClickListener = new MmsDownloadClickListener();
private final ClickListener clickListener = new ClickListener(); private final ClickListener clickListener = new ClickListener();
private final Context context; private final Context context;
public ConversationItem(Context context) { public ConversationItem(Context context) {
super(context); super(context);
this.context = context;
LayoutInflater li = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); }
li.inflate(R.layout.conversation_item, this);
public ConversationItem(Context context, AttributeSet attrs) {
this.context = context; super(context, attrs);
this.context = context;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
this.bodyText = (TextView) findViewById(R.id.conversation_item_body); this.bodyText = (TextView) findViewById(R.id.conversation_item_body);
this.dateText = (TextView) findViewById(R.id.conversation_item_date); this.dateText = (TextView) findViewById(R.id.conversation_item_date);
this.pendingImage = (ImageView)findViewById(R.id.sms_pending_indicator); this.pendingImage = (ImageView)findViewById(R.id.sms_pending_indicator);
@ -109,55 +115,50 @@ public class ConversationItem extends LinearLayout {
this.mmsDownloadButton = (Button) findViewById(R.id.mms_download_button); this.mmsDownloadButton = (Button) findViewById(R.id.mms_download_button);
this.mmsDownloadingLabel = (TextView) findViewById(R.id.mms_label_downloading); this.mmsDownloadingLabel = (TextView) findViewById(R.id.mms_label_downloading);
this.contactPhoto = (ImageView)findViewById(R.id.contact_photo); this.contactPhoto = (ImageView)findViewById(R.id.contact_photo);
setOnClickListener(clickListener); setOnClickListener(clickListener);
this.failedImage.setOnClickListener(failedIconClickListener); this.failedImage.setOnClickListener(failedIconClickListener);
this.mmsDownloadButton.setOnClickListener(mmsDownloadClickListener); this.mmsDownloadButton.setOnClickListener(mmsDownloadClickListener);
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(ApplicationPreferencesActivity.DARK_CONVERSATION_PREF, false)) {
this.bodyText.setTextColor(Color.WHITE);
this.dateText.setTextColor(Color.LTGRAY);
}
} }
public void set(MasterSecret masterSecret, MessageRecord messageRecord, Handler failedIconHandler) public void set(MasterSecret masterSecret, MessageRecord messageRecord, Handler failedIconHandler)
{ {
this.messageRecord = messageRecord; this.messageRecord = messageRecord;
this.masterSecret = masterSecret; this.masterSecret = masterSecret;
this.failedIconHandler = failedIconHandler; this.failedIconHandler = failedIconHandler;
// Double-dispatch back to methods below. // Double-dispatch back to methods below.
messageRecord.setOnConversationItem(this); messageRecord.setOnConversationItem(this);
} }
public MessageRecord getMessageRecord() { public MessageRecord getMessageRecord() {
return messageRecord; return messageRecord;
} }
public void setMessageRecord(MessageRecord messageRecord) { public void setMessageRecord(MessageRecord messageRecord) {
setBody(messageRecord); setBody(messageRecord);
setDate(messageRecord.getDate()); setDate(messageRecord.getDate());
setStatusIcons(messageRecord); setStatusIcons(messageRecord);
setEvents(messageRecord); setEvents(messageRecord);
} }
public void setMessageRecord(MmsMessageRecord messageRecord) { public void setMessageRecord(MmsMessageRecord messageRecord) {
setMessageRecord((MessageRecord)messageRecord); setMessageRecord((MessageRecord)messageRecord);
if (messageRecord.isNotification()) if (messageRecord.isNotification())
setMmsNotificationAttributes(messageRecord); setMmsNotificationAttributes(messageRecord);
else else
setMmsMediaAttributes(messageRecord); setMmsMediaAttributes(messageRecord);
} }
private void setMmsNotificationAttributes(MmsMessageRecord messageRecord) { private void setMmsNotificationAttributes(MmsMessageRecord messageRecord) {
String messageSize = "Message size: " + messageRecord.getMessageSize() + " KB"; String messageSize = "Message size: " + messageRecord.getMessageSize() + " KB";
String expires = "Expires: " + DateUtils.getRelativeTimeSpanString(getContext(), messageRecord.getExpiration(), false); String expires = "Expires: " + DateUtils.getRelativeTimeSpanString(getContext(), messageRecord.getExpiration(), false);
dateText.setText(messageSize + "\n" + expires); dateText.setText(messageSize + "\n" + expires);
if (MmsDatabase.Types.isDisplayDownloadButton(messageRecord.getStatus())) { if (MmsDatabase.Types.isDisplayDownloadButton(messageRecord.getStatus())) {
mmsDownloadButton.setVisibility(View.VISIBLE); mmsDownloadButton.setVisibility(View.VISIBLE);
mmsDownloadingLabel.setVisibility(View.GONE); mmsDownloadingLabel.setVisibility(View.GONE);
@ -166,17 +167,17 @@ public class ConversationItem extends LinearLayout {
mmsDownloadButton.setVisibility(View.GONE); mmsDownloadButton.setVisibility(View.GONE);
mmsDownloadingLabel.setVisibility(View.VISIBLE); mmsDownloadingLabel.setVisibility(View.VISIBLE);
} }
if (MmsDatabase.Types.isHardError(messageRecord.getStatus())) if (MmsDatabase.Types.isHardError(messageRecord.getStatus()))
failedImage.setVisibility(View.VISIBLE); failedImage.setVisibility(View.VISIBLE);
} }
private void setMmsMediaAttributes(MmsMessageRecord messageRecord) { private void setMmsMediaAttributes(MmsMessageRecord messageRecord) {
SlideDeck slideDeck = messageRecord.getSlideDeck(); SlideDeck slideDeck = messageRecord.getSlideDeck();
List<Slide> slides = slideDeck.getSlides(); List<Slide> slides = slideDeck.getSlides();
Iterator<Slide> iterator = slides.iterator(); Iterator<Slide> iterator = slides.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
Slide slide = iterator.next(); Slide slide = iterator.next();
if (slide.hasImage()) { if (slide.hasImage()) {
@ -190,11 +191,11 @@ public class ConversationItem extends LinearLayout {
mmsThumbnail.setVisibility(View.GONE); mmsThumbnail.setVisibility(View.GONE);
} }
public void setHandler(Handler failedIconHandler) { public void setHandler(Handler failedIconHandler) {
this.failedIconHandler = failedIconHandler; this.failedIconHandler = failedIconHandler;
} }
private void checkForAutoInitiate(MessageRecord messageRecord) { private void checkForAutoInitiate(MessageRecord messageRecord) {
if (AutoInitiateActivity.isValidAutoInitiateSituation(context, masterSecret, messageRecord.getRecipients().getPrimaryRecipient(), messageRecord.getBody(), messageRecord.getThreadId())) { if (AutoInitiateActivity.isValidAutoInitiateSituation(context, masterSecret, messageRecord.getRecipients().getPrimaryRecipient(), messageRecord.getBody(), messageRecord.getThreadId())) {
AutoInitiateActivity.exemptThread(context, messageRecord.getThreadId()); AutoInitiateActivity.exemptThread(context, messageRecord.getThreadId());
@ -203,40 +204,24 @@ public class ConversationItem extends LinearLayout {
intent.putExtra("threadId", messageRecord.getThreadId()); intent.putExtra("threadId", messageRecord.getThreadId());
intent.putExtra("masterSecret", masterSecret); intent.putExtra("masterSecret", masterSecret);
intent.putExtra("recipient", messageRecord.getRecipients().getPrimaryRecipient()); intent.putExtra("recipient", messageRecord.getRecipients().getPrimaryRecipient());
context.startActivity(intent); context.startActivity(intent);
} }
} }
private void setBodyText(MessageRecord messageRecord) { private void setBodyText(MessageRecord messageRecord) {
String address = ""; String body = messageRecord.getBody();
Recipient recipient = messageRecord.getMessageRecipient();
String body = messageRecord.getBody();
if (messageRecord.isKeyExchange() && messageRecord.isOutgoing()) body = "\nSent key exchange message"; if (messageRecord.isKeyExchange() && messageRecord.isOutgoing()) body = "\nSent key exchange message";
else if (messageRecord.isProcessedKeyExchange() && !messageRecord.isOutgoing()) body = "\nReceived and processed key exchange message."; else if (messageRecord.isProcessedKeyExchange() && !messageRecord.isOutgoing()) body = "\nReceived and processed key exchange message.";
else if (messageRecord.isStaleKeyExchange()) body = "\nError, received stale key exchange message."; else if (messageRecord.isStaleKeyExchange()) body = "\nError, received stale key exchange message.";
else if (messageRecord.isKeyExchange() && !messageRecord.isOutgoing()) body = "\nReceived key exchange message, click to process"; else if (messageRecord.isKeyExchange() && !messageRecord.isOutgoing()) body = "\nReceived key exchange message, click to process";
else if (messageRecord.isOutgoing()) address = "Me: ";
else address = (recipient.getName() == null ? recipient.getNumber() : recipient.getName()) + ": ";
bodyText.setText(address + body, TextView.BufferType.SPANNABLE);
if (messageRecord.isKeyExchange() || messageRecord.getEmphasis()) {
((Spannable)bodyText.getText()).setSpan(new ForegroundColorSpan(context.getResources().getColor(android.R.color.darker_gray)), address.length(), address.length() + body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
((Spannable)bodyText.getText()).setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, address.length() + body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
((Spannable)bodyText.getText()).setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, address.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
private void setBodyBackground(MessageRecord messageRecord) { bodyText.setText(body, TextView.BufferType.SPANNABLE);
if (!PreferenceManager.getDefaultSharedPreferences(context).getBoolean(ApplicationPreferencesActivity.DARK_CONVERSATION_PREF, false)) {
if (messageRecord.isOutgoing()) setBackgroundResource(R.drawable.conversation_item_background_lightblue); if (messageRecord.isKeyExchange() || messageRecord.getEmphasis()) {
else setBackgroundResource(R.drawable.conversation_item_background); ((Spannable)bodyText.getText()).setSpan(new ForegroundColorSpan(context.getResources().getColor(android.R.color.darker_gray)), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else { ((Spannable)bodyText.getText()).setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
if (messageRecord.isOutgoing()) setBackgroundResource(R.drawable.conversation_item_background_black);
else setBackgroundResource(R.drawable.conversation_item_background_lightgrey);
} }
} }
@ -250,8 +235,8 @@ public class ConversationItem extends LinearLayout {
contactPhoto.setImageBitmap(recipient.getContactPhoto()); contactPhoto.setImageBitmap(recipient.getContactPhoto());
return; return;
} }
} }
if (hasLocalNumber()) { if (hasLocalNumber()) {
contactPhoto.setImageBitmap(RecipientFactory.getRecipientsFromString(context, getLocalNumber()).getPrimaryRecipient().getContactPhoto()); contactPhoto.setImageBitmap(RecipientFactory.getRecipientsFromString(context, getLocalNumber()).getPrimaryRecipient().getContactPhoto());
} else { } else {
@ -261,60 +246,58 @@ public class ConversationItem extends LinearLayout {
Log.w("ConversationItem", rfe); Log.w("ConversationItem", rfe);
} }
} }
private void setBodyImage(MessageRecord messageRecord) { private void setBodyImage(MessageRecord messageRecord) {
Recipient recipient = messageRecord.getMessageRecipient(); Recipient recipient = messageRecord.getMessageRecipient();
if (!PreferenceManager.getDefaultSharedPreferences(context).getBoolean(ApplicationPreferencesActivity.CONVERSATION_ICONS_PREF, ApplicationPreferencesActivity.showIcon()) || if (messageRecord.isKeyExchange())
messageRecord.isKeyExchange())
{ {
contactPhoto.setVisibility(View.GONE); contactPhoto.setVisibility(View.GONE);
return; return;
} }
if (!messageRecord.isOutgoing()) contactPhoto.setImageBitmap(recipient.getContactPhoto()); if (!messageRecord.isOutgoing()) contactPhoto.setImageBitmap(recipient.getContactPhoto());
else setContactPhotoForUserIdentity(); else setContactPhotoForUserIdentity();
contactPhoto.setVisibility(View.VISIBLE); contactPhoto.setVisibility(View.VISIBLE);
} }
private void setBody(MessageRecord messageRecord) { private void setBody(MessageRecord messageRecord) {
setBodyText(messageRecord); setBodyText(messageRecord);
setBodyBackground(messageRecord);
setBodyImage(messageRecord); setBodyImage(messageRecord);
} }
private String getLocalNumber() { private String getLocalNumber() {
return ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number(); return ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number();
} }
private boolean hasLocalNumber() { private boolean hasLocalNumber() {
String number = getLocalNumber(); String number = getLocalNumber();
return (number != null) && (number.trim().length() > 0); return (number != null) && (number.trim().length() > 0);
} }
private void setDate(long date) { private void setDate(long date) {
dateText.setText("Sent: " + DateUtils.getRelativeTimeSpanString(getContext(), date, false)); dateText.setText(DateUtils.getRelativeTimeSpanString(getContext(), date, false));
} }
private void setStatusIcons(MessageRecord messageRecord) { private void setStatusIcons(MessageRecord messageRecord) {
pendingImage.setVisibility(messageRecord.isPending() ? View.VISIBLE : View.GONE); pendingImage.setVisibility(messageRecord.isPending() ? View.VISIBLE : View.GONE);
failedImage.setVisibility(messageRecord.isFailed() ? View.VISIBLE : View.GONE); failedImage.setVisibility(messageRecord.isFailed() ? View.VISIBLE : View.GONE);
secureImage.setVisibility(messageRecord.isSecure() ? View.VISIBLE : View.GONE); secureImage.setVisibility(messageRecord.isSecure() ? View.VISIBLE : View.GONE);
keyImage.setVisibility(messageRecord.isKeyExchange() ? View.VISIBLE : View.GONE); keyImage.setVisibility(messageRecord.isKeyExchange() ? View.VISIBLE : View.GONE);
mmsThumbnail.setVisibility(View.GONE); mmsThumbnail.setVisibility(View.GONE);
mmsDownloadButton.setVisibility(View.GONE); mmsDownloadButton.setVisibility(View.GONE);
mmsDownloadingLabel.setVisibility(View.GONE); mmsDownloadingLabel.setVisibility(View.GONE);
} }
private void setEvents(MessageRecord messageRecord) { private void setEvents(MessageRecord messageRecord) {
setClickable(messageRecord.isKeyExchange() && !messageRecord.isOutgoing()); setClickable(messageRecord.isKeyExchange() && !messageRecord.isOutgoing());
if (!messageRecord.isOutgoing() && messageRecord.getRecipients().isSingleRecipient()) if (!messageRecord.isOutgoing() && messageRecord.getRecipients().isSingleRecipient())
checkForAutoInitiate(messageRecord); checkForAutoInitiate(messageRecord);
} }
private void handleKeyExchangeClicked() { private void handleKeyExchangeClicked() {
Intent intent = new Intent(context, ReceiveKeyActivity.class); Intent intent = new Intent(context, ReceiveKeyActivity.class);
intent.putExtra("recipient", messageRecord.getRecipients().getPrimaryRecipient()); intent.putExtra("recipient", messageRecord.getRecipients().getPrimaryRecipient());
@ -324,17 +307,17 @@ public class ConversationItem extends LinearLayout {
intent.putExtra("sent", messageRecord.isOutgoing()); intent.putExtra("sent", messageRecord.isOutgoing());
context.startActivity(intent); context.startActivity(intent);
} }
private class ThumbnailSaveListener extends Handler implements View.OnLongClickListener, Runnable, MediaScannerConnection.MediaScannerConnectionClient { private class ThumbnailSaveListener extends Handler implements View.OnLongClickListener, Runnable, MediaScannerConnection.MediaScannerConnectionClient {
private static final int SUCCESS = 0; private static final int SUCCESS = 0;
private static final int FAILURE = 1; private static final int FAILURE = 1;
private static final int WRITE_ACCESS_FAILURE = 2; private static final int WRITE_ACCESS_FAILURE = 2;
private final Slide slide; private final Slide slide;
private ProgressDialog progressDialog; private ProgressDialog progressDialog;
private MediaScannerConnection mediaScannerConnection; private MediaScannerConnection mediaScannerConnection;
private File mediaFile; private File mediaFile;
public ThumbnailSaveListener(Slide slide) { public ThumbnailSaveListener(Slide slide) {
this.slide = slide; this.slide = slide;
} }
@ -349,40 +332,40 @@ public class ConversationItem extends LinearLayout {
mediaFile = constructOutputFile(); mediaFile = constructOutputFile();
InputStream inputStream = slide.getPartDataInputStream(); InputStream inputStream = slide.getPartDataInputStream();
OutputStream outputStream = new FileOutputStream(mediaFile); OutputStream outputStream = new FileOutputStream(mediaFile);
byte[] buffer = new byte[4096]; byte[] buffer = new byte[4096];
int read; int read;
while ((read = inputStream.read(buffer)) != -1) { while ((read = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, read); outputStream.write(buffer, 0, read);
} }
outputStream.close(); outputStream.close();
inputStream.close(); inputStream.close();
mediaScannerConnection = new MediaScannerConnection(context, this); mediaScannerConnection = new MediaScannerConnection(context, this);
mediaScannerConnection.connect(); mediaScannerConnection.connect();
} catch (IOException ioe) { } catch (IOException ioe) {
Log.w("ConversationItem", ioe); Log.w("ConversationItem", ioe);
this.obtainMessage(FAILURE).sendToTarget(); this.obtainMessage(FAILURE).sendToTarget();
} }
} }
private File constructOutputFile() throws IOException { private File constructOutputFile() throws IOException {
File sdCard = Environment.getExternalStorageDirectory(); File sdCard = Environment.getExternalStorageDirectory();
File outputDirectory; File outputDirectory;
if (slide.hasVideo()) if (slide.hasVideo())
outputDirectory = new File(sdCard.getAbsoluteFile() + File.separator + "Movies"); outputDirectory = new File(sdCard.getAbsoluteFile() + File.separator + "Movies");
else if (slide.hasAudio()) else if (slide.hasAudio())
outputDirectory = new File(sdCard.getAbsolutePath() + File.separator + "Music"); outputDirectory = new File(sdCard.getAbsolutePath() + File.separator + "Music");
else else
outputDirectory = new File(sdCard.getAbsolutePath() + File.separator + "Pictures"); outputDirectory = new File(sdCard.getAbsolutePath() + File.separator + "Pictures");
outputDirectory.mkdirs(); outputDirectory.mkdirs();
return File.createTempFile("textsecure", ".attach", outputDirectory); return File.createTempFile("textsecure", ".attach", outputDirectory);
} }
private void saveToSdCard() { private void saveToSdCard() {
progressDialog = new ProgressDialog(context); progressDialog = new ProgressDialog(context);
progressDialog.setTitle("Saving Attachment"); progressDialog.setTitle("Saving Attachment");
@ -393,7 +376,7 @@ public class ConversationItem extends LinearLayout {
progressDialog.show(); progressDialog.show();
new Thread(this).start(); new Thread(this).start();
} }
public boolean onLongClick(View v) { public boolean onLongClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(context); AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("Save to SD Card?"); builder.setTitle("Save to SD Card?");
@ -407,10 +390,10 @@ public class ConversationItem extends LinearLayout {
}); });
builder.setNegativeButton(R.string.no, null); builder.setNegativeButton(R.string.no, null);
builder.show(); builder.show();
return true; return true;
} }
@Override @Override
public void handleMessage(Message message) { public void handleMessage(Message message) {
switch (message.what) { switch (message.what) {
@ -424,7 +407,7 @@ public class ConversationItem extends LinearLayout {
Toast.makeText(context, "Unable to write to SD Card!", Toast.LENGTH_LONG); Toast.makeText(context, "Unable to write to SD Card!", Toast.LENGTH_LONG);
break; break;
} }
progressDialog.dismiss(); progressDialog.dismiss();
} }
@ -434,13 +417,13 @@ public class ConversationItem extends LinearLayout {
public void onScanCompleted(String path, Uri uri) { public void onScanCompleted(String path, Uri uri) {
mediaScannerConnection.disconnect(); mediaScannerConnection.disconnect();
this.obtainMessage(SUCCESS).sendToTarget(); this.obtainMessage(SUCCESS).sendToTarget();
} }
} }
private class ThumbnailClickListener implements View.OnClickListener { private class ThumbnailClickListener implements View.OnClickListener {
private final Slide slide; private final Slide slide;
public ThumbnailClickListener(Slide slide) { public ThumbnailClickListener(Slide slide) {
this.slide = slide; this.slide = slide;
} }
@ -452,7 +435,7 @@ public class ConversationItem extends LinearLayout {
intent.setDataAndType(slide.getUri(), slide.getContentType()); intent.setDataAndType(slide.getUri(), slide.getContentType());
context.startActivity(intent); context.startActivity(intent);
} }
public void onClick(View v) { public void onClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(context); AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("View secure media?"); builder.setTitle("View secure media?");
@ -466,15 +449,15 @@ public class ConversationItem extends LinearLayout {
}); });
builder.setNegativeButton(R.string.no, null); builder.setNegativeButton(R.string.no, null);
builder.show(); builder.show();
} }
} }
private class MmsDownloadClickListener implements View.OnClickListener { private class MmsDownloadClickListener implements View.OnClickListener {
public void onClick(View v) { public void onClick(View v) {
Log.w("MmsDownloadClickListener", "Content location: " + new String(((MmsMessageRecord)messageRecord).getContentLocation())); Log.w("MmsDownloadClickListener", "Content location: " + new String(((MmsMessageRecord)messageRecord).getContentLocation()));
mmsDownloadButton.setVisibility(View.GONE); mmsDownloadButton.setVisibility(View.GONE);
mmsDownloadingLabel.setVisibility(View.VISIBLE); mmsDownloadingLabel.setVisibility(View.VISIBLE);
Intent intent = new Intent(context, SendReceiveService.class); Intent intent = new Intent(context, SendReceiveService.class);
intent.putExtra("content_location", new String(((MmsMessageRecord)messageRecord).getContentLocation())); intent.putExtra("content_location", new String(((MmsMessageRecord)messageRecord).getContentLocation()));
intent.putExtra("message_id", ((MmsMessageRecord)messageRecord).getId()); intent.putExtra("message_id", ((MmsMessageRecord)messageRecord).getId());
@ -484,7 +467,7 @@ public class ConversationItem extends LinearLayout {
context.startService(intent); context.startService(intent);
} }
} }
private class FailedIconClickListener implements View.OnClickListener { private class FailedIconClickListener implements View.OnClickListener {
public void onClick(View v) { public void onClick(View v) {
if (failedIconHandler != null && !messageRecord.isKeyExchange()) { if (failedIconHandler != null && !messageRecord.isKeyExchange()) {
@ -494,15 +477,15 @@ public class ConversationItem extends LinearLayout {
} }
} }
} }
private class ClickListener implements View.OnClickListener { private class ClickListener implements View.OnClickListener {
public void onClick(View v) { public void onClick(View v) {
if (messageRecord.isKeyExchange() && if (messageRecord.isKeyExchange() &&
!messageRecord.isOutgoing() && !messageRecord.isOutgoing() &&
!messageRecord.isProcessedKeyExchange() && !messageRecord.isProcessedKeyExchange() &&
!messageRecord.isStaleKeyExchange()) !messageRecord.isStaleKeyExchange())
handleKeyExchangeClicked(); handleKeyExchangeClicked();
} }
} }
} }

View File

@ -0,0 +1,118 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
import org.thoughtcrime.securesms.R;
public class ImageDivet extends ImageView {
private static final float CORNER_OFFSET = 12F;
private static final String[] POSITIONS = new String[] {"left", "right"};
private Drawable drawable;
private int drawableIntrinsicWidth;
private int drawableIntrinsicHeight;
private int position;
private float density;
public ImageDivet(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize(attrs);
}
public ImageDivet(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(attrs);
}
public ImageDivet(Context context) {
super(context);
initialize(null);
}
private void initialize(AttributeSet attrs) {
if (attrs != null) {
position = attrs.getAttributeListValue(null, "position", POSITIONS, -1);
}
density = getContext().getResources().getDisplayMetrics().density;
setDrawable();
}
private void setDrawable() {
Resources r = getContext().getResources();
switch (position) {
case 0:
drawable = r.getDrawable(R.drawable.divet_right);
break;
case 1:
drawable = r.getDrawable(R.drawable.divet_left);
break;
}
drawableIntrinsicWidth = drawable.getIntrinsicWidth();
drawableIntrinsicHeight = drawable.getIntrinsicHeight();
}
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
c.save();
computeBounds(c);
drawable.draw(c);
c.restore();
}
public void setPosition(int position) {
this.position = position;
setDrawable();
invalidate();
}
public int getPosition() {
return position;
}
public float getCloseOffset() {
return CORNER_OFFSET * density;
}
public ImageView asImageView() {
return this;
}
public float getFarOffset() {
return getCloseOffset() + drawableIntrinsicHeight;
}
private void computeBounds(Canvas c) {
final int left = 0;
final int top = 0;
final int right = getWidth();
final int cornerOffset = (int) getCloseOffset();
switch (position) {
case 1:
drawable.setBounds(
right - drawableIntrinsicWidth,
top + cornerOffset,
right,
top + cornerOffset + drawableIntrinsicHeight);
break;
case 0:
drawable.setBounds(
left,
top + cornerOffset,
left + drawableIntrinsicWidth,
top + cornerOffset + drawableIntrinsicHeight);
break;
}
}
}