Merge branch 'development' of https://github.com/siacs/Conversations into development

kruks23 created

Change summary

AndroidManifest.xml                                            |   6 
libs/android-support-v13.jar                                   |   0 
libs/android-support-v4.jar                                    |   0 
res/drawable-hdpi/tab_selected_conversations.9.png             |   0 
res/drawable-hdpi/tab_selected_focused_conversations.9.png     |   0 
res/drawable-hdpi/tab_selected_pressed_conversations.9.png     |   0 
res/drawable-hdpi/tab_unselected_conversations.9.png           |   0 
res/drawable-hdpi/tab_unselected_focused_conversations.9.png   |   0 
res/drawable-hdpi/tab_unselected_pressed_conversations.9.png   |   0 
res/drawable-mdpi/tab_selected_conversations.9.png             |   0 
res/drawable-mdpi/tab_selected_focused_conversations.9.png     |   0 
res/drawable-mdpi/tab_selected_pressed_conversations.9.png     |   0 
res/drawable-mdpi/tab_unselected_conversations.9.png           |   0 
res/drawable-mdpi/tab_unselected_focused_conversations.9.png   |   0 
res/drawable-mdpi/tab_unselected_pressed_conversations.9.png   |   0 
res/drawable-xhdpi/tab_selected_conversations.9.png            |   0 
res/drawable-xhdpi/tab_selected_focused_conversations.9.png    |   0 
res/drawable-xhdpi/tab_selected_pressed_conversations.9.png    |   0 
res/drawable-xhdpi/tab_unselected_conversations.9.png          |   0 
res/drawable-xhdpi/tab_unselected_focused_conversations.9.png  |   0 
res/drawable-xhdpi/tab_unselected_pressed_conversations.9.png  |   0 
res/drawable-xxhdpi/tab_selected_conversations.9.png           |   0 
res/drawable-xxhdpi/tab_selected_focused_conversations.9.png   |   0 
res/drawable-xxhdpi/tab_selected_pressed_conversations.9.png   |   0 
res/drawable-xxhdpi/tab_unselected_conversations.9.png         |   0 
res/drawable-xxhdpi/tab_unselected_focused_conversations.9.png |   0 
res/drawable-xxhdpi/tab_unselected_pressed_conversations.9.png |   0 
res/drawable/actionbar_tab_indicator.xml                       |  19 
res/drawable/snackbar.xml                                      |   6 
res/layout/activity_start_conversation.xml                     |   8 
res/layout/create_contact_dialog.xml                           |  37 
res/layout/fragment_conversation.xml                           | 107 
res/menu/contact_context.xml                                   |  14 
res/menu/start_conversation.xml                                |  33 
res/values/strings.xml                                         |  15 
res/values/themes.xml                                          |  27 
src/eu/siacs/conversations/entities/Contact.java               |  21 
src/eu/siacs/conversations/entities/ListItem.java              |   7 
src/eu/siacs/conversations/services/XmppConnectionService.java |   3 
src/eu/siacs/conversations/ui/ConversationActivity.java        |   9 
src/eu/siacs/conversations/ui/StartConversation.java           | 359 ++++
src/eu/siacs/conversations/ui/XmppActivity.java                |   8 
src/eu/siacs/conversations/utils/UIHelper.java                 |   4 
43 files changed, 600 insertions(+), 83 deletions(-)

Detailed changes

AndroidManifest.xml 🔗

@@ -48,6 +48,12 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity 
+            android:name="eu.siacs.conversations.ui.StartConversation"
+            android:label="@string/title_activity_start_conversation"
+            android:parentActivityName="eu.siacs.conversations.ui.ConversationActivity"
+            android:logo="@drawable/ic_activity"
+            ></activity>
         <activity
             android:name="eu.siacs.conversations.ui.SettingsActivity"
             android:label="@string/title_activity_settings"

res/drawable/actionbar_tab_indicator.xml 🔗

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- Non focused states -->
+    <item android:state_focused="false" android:state_selected="false" android:state_pressed="false" android:drawable="@android:color/transparent" />
+    <item android:state_focused="false" android:state_selected="true"  android:state_pressed="false" android:drawable="@drawable/tab_selected_conversations" />
+
+    <!-- Focused states -->
+    <item android:state_focused="true" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/tab_unselected_focused_conversations" />
+    <item android:state_focused="true" android:state_selected="true"  android:state_pressed="false" android:drawable="@drawable/tab_selected_focused_conversations" />
+
+    <!-- Pressed -->
+    <!--    Non focused states -->
+    <item android:state_focused="false" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/tab_unselected_pressed_conversations" />
+    <item android:state_focused="false" android:state_selected="true"  android:state_pressed="true" android:drawable="@drawable/tab_selected_pressed_conversations" />
+
+    <!--    Focused states -->
+    <item android:state_focused="true" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/tab_unselected_pressed_conversations" />
+    <item android:state_focused="true" android:state_selected="true"  android:state_pressed="true" android:drawable="@drawable/tab_selected_pressed_conversations" />
+    </selector>

res/drawable/snackbar.xml 🔗

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@color/darkbackground"/>
+    <corners android:radius="8dip"/>
+    <padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
+</shape>

res/layout/activity_start_conversation.xml 🔗

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/start_conversation_view_pager"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/primarybackground">
+
+</android.support.v4.view.ViewPager>

res/layout/create_contact_dialog.xml 🔗

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:padding="16dp">
+
+     <TextView
+        android:id="@+id/your_account"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="14sp"
+        android:textColor="@color/primarytext"
+        android:text="@string/your_account" />
+    <Spinner
+        android:id="@+id/account"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content" />
+
+     <TextView
+        android:id="@+id/jabber_id"
+        android:paddingTop="8dp"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="14sp"
+        android:textColor="@color/primarytext"
+        android:text="@string/account_settings_jabber_id" />
+    
+    <EditText 
+        android:id="@+id/jid"
+        android:paddingTop="8dp"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:inputType="textEmailAddress"
+        android:hint="@string/account_settings_example_jabber_id"
+        />
+</LinearLayout>

res/layout/fragment_conversation.xml 🔗

@@ -5,13 +5,31 @@
     android:layout_height="match_parent"
     android:background="@color/secondarybackground" >
 
-    <RelativeLayout
-        android:id="@+id/textsend"
+   
+
+    <ListView
+        android:id="@+id/messages_view"
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true"
+        android:layout_above="@+id/snackbar"
         android:layout_alignParentLeft="true"
-        android:background="@color/primarybackground" >
+        android:layout_alignParentTop="true"
+        android:background="@color/secondarybackground"
+        android:divider="@null"
+        android:dividerHeight="0dp"
+        android:listSelector="@android:color/transparent"
+        android:stackFromBottom="true"
+        android:transcriptMode="normal"
+        tools:listitem="@layout/message_sent" >
+    </ListView>
+
+         <RelativeLayout
+             android:id="@+id/textsend"
+             android:layout_width="fill_parent"
+             android:layout_height="wrap_content"
+             android:layout_alignParentBottom="true"
+             android:layout_alignParentLeft="true"
+             android:background="@color/primarybackground" >
 
         <EditText
             android:id="@+id/textinput"
@@ -43,53 +61,42 @@
             android:src="@drawable/ic_action_send_now" />
     </RelativeLayout>
 
-    <ListView
-        android:id="@+id/messages_view"
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:layout_above="@+id/textsend"
-        android:layout_alignParentLeft="true"
-        android:layout_below="@+id/snackbar"
-        android:background="@color/secondarybackground"
-        android:divider="@null"
-        android:dividerHeight="0dp"
-        android:listSelector="@android:color/transparent"
-        android:stackFromBottom="true"
-        android:transcriptMode="normal"
-        tools:listitem="@layout/message_sent" >
-    </ListView>
+         <RelativeLayout
+             android:id="@+id/snackbar"
+             android:layout_width="fill_parent"
+             android:layout_height="wrap_content"
+             android:layout_above="@+id/textsend"
+             android:background="@drawable/snackbar"
+             android:minHeight="48dp"
+             android:layout_marginLeft="8dp"
+             android:layout_marginRight="8dp"
+             android:layout_marginBottom="4dp"
+             android:visibility="gone" >
 
-    <RelativeLayout
-        android:id="@+id/snackbar"
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:minHeight="48dp"
-        android:background="@color/darkbackground"
-        android:visibility="gone">
+             <TextView
+                 android:id="@+id/snackbar_message"
+                 android:layout_width="wrap_content"
+                 android:layout_height="wrap_content"
+                 android:layout_alignParentLeft="true"
+                 android:layout_centerVertical="true"
+                 android:paddingLeft="24dp"
+                 android:textColor="@color/ondarktext"
+                 android:textSize="14sp" />
 
-        <TextView
-            android:id="@+id/snackbar_message"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignParentLeft="true"
-            android:layout_centerVertical="true"
-            android:textSize="14sp"
-            android:textColor="@color/ondarktext"
-            android:paddingLeft="24dp"/>
-        <TextView
-            android:id="@+id/snackbar_action"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignParentRight="true"
-            android:layout_centerVertical="true"
-            android:textSize="14sp"
-            android:textColor="@color/ondarktext"
-            android:textStyle="bold"
-            android:textAllCaps="true"
-            android:paddingLeft="24dp"
-            android:paddingRight="24dp"
-            android:paddingTop="18dp"
-            android:paddingBottom="18dp"/>
-        </RelativeLayout>
+             <TextView
+                 android:id="@+id/snackbar_action"
+                 android:layout_width="wrap_content"
+                 android:layout_height="wrap_content"
+                 android:layout_alignParentRight="true"
+                 android:layout_centerVertical="true"
+                 android:paddingBottom="16dp"
+                 android:paddingLeft="24dp"
+                 android:paddingRight="24dp"
+                 android:paddingTop="16dp"
+                 android:textAllCaps="true"
+                 android:textColor="@color/ondarktext"
+                 android:textSize="14sp"
+                 android:textStyle="bold" />
+         </RelativeLayout>
 
 </RelativeLayout>

res/menu/contact_context.xml 🔗

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <item
+        android:id="@+id/context_start_conversation"
+        android:title="@string/start_conversation"/>
+    <item
+        android:id="@+id/context_contact_details"
+        android:title="@string/view_contact_details"/>
+    <item
+        android:id="@+id/context_delete_contact"
+        android:title="@string/delete_contact"/>
+
+</menu>

res/menu/start_conversation.xml 🔗

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <item
+        android:id="@+id/action_search"
+        android:actionViewClass="android.widget.SearchView"
+        android:icon="@drawable/ic_action_search"
+        android:showAsAction="collapseActionView|always"
+        android:title="@string/search"/>
+    
+    <item
+        android:id="@+id/action_create_contact"
+        android:icon="@drawable/ic_action_add_person"
+        android:showAsAction="always"
+        android:title="@string/create_contact"/>
+    <item
+        android:id="@+id/action_create_conference"
+        android:icon="@drawable/ic_action_add_group"
+        android:showAsAction="always"
+        android:title="@string/create_conference"/>
+    
+    <item
+        android:id="@+id/action_accounts"
+        android:orderInCategory="90"
+        android:showAsAction="never"
+        android:title="@string/action_accounts"/>
+    <item
+        android:id="@+id/action_settings"
+        android:orderInCategory="100"
+        android:showAsAction="never"
+        android:title="@string/action_settings"/>
+
+</menu>

res/values/strings.xml 🔗

@@ -21,6 +21,7 @@
     <string name="title_activity_contact_details">Contact Details</string>
     <string name="title_activity_conversations">Conversations</string>
     <string name="title_activity_sharewith">Share with Conversation</string>
+    <string name="title_activity_start_conversation">Start Conversation</string>
     <string name="just_now">just now</string>
     <string name="minute_ago">1 min ago</string>
     <string name="minutes_ago">%d mins ago</string>
@@ -200,10 +201,10 @@
     <string name="mgmt_account_account_offline">Account is offline</string>
     <string name="attach_record_voice">Record voice</string>
     <string name="account_settings">Account Settings</string>
-    <string name="account_settings_jabber_id">Jabber ID:</string>
-    <string name="account_settings_password">Password:</string>
+    <string name="account_settings_jabber_id">Jabber ID</string>
+    <string name="account_settings_password">Password</string>
     <string name="account_settings_example_jabber_id">username@example.com</string>
-    <string name="account_settings_confirm_password">Confirm password:</string>
+    <string name="account_settings_confirm_password">Confirm password</string>
     <string name="password">Password</string>
     <string name="confirm_password">Confirm password</string>
     <string name="passwords_do_not_match">Passwords do not match</string>
@@ -254,4 +255,12 @@
     <string name="otr_fingerprint">OTR fingerprint</string>
     <string name="verify">Verify</string>
     <string name="decrypt">Decrypt</string>
+    <string name="conferences">Conferences</string>
+    <string name="search">Search</string>
+    <string name="create_contact">Create Contact</string>
+    <string name="create_conference">Create Conference</string>
+    <string name="delete_contact">Delete Contact</string>
+    <string name="view_contact_details">View contact details</string>
+    <string name="create">Create</string>
+    <string name="contact_already_exists">The contact already exists</string>
 </resources>

res/values/themes.xml 🔗

@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <style name="ConversationsTheme"
-           parent="@android:style/Theme.Holo.Light.DarkActionBar">
+
+    <style name="ConversationsTheme" parent="@android:style/Theme.Holo.Light.DarkActionBar">
         <item name="android:actionBarStyle">@style/ConversationsActionBar</item>
         <item name="android:actionBarWidgetTheme">@style/ConversationsActionBarWidget</item>
+        <item name="android:actionBarTabStyle">@style/ConversationsActionBarTabs</item>
     </style>
 
-    <style name="ConversationsActionBar"
-           parent="@android:style/Widget.Holo.Light.ActionBar.Solid.Inverse">
+    <style name="ConversationsActionBar" parent="@android:style/Widget.Holo.Light.ActionBar.Solid.Inverse">
         <item name="android:background">#259b24</item>
+        <item name="android:backgroundStacked">#0a7e07</item>
         <item name="android:displayOptions">showHome|homeAsUp|showTitle</item>
-	<item name="android:icon">@android:color/transparent</item>
+        <item name="android:icon">@android:color/transparent</item>
+    </style>
+
+    <style name="ConversationsActionBarWidget" parent="android:Theme.Holo.Light">
+        <item name="android:popupMenuStyle">@android:style/Widget.Holo.Light.PopupMenu</item>
+        <item name="android:dropDownListViewStyle">@android:style/Widget.Holo.Light.ListView.DropDown</item>
     </style>
     
-    
-    <style name="ConversationsActionBarWidget"
-    parent="android:Theme.Holo.Light">
-    <item name="android:popupMenuStyle">@android:style/Widget.Holo.Light.PopupMenu</item>
-    <item name="android:dropDownListViewStyle">@android:style/Widget.Holo.Light.ListView.DropDown</item>
-</style>
-</resources>
+    <style name="ConversationsActionBarTabs" parent="@android:style/Widget.Holo.ActionBar.TabView">
+       <item name="android:background">@drawable/actionbar_tab_indicator</item>
+    </style>
 
+</resources>

src/eu/siacs/conversations/entities/Contact.java 🔗

@@ -12,7 +12,7 @@ import eu.siacs.conversations.xml.Element;
 import android.content.ContentValues;
 import android.database.Cursor;
 
-public class Contact {
+public class Contact implements ListItem {
 	public static final String TABLENAME = "contacts";
 
 	public static final String SYSTEMNAME = "systemname";
@@ -37,7 +37,7 @@ public class Contact {
 	protected Account account;
 
 	protected boolean inRoster = true;
-	
+
 	public Lastseen lastseen = new Lastseen();
 
 	public Contact(String account, String systemName, String serverName,
@@ -83,8 +83,10 @@ public class Contact {
 	}
 
 	public boolean match(String needle) {
-		return (jid.toLowerCase().contains(needle.toLowerCase()) || (getDisplayName()
-				.toLowerCase().contains(needle.toLowerCase())));
+		return needle == null
+				|| jid.contains(needle.toLowerCase())
+				|| getDisplayName().toLowerCase()
+						.contains(needle.toLowerCase());
 	}
 
 	public ContentValues getContentValues() {
@@ -142,8 +144,8 @@ public class Contact {
 						|| domainParts[0].equals("room")
 						|| domainParts[0].equals("muc")
 						|| domainParts[0].equals("chat")
-						|| domainParts[0].equals("sala")
-						|| domainParts[0].equals("salas"));
+						|| domainParts[0].equals("sala") || domainParts[0]
+							.equals("salas"));
 			}
 		}
 	}
@@ -308,9 +310,14 @@ public class Contact {
 		public static final int DIRTY_PUSH = 6;
 		public static final int DIRTY_DELETE = 7;
 	}
-	
+
 	public class Lastseen {
 		public long time = 0;
 		public String presence = null;
 	}
+
+	@Override
+	public int compareTo(ListItem another) {
+		return this.getDisplayName().compareToIgnoreCase(another.getDisplayName());
+	}
 }

src/eu/siacs/conversations/entities/ListItem.java 🔗

@@ -0,0 +1,7 @@
+package eu.siacs.conversations.entities;
+
+public interface ListItem extends Comparable<ListItem> {
+	public String getDisplayName();
+	public String getJid();
+	public String getProfilePhoto();
+}

src/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -1282,6 +1282,7 @@ public class XmppConnectionService extends Service {
 
 	public void deleteContactOnServer(Contact contact) {
 		contact.resetOption(Contact.Options.DIRTY_PUSH);
+		contact.setOption(Contact.Options.DIRTY_DELETE);
 		Account account = contact.getAccount();
 		if (account.getStatus() == Account.STATUS_ONLINE) {
 			IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
@@ -1290,8 +1291,6 @@ public class XmppConnectionService extends Service {
 			item.setAttribute("subscription", "remove");
 			account.getXmppConnection().sendIqPacket(iq, null);
 			contact.resetOption(Contact.Options.DIRTY_DELETE);
-		} else {
-			contact.setOption(Contact.Options.DIRTY_DELETE);
 		}
 	}
 

src/eu/siacs/conversations/ui/ConversationActivity.java 🔗

@@ -488,7 +488,7 @@ public class ConversationActivity extends XmppActivity {
 			attachFilePopup.show();
 			break;
 		case R.id.action_add:
-			startActivity(new Intent(this, ContactsActivity.class));
+			startActivity(new Intent(this, StartConversation.class));
 			break;
 		case R.id.action_archive:
 			this.endConversation(getSelectedConversation());
@@ -496,12 +496,7 @@ public class ConversationActivity extends XmppActivity {
 		case R.id.action_contact_details:
 			Contact contact = this.getSelectedConversation().getContact();
 			if (contact.showInRoster()) {
-				Intent intent = new Intent(this, ContactDetailsActivity.class);
-				intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
-				intent.putExtra("account", this.getSelectedConversation()
-						.getAccount().getJid());
-				intent.putExtra("contact", contact.getJid());
-				startActivity(intent);
+				switchToContactDetails(contact);
 			} else {
 				showAddToRosterDialog(getSelectedConversation());
 			}

src/eu/siacs/conversations/ui/StartConversation.java 🔗

@@ -0,0 +1,359 @@
+package eu.siacs.conversations.ui;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import android.app.ActionBar;
+import android.app.ActionBar.Tab;
+import android.app.ActionBar.TabListener;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.app.ListFragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.database.DataSetObserver;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.support.v13.app.FragmentPagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SearchView;
+import android.widget.SpinnerAdapter;
+import android.widget.SearchView.OnQueryTextListener;
+import android.widget.Spinner;
+import android.widget.TextView;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Contact;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.ListItem;
+import eu.siacs.conversations.utils.UIHelper;
+import eu.siacs.conversations.utils.Validator;
+
+public class StartConversation extends XmppActivity {
+
+	private Tab mContactsTab;
+	private Tab mConferencesTab;
+	private ViewPager mViewPager;
+	private SearchView mSearchView;
+
+	private MyListFragment mContactsListFragment = new MyListFragment();
+	private List<ListItem> contacts = new ArrayList<ListItem>();
+	private ArrayAdapter<ListItem> mContactsAdapter;
+
+	private MyListFragment mConferenceListFragment = new MyListFragment();
+	private List<ListItem> conferences = new ArrayList<ListItem>();
+	private ArrayAdapter<ListItem> mConferenceAdapter;
+	
+	private List<String> mActivatedAccounts = new ArrayList<String>();
+
+	private TabListener mTabListener = new TabListener() {
+
+		@Override
+		public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+			return;
+		}
+
+		@Override
+		public void onTabSelected(Tab tab, FragmentTransaction ft) {
+			mViewPager.setCurrentItem(tab.getPosition());
+			onTabChanged();
+		}
+
+		@Override
+		public void onTabReselected(Tab tab, FragmentTransaction ft) {
+			return;
+		}
+	};
+
+	private ViewPager.SimpleOnPageChangeListener mOnPageChangeListener = new ViewPager.SimpleOnPageChangeListener() {
+		@Override
+		public void onPageSelected(int position) {
+			getActionBar().setSelectedNavigationItem(position);
+			onTabChanged();
+		}
+	};
+	private OnQueryTextListener mOnQueryTextListener = new OnQueryTextListener() {
+
+		@Override
+		public boolean onQueryTextSubmit(String query) {
+			return true;
+		}
+
+		@Override
+		public boolean onQueryTextChange(String newText) {
+			filterContacts(newText);
+			return true;
+		}
+	};
+
+	@Override
+	public void onCreate(Bundle savedInstanceState) {
+		super.onCreate(savedInstanceState);
+		setContentView(R.layout.activity_start_conversation);
+		mViewPager = (ViewPager) findViewById(R.id.start_conversation_view_pager);
+		ActionBar actionBar = getActionBar();
+		actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+
+		mContactsTab = actionBar.newTab().setText(R.string.contacts)
+				.setTabListener(mTabListener);
+		mConferencesTab = actionBar.newTab().setText(R.string.conferences)
+				.setTabListener(mTabListener);
+		actionBar.addTab(mContactsTab);
+		actionBar.addTab(mConferencesTab);
+
+		mViewPager.setOnPageChangeListener(mOnPageChangeListener);
+		mViewPager.setAdapter(new FragmentPagerAdapter(getFragmentManager()) {
+
+			@Override
+			public int getCount() {
+				return 2;
+			}
+
+			@Override
+			public Fragment getItem(int position) {
+				if (position == 0) {
+					return mContactsListFragment;
+				} else {
+					return mConferenceListFragment;
+				}
+			}
+		});
+
+		mConferenceAdapter = new ListItemAdapter(conferences);
+		mConferenceListFragment.setListAdapter(mConferenceAdapter);
+
+		mContactsAdapter = new ListItemAdapter(contacts);
+		mContactsListFragment.setListAdapter(mContactsAdapter);
+		mContactsListFragment
+				.setOnListItemClickListener(new OnItemClickListener() {
+
+					@Override
+					public void onItemClick(AdapterView<?> arg0, View arg1,
+							int position, long arg3) {
+						openConversationForContact(position);
+					}
+				});
+
+	}
+
+	protected void openConversationForContact(int position) {
+		Contact contact = (Contact) contacts.get(position);
+		Conversation conversation = xmppConnectionService
+				.findOrCreateConversation(contact.getAccount(),
+						contact.getJid(), false);
+		switchToConversation(conversation, null, false);
+	}
+	
+	protected void openDetailsForContact(int position) {
+		Contact contact = (Contact) contacts.get(position);
+		switchToContactDetails(contact);
+	}
+	
+	protected void deleteContact(int position) {
+		Contact contact = (Contact) contacts.get(position);
+		xmppConnectionService.deleteContactOnServer(contact);
+		filterContacts(null);
+	}
+	
+	protected void showCreateContactDialog() {
+		AlertDialog.Builder builder = new AlertDialog.Builder(this);
+		builder.setTitle(R.string.create_contact);
+		View dialogView = getLayoutInflater().inflate(R.layout.create_contact_dialog, null);
+		final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account);
+		final EditText jid = (EditText) dialogView.findViewById(R.id.jid);
+		populateAccountSpinner(spinner);
+		builder.setView(dialogView);
+		builder.setNegativeButton(R.string.cancel, null);
+		builder.setPositiveButton(R.string.create, null);
+		final AlertDialog dialog = builder.create();
+		dialog.show();
+		dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
+			
+			@Override
+			public void onClick(View v) {
+				if (Validator.isValidJid(jid.getText().toString())) {
+					String accountJid = (String) spinner.getSelectedItem();
+					String contactJid = jid.getText().toString();
+					Account account = xmppConnectionService.findAccountByJid(accountJid);
+					Contact contact = account.getRoster().getContact(contactJid);
+					if (contact.showInRoster()) {
+						jid.setError(getString(R.string.contact_already_exists));
+					} else {
+						xmppConnectionService.createContact(contact);
+						switchToConversation(contact);
+						dialog.dismiss();
+					}
+				} else {
+					jid.setError(getString(R.string.invalid_jid));
+				}
+			}
+		});
+		
+	}
+	
+	protected void switchToConversation(Contact contact) {
+		Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false);
+		switchToConversation(conversation, null, false);
+	}
+	
+	private void populateAccountSpinner(Spinner spinner) {
+		ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, mActivatedAccounts);
+		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+		spinner.setAdapter(adapter);
+	}
+
+	@Override
+	public boolean onCreateOptionsMenu(Menu menu) {
+		getMenuInflater().inflate(R.menu.start_conversation, menu);
+		MenuItem menuCreateContact = (MenuItem) menu
+				.findItem(R.id.action_create_contact);
+		MenuItem menuCreateConference = (MenuItem) menu
+				.findItem(R.id.action_create_conference);
+		MenuItem menuSearch = (MenuItem) menu.findItem(R.id.action_search);
+		if (getActionBar().getSelectedNavigationIndex() == 0) {
+			menuCreateConference.setVisible(false);
+		} else {
+			menuCreateContact.setVisible(false);
+		}
+		mSearchView = (SearchView) menuSearch.getActionView();
+		int id = mSearchView.getContext().getResources()
+				.getIdentifier("android:id/search_src_text", null, null);
+		TextView textView = (TextView) mSearchView.findViewById(id);
+		textView.setTextColor(Color.WHITE);
+		mSearchView.setOnQueryTextListener(this.mOnQueryTextListener);
+		return true;
+	}
+
+	@Override
+	public boolean onOptionsItemSelected(MenuItem item) {
+		switch (item.getItemId()) {
+		case R.id.action_create_contact:
+			showCreateContactDialog();
+			break;
+		}
+		return super.onOptionsItemSelected(item);
+	}
+
+	@Override
+	void onBackendConnected() {
+		filterContacts(null);
+		this.mActivatedAccounts.clear();
+		for (Account account : xmppConnectionService.getAccounts()) {
+			if (account.getStatus() != Account.STATUS_DISABLED) {
+				this.mActivatedAccounts.add(account.getJid());
+			}
+		}
+	}
+
+	protected void filterContacts(String needle) {
+		this.contacts.clear();
+		for (Account account : xmppConnectionService.getAccounts()) {
+			if (account.getStatus() != Account.STATUS_DISABLED) {
+				for (Contact contact : account.getRoster().getContacts()) {
+					if (contact.showInRoster() && contact.match(needle)) {
+						this.contacts.add(contact);
+					}
+				}
+			}
+		}
+		Collections.sort(this.contacts);
+		mContactsAdapter.notifyDataSetChanged();
+	}
+
+	private void onTabChanged() {
+		invalidateOptionsMenu();
+	}
+
+	private class ListItemAdapter extends ArrayAdapter<ListItem> {
+
+		public ListItemAdapter(List<ListItem> objects) {
+			super(getApplicationContext(), 0, objects);
+		}
+
+		@Override
+		public View getView(int position, View view, ViewGroup parent) {
+			LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+			ListItem item = getItem(position);
+			if (view == null) {
+				view = (View) inflater.inflate(R.layout.contact, null);
+			}
+			TextView name = (TextView) view
+					.findViewById(R.id.contact_display_name);
+			TextView jid = (TextView) view.findViewById(R.id.contact_jid);
+			ImageView picture = (ImageView) view
+					.findViewById(R.id.contact_photo);
+
+			jid.setText(item.getJid());
+			name.setText(item.getDisplayName());
+			picture.setImageBitmap(UIHelper.getContactPicture(item, 48,
+					this.getContext(), false));
+			return view;
+		}
+
+	}
+
+	public static class MyListFragment extends ListFragment {
+		private AdapterView.OnItemClickListener mOnItemClickListener;
+		private int mContextPosition = -1;
+
+		@Override
+		public void onListItemClick(ListView l, View v, int position, long id) {
+			if (mOnItemClickListener != null) {
+				mOnItemClickListener.onItemClick(l, v, position, id);
+			}
+		}
+
+		public void setOnListItemClickListener(AdapterView.OnItemClickListener l) {
+			this.mOnItemClickListener = l;
+		}
+
+		@Override
+		public void onViewCreated(View view, Bundle savedInstanceState) {
+			super.onViewCreated(view, savedInstanceState);
+			registerForContextMenu(getListView());
+		}
+
+		@Override
+		public void onCreateContextMenu(ContextMenu menu, View v,
+				ContextMenuInfo menuInfo) {
+			super.onCreateContextMenu(menu, v, menuInfo);
+			getActivity().getMenuInflater().inflate(R.menu.contact_context,
+					menu);
+			AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
+			this.mContextPosition = acmi.position;
+		}
+
+		@Override
+		public boolean onContextItemSelected(MenuItem item) {
+			StartConversation activity = (StartConversation) getActivity();
+			switch(item.getItemId()) {
+			case R.id.context_start_conversation:
+				activity.openConversationForContact(mContextPosition);
+				break;
+			case R.id.context_contact_details:
+				activity.openDetailsForContact(mContextPosition);
+				break;
+			case R.id.context_delete_contact:
+				activity.deleteContact(mContextPosition);
+				break;
+			}
+			return true;
+		}
+	}
+}

src/eu/siacs/conversations/ui/XmppActivity.java 🔗

@@ -170,6 +170,14 @@ public abstract class XmppActivity extends Activity {
 		}
 		startActivity(viewConversationIntent);
 	}
+	
+	public void switchToContactDetails(Contact contact) {
+		Intent intent = new Intent(this, ContactDetailsActivity.class);
+		intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
+		intent.putExtra("account", contact.getAccount().getJid());
+		intent.putExtra("contact", contact.getJid());
+		startActivity(intent);
+	}
 
 	protected void announcePgp(Account account, final Conversation conversation) {
 		xmppConnectionService.getPgpEngine().generateSignature(account,

src/eu/siacs/conversations/utils/UIHelper.java 🔗

@@ -13,11 +13,11 @@ import eu.siacs.conversations.R;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.ListItem;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.entities.MucOptions.User;
 import eu.siacs.conversations.ui.ConversationActivity;
 import eu.siacs.conversations.ui.ManageAccountActivity;
-
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Notification;
@@ -239,7 +239,7 @@ public class UIHelper {
 		}
 	}
 
-	public static Bitmap getContactPicture(Contact contact, int dpSize,
+	public static Bitmap getContactPicture(ListItem contact, int dpSize,
 			Context context, boolean notification) {
 		String uri = contact.getProfilePhoto();
 		if (uri == null) {