diff --git a/res/layout/conversation_item_activity.xml b/res/layout/conversation_item_activity.xml new file mode 100644 index 0000000000..11b3032fa2 --- /dev/null +++ b/res/layout/conversation_item_activity.xml @@ -0,0 +1,34 @@ + + + + + + + diff --git a/res/layout/conversation_list_item_view.xml b/res/layout/conversation_list_item_view.xml index a2022889aa..e99aac810b 100644 --- a/res/layout/conversation_list_item_view.xml +++ b/res/layout/conversation_list_item_view.xml @@ -53,6 +53,7 @@ android:fontFamily="sans-serif-light" android:singleLine="true" android:layout_alignTop="@id/contact_photo_frame" + android:layout_toRightOf="@id/from" android:layout_alignParentRight="true" /> - + android:paddingLeft="15dp" + android:paddingRight="15dp" /> + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:fastScrollEnabled="true" /> + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center|center_vertical" + android:layout_marginTop="15dp" + android:text="@string/contact_selection_group_activity__no_contacts" + android:textSize="20sp" /> diff --git a/res/layout/single_contact_selection_list_item.xml b/res/layout/single_contact_selection_list_item.xml index 463a44bafc..5afc8cf434 100644 --- a/res/layout/single_contact_selection_list_item.xml +++ b/res/layout/single_contact_selection_list_item.xml @@ -5,6 +5,14 @@ android:layout_height="?android:attr/listPreferredItemHeight" android:paddingRight="25dip"> + + + + + + + + \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index eddcd6b07a..1089303301 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -17,7 +17,6 @@ package org.thoughtcrime.securesms; import android.app.AlertDialog; -import android.app.ProgressDialog; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.Context; @@ -85,7 +84,6 @@ import org.thoughtcrime.securesms.util.CharacterCalculator; import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.EncryptedCharacterCalculator; -import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.MemoryCleaner; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.textsecure.crypto.InvalidMessageException; @@ -95,6 +93,7 @@ import org.whispersystems.textsecure.storage.Session; import org.whispersystems.textsecure.util.Util; import java.io.IOException; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -211,10 +210,16 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi switch (reqCode) { case PICK_CONTACT: if (resultCode == RESULT_OK) { - List contacts = data.getParcelableArrayListExtra("contacts"); - if (contacts != null) { - recipientsPanel.addContacts(contacts); + Recipients recipients = data.getParcelableExtra("recipients"); + if (recipients != null) { + recipientsPanel.addRecipients(recipients); this.recipients = getRecipients(); + } else { + ArrayList contacts = data.getParcelableArrayListExtra("contacts"); + if (contacts != null) { + recipientsPanel.addContacts(contacts); + this.recipients = getRecipients(); + } } } else { Log.w("ConversationActivity", "gonna have a bad time."); diff --git a/src/org/thoughtcrime/securesms/ConversationAdapter.java b/src/org/thoughtcrime/securesms/ConversationAdapter.java index 839a23f867..8a37dfc8c3 100644 --- a/src/org/thoughtcrime/securesms/ConversationAdapter.java +++ b/src/org/thoughtcrime/securesms/ConversationAdapter.java @@ -51,6 +51,10 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re private final Map> messageRecordCache = Collections.synchronizedMap(new LRUCache>(MAX_CACHE_SIZE)); + public static final int MESSAGE_TYPE_OUTGOING = 0; + public static final int MESSAGE_TYPE_INCOMING = 1; + public static final int MESSAGE_TYPE_GROUP_ACTION = 2; + private final Handler failedIconClickHandler; private final Context context; private final MasterSecret masterSecret; @@ -85,8 +89,18 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re 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); + switch (type) { + case ConversationAdapter.MESSAGE_TYPE_OUTGOING: + view = inflater.inflate(R.layout.conversation_item_sent, parent, false); + break; + case ConversationAdapter.MESSAGE_TYPE_INCOMING: + view = inflater.inflate(R.layout.conversation_item_received, parent, false); + break; + case ConversationAdapter.MESSAGE_TYPE_GROUP_ACTION: + view = inflater.inflate(R.layout.conversation_item_activity, parent, false); + break; + default: throw new IllegalArgumentException("unsupported item view type given to ConversationAdapter"); + } bindView(view, context, cursor); return view; @@ -94,7 +108,7 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re @Override public int getViewTypeCount() { - return 2; + return 3; } @Override @@ -107,9 +121,9 @@ public class ConversationAdapter extends CursorAdapter implements AbsListView.Re long id = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.ID)); String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT)); MessageRecord messageRecord = getMessageRecord(id, cursor, type); - - if (messageRecord.isOutgoing()) return 0; - else return 1; + if (messageRecord.getGroupAction() > 0) return MESSAGE_TYPE_GROUP_ACTION; + if (messageRecord.isOutgoing()) return MESSAGE_TYPE_OUTGOING; + else return MESSAGE_TYPE_INCOMING; } private MessageRecord getMessageRecord(long messageId, Cursor cursor, String type) { diff --git a/src/org/thoughtcrime/securesms/ConversationItem.java b/src/org/thoughtcrime/securesms/ConversationItem.java index 26b92ad700..b74aab090c 100644 --- a/src/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/ConversationItem.java @@ -121,7 +121,7 @@ public class ConversationItem extends LinearLayout { this.secureImage = (ImageView)findViewById(R.id.sms_secure_indicator); this.failedImage = (ImageView)findViewById(R.id.sms_failed_indicator); this.keyImage = (ImageView)findViewById(R.id.key_exchange_indicator); - this.mmsContainer = (View) findViewById(R.id.mms_view); + 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); @@ -129,8 +129,8 @@ public class ConversationItem extends LinearLayout { this.deliveredImage = (ImageView)findViewById(R.id.delivered_indicator); setOnClickListener(clickListener); - this.failedImage.setOnClickListener(failedIconClickListener); - this.mmsDownloadButton.setOnClickListener(mmsDownloadClickListener); + if (failedImage != null) failedImage.setOnClickListener(failedIconClickListener); + if (mmsDownloadButton != null) mmsDownloadButton.setOnClickListener(mmsDownloadClickListener); } public void set(MasterSecret masterSecret, MessageRecord messageRecord, @@ -142,10 +142,12 @@ public class ConversationItem extends LinearLayout { this.groupThread = groupThread; setBodyText(messageRecord); - setStatusIcons(messageRecord); - setContactPhoto(messageRecord); - setGroupMessageStatus(messageRecord); - setEvents(messageRecord); + if (messageRecord.getGroupAction() < 1) { + setStatusIcons(messageRecord); + setContactPhoto(messageRecord); + setGroupMessageStatus(messageRecord); + setEvents(messageRecord); + } if (messageRecord instanceof NotificationMmsMessageRecord) { setNotificationMmsAttributes((NotificationMmsMessageRecord)messageRecord); diff --git a/src/org/thoughtcrime/securesms/SingleContactSelectionActivity.java b/src/org/thoughtcrime/securesms/SingleContactSelectionActivity.java index 92c3c0dbc7..4d146e6a43 100644 --- a/src/org/thoughtcrime/securesms/SingleContactSelectionActivity.java +++ b/src/org/thoughtcrime/securesms/SingleContactSelectionActivity.java @@ -17,27 +17,18 @@ package org.thoughtcrime.securesms; import android.content.Intent; -import android.database.Cursor; import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; import android.util.Log; -import android.widget.CursorAdapter; -import android.widget.EditText; -import android.widget.Filter; -import android.widget.FilterQueryProvider; import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; -import org.thoughtcrime.securesms.contacts.ContactAccessor; +import org.thoughtcrime.securesms.components.SingleRecipientPanel; +import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.util.ActionBarUtil; import org.thoughtcrime.securesms.util.DynamicTheme; import java.util.ArrayList; -import java.util.List; import static org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData; @@ -76,31 +67,20 @@ public class SingleContactSelectionActivity extends PassphraseRequiredSherlockFr } }); - - EditText filter = (EditText)findViewById(R.id.filter); - filter.addTextChangedListener(new TextWatcher() { + SingleRecipientPanel recipientsPanel = (SingleRecipientPanel) findViewById(R.id.recipients); + recipientsPanel.setPanelChangeListener(new SingleRecipientPanel.RecipientsPanelChangedListener() { @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { - - } - - @Override - public void afterTextChanged(Editable editable) { - final CursorAdapter adapter = (CursorAdapter)listFragment.getListView().getAdapter(); - Log.i(TAG, "new text change: " + editable); - Filter filter = adapter.getFilter(); - - if (filter != null) { filter.filter(editable.toString()); adapter.notifyDataSetChanged(); } - else { Log.w(TAG, "filter was null, bad time."); } - - + public void onRecipientsPanelUpdate(Recipients recipients) { + Log.i(TAG, "onRecipientsPanelUpdate received."); + if (recipients != null) { + Intent resultIntent = getIntent(); + resultIntent.putExtra("recipients", recipients); + setResult(RESULT_OK, resultIntent); + finish(); + } } }); + } @Override diff --git a/src/org/thoughtcrime/securesms/SingleContactSelectionListFragment.java b/src/org/thoughtcrime/securesms/SingleContactSelectionListFragment.java index 93e2b8f82f..765b71e9cd 100644 --- a/src/org/thoughtcrime/securesms/SingleContactSelectionListFragment.java +++ b/src/org/thoughtcrime/securesms/SingleContactSelectionListFragment.java @@ -22,35 +22,25 @@ import android.content.Context; import android.content.DialogInterface; import android.database.Cursor; import android.database.MergeCursor; -import android.net.Uri; import android.os.Bundle; -import android.provider.ContactsContract; import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.CheckBox; import android.widget.CursorAdapter; -import android.widget.FilterQueryProvider; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.TextView; import com.actionbarsherlock.app.SherlockListFragment; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuInflater; -import com.actionbarsherlock.view.MenuItem; import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData; import org.thoughtcrime.securesms.contacts.ContactAccessor.NumberData; import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; /** * Activity for selecting a list of contacts. Displayed inside @@ -110,18 +100,6 @@ public class SingleContactSelectionListFragment extends SherlockListFragment private void initializeCursor() { final ContactSelectionListAdapter listAdapter = new ContactSelectionListAdapter(getActivity(), null); - listAdapter.setFilterQueryProvider(new FilterQueryProvider() { - @Override - public Cursor runQuery(CharSequence charSequence) { - final Uri uri = ContactsContract.Contacts.CONTENT_URI; - final String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + " = 1" + - " AND " + ContactsContract.Contacts.DISPLAY_NAME + " like ?"; - - final Cursor filteredCursor = getActivity().getContentResolver().query(uri, null, selection, new String[]{"%"+charSequence.toString()+"%"}, - ContactsContract.Contacts.DISPLAY_NAME + " ASC"); - return filteredCursor; - } - }); setListAdapter(listAdapter); this.getLoaderManager().initLoader(0, null, this); } @@ -200,7 +178,7 @@ public class SingleContactSelectionListFragment extends SherlockListFragment this.contactData = pushContactData.contactData; this.pushSupport = pushContactData.pushSupport; - /*if (!pushSupport) { + if (!pushSupport) { this.name.setTextColor(0xa0000000); this.number.setTextColor(0xa0000000); this.pushLabel.setBackgroundColor(0x99000000); @@ -208,7 +186,7 @@ public class SingleContactSelectionListFragment extends SherlockListFragment this.name.setTextColor(0xff000000); this.number.setTextColor(0xff000000); this.pushLabel.setBackgroundColor(0xff64a926); - }*/ + } this.name.setText(contactData.name); @@ -284,7 +262,8 @@ public class SingleContactSelectionListFragment extends SherlockListFragment @Override public void onLoadFinished(Loader arg0, Cursor cursor) { - ((CursorAdapter) getListAdapter()).changeCursor(cursor); + Cursor pushCursor = ContactAccessor.getInstance().getCursorForContactsWithPush(getActivity()); + ((CursorAdapter) getListAdapter()).changeCursor(new MergeCursor(new Cursor[]{pushCursor,cursor})); } @Override diff --git a/src/org/thoughtcrime/securesms/components/PushRecipientsPanel.java b/src/org/thoughtcrime/securesms/components/PushRecipientsPanel.java index 20fd4af6e7..af3ac2cf18 100644 --- a/src/org/thoughtcrime/securesms/components/PushRecipientsPanel.java +++ b/src/org/thoughtcrime/securesms/components/PushRecipientsPanel.java @@ -74,7 +74,6 @@ public class PushRecipientsPanel extends RelativeLayout { } public void addRecipient(String name, String number) { - Log.i(TAG, "addRecipient for " + name + "/" + number); if (name != null) recipientsText.append(name + "< " + number + ">, "); else recipientsText.append(number + ", "); } diff --git a/src/org/thoughtcrime/securesms/components/SingleRecipientPanel.java b/src/org/thoughtcrime/securesms/components/SingleRecipientPanel.java new file mode 100644 index 0000000000..2a1fb0081c --- /dev/null +++ b/src/org/thoughtcrime/securesms/components/SingleRecipientPanel.java @@ -0,0 +1,167 @@ +/** + * Copyright (C) 2011 Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.thoughtcrime.securesms.components; + +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.AdapterView; +import android.widget.RelativeLayout; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.contacts.ContactAccessor; +import org.thoughtcrime.securesms.contacts.RecipientsAdapter; +import org.thoughtcrime.securesms.contacts.RecipientsEditor; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientFactory; +import org.thoughtcrime.securesms.recipients.RecipientFormattingException; +import org.thoughtcrime.securesms.recipients.Recipients; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + * Panel component combining both an editable field with a button for + * a list-based contact selector. + * + * @author Moxie Marlinspike + */ +public class SingleRecipientPanel extends RelativeLayout { + private final String TAG = "SingleRecipientsPanel"; + private RecipientsPanelChangedListener panelChangeListener; + + private RecipientsEditor recipientsText; + private View panel; + + private static final int RECIPIENTS_MAX_LENGTH = 312; + + public SingleRecipientPanel(Context context) { + super(context); + initialize(); + } + + public SingleRecipientPanel(Context context, AttributeSet attrs) { + super(context, attrs); + initialize(); + } + + public SingleRecipientPanel(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initialize(); + } + + public void addRecipient(String name, String number) { + Log.i(TAG, "addRecipient for " + name + "/" + number); + if (name != null) recipientsText.append(name + "< " + number + ">, "); + else recipientsText.append(number + ", "); + } + + public void addRecipients(Recipients recipients) { + List recipientList = recipients.getRecipientsList(); + Iterator iterator = recipientList.iterator(); + + while (iterator.hasNext()) { + Recipient recipient = iterator.next(); + addRecipient(recipient.getName(), recipient.getNumber()); + } + } + + public void addContacts(List contacts) { + for (ContactAccessor.ContactData contact : contacts) { + for (ContactAccessor.NumberData number : contact.numbers) { + addRecipient(contact.name, number.number); + } + } + } + + public Recipients getRecipients() throws RecipientFormattingException { + String rawText = recipientsText.getText().toString(); + Recipients recipients = RecipientFactory.getRecipientsFromString(getContext(), rawText, false); + + if (recipients.isEmpty()) + throw new RecipientFormattingException("Recipient List Is Empty!"); + + return recipients; + } + + public void disable() { + recipientsText.setText(""); + panel.setVisibility(View.GONE); + } + + public void setPanelChangeListener(RecipientsPanelChangedListener panelChangeListener) { + this.panelChangeListener = panelChangeListener; + } + + private void initialize() { + LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.single_recipient_panel, this, true); + + panel = findViewById(R.id.recipients_panel); + initRecipientsEditor(); + } + + private void initRecipientsEditor() { + Recipients recipients; + recipientsText = (RecipientsEditor)findViewById(R.id.recipients_text); + + try { + recipients = getRecipients(); + } catch (RecipientFormattingException e) { + recipients = new Recipients( new LinkedList() ); + } + + recipientsText.setAdapter(new RecipientsAdapter(this.getContext())); + recipientsText.populate(recipients); + + recipientsText.setOnFocusChangeListener(new FocusChangedListener()); + recipientsText.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView adapterView, View view, int i, long l) { + if (panelChangeListener != null) { + try { + panelChangeListener.onRecipientsPanelUpdate(getRecipients()); + } catch (RecipientFormattingException rfe) { + panelChangeListener.onRecipientsPanelUpdate(null); + } + } + recipientsText.setText(""); + } + }); + } + + private class FocusChangedListener implements OnFocusChangeListener { + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus && (panelChangeListener != null)) { + try { + panelChangeListener.onRecipientsPanelUpdate(getRecipients()); + } catch (RecipientFormattingException rfe) { + panelChangeListener.onRecipientsPanelUpdate(null); + } + } + } + } + + public interface RecipientsPanelChangedListener { + public void onRecipientsPanelUpdate(Recipients recipients); + } + +} \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java b/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java index 7e19aa5c21..4930b135ea 100644 --- a/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java +++ b/src/org/thoughtcrime/securesms/contacts/ContactAccessor.java @@ -102,7 +102,6 @@ public class ContactAccessor { try { if (lookupCursor != null && lookupCursor.moveToFirst()) { cursor.addRow(new Object[]{lookupCursor.getLong(0), lookupCursor.getString(1), 1}); - Log.w("poop", "Adding matrix row for " + lookupCursor.getLong(0) + " : " + lookupCursor.getString(1)); } } finally { if (lookupCursor != null) @@ -414,14 +413,13 @@ public class ContactAccessor { if (RecipientsAdapter.usefulAsDigits(cons)) { phone = PhoneNumberUtils.convertKeypadLettersToDigits(cons); - if (phone.equals(cons)) { + if (phone.equals(cons) && !PhoneNumberUtils.isWellFormedSmsAddress(phone)) { phone = ""; } else { phone = phone.trim(); } } } - Uri uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(cons)); String selection = String.format("%s=%s OR %s=%s OR %s=%s", Phone.TYPE, diff --git a/src/org/thoughtcrime/securesms/contacts/RecipientsEditor.java b/src/org/thoughtcrime/securesms/contacts/RecipientsEditor.java index 721db41ef5..f18a36818f 100644 --- a/src/org/thoughtcrime/securesms/contacts/RecipientsEditor.java +++ b/src/org/thoughtcrime/securesms/contacts/RecipientsEditor.java @@ -117,7 +117,6 @@ public class RecipientsEditor extends MultiAutoCompleteTextView { int len = getText().length(); return end == len; - } public int getRecipientCount() { @@ -196,19 +195,19 @@ public class RecipientsEditor extends MultiAutoCompleteTextView { }*/ public static CharSequence contactToToken(Recipient c) { - String name = c.getName(); - String number = c.getNumber(); - SpannableString s = new SpannableString(RecipientsFormatter.formatNameAndNumber(name, number)); - int len = s.length(); + String name = c.getName(); + String number = c.getNumber(); + SpannableString s = new SpannableString(RecipientsFormatter.formatNameAndNumber(name, number)); + int len = s.length(); - if (len == 0) { - return s; - } + if (len == 0) { + return s; + } - s.setSpan(new Annotation("number", c.getNumber()), 0, len, + s.setSpan(new Annotation("number", c.getNumber()), 0, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - return s; + return s; } public void populate(Recipients list) { @@ -229,7 +228,6 @@ public class RecipientsEditor extends MultiAutoCompleteTextView { x -= getCompoundPaddingLeft(); y -= getExtendedPaddingTop(); - x += getScrollX(); y += getScrollY();