From 35f8ab58f4b6f97f02031b056d2a5f6f993672cf Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 24 Jan 2014 02:04:05 +0100 Subject: inital commit --- src/de/gultsch/chat/Contact.java | 35 ++++ src/de/gultsch/chat/Conversation.java | 21 +++ src/de/gultsch/chat/ConversationCursor.java | 94 ++++++++++ src/de/gultsch/chat/ConversationList.java | 41 +++++ src/de/gultsch/chat/Message.java | 19 ++ .../chat/services/XmppConnectionService.java | 29 +++ src/de/gultsch/chat/ui/ConversationActivity.java | 161 +++++++++++++++++ src/de/gultsch/chat/ui/ConversationFragment.java | 18 ++ src/de/gultsch/chat/ui/ManageAccountActivity.java | 7 + .../gultsch/chat/ui/NewConversationActivity.java | 197 +++++++++++++++++++++ src/de/gultsch/chat/ui/SettingsActivity.java | 16 ++ src/de/gultsch/chat/ui/SettingsFragment.java | 15 ++ 12 files changed, 653 insertions(+) create mode 100644 src/de/gultsch/chat/Contact.java create mode 100644 src/de/gultsch/chat/Conversation.java create mode 100644 src/de/gultsch/chat/ConversationCursor.java create mode 100644 src/de/gultsch/chat/ConversationList.java create mode 100644 src/de/gultsch/chat/Message.java create mode 100644 src/de/gultsch/chat/services/XmppConnectionService.java create mode 100644 src/de/gultsch/chat/ui/ConversationActivity.java create mode 100644 src/de/gultsch/chat/ui/ConversationFragment.java create mode 100644 src/de/gultsch/chat/ui/ManageAccountActivity.java create mode 100644 src/de/gultsch/chat/ui/NewConversationActivity.java create mode 100644 src/de/gultsch/chat/ui/SettingsActivity.java create mode 100644 src/de/gultsch/chat/ui/SettingsFragment.java (limited to 'src') diff --git a/src/de/gultsch/chat/Contact.java b/src/de/gultsch/chat/Contact.java new file mode 100644 index 00000000..64204461 --- /dev/null +++ b/src/de/gultsch/chat/Contact.java @@ -0,0 +1,35 @@ +package de.gultsch.chat; + +import java.io.Serializable; + +import android.net.Uri; + +public class Contact implements Serializable { + private static final long serialVersionUID = -4570817093119419962L; + protected String display_name; + protected String jid; + protected String photo; + + public Contact(String display_name, String jid, String photo) { + this.display_name = display_name; + this.jid = jid; + this.photo = photo; + } + + public String getDisplayName() { + return this.display_name; + } + + public Uri getProfilePhoto() { + if (photo == null) return null; + return Uri.parse(photo); + } + + public String getJid() { + return this.jid; + } + + public boolean match(String needle) { + return (jid.toLowerCase().contains(needle.toLowerCase()) || (display_name.toLowerCase().contains(needle.toLowerCase()))); + } +} diff --git a/src/de/gultsch/chat/Conversation.java b/src/de/gultsch/chat/Conversation.java new file mode 100644 index 00000000..cec96a35 --- /dev/null +++ b/src/de/gultsch/chat/Conversation.java @@ -0,0 +1,21 @@ +package de.gultsch.chat; + +import java.util.ArrayList; + + +public class Conversation { + private String name; + private ArrayList msgs = new ArrayList(); + + public Conversation(String name) { + this.name = name; + } + + public ArrayList getLastMessages(int count, int offset) { + msgs.add(new Message("this is my last message")); + return msgs; + } + public String getName() { + return this.name; + } +} diff --git a/src/de/gultsch/chat/ConversationCursor.java b/src/de/gultsch/chat/ConversationCursor.java new file mode 100644 index 00000000..1ce431b4 --- /dev/null +++ b/src/de/gultsch/chat/ConversationCursor.java @@ -0,0 +1,94 @@ +package de.gultsch.chat; + + +import java.util.ArrayList; + + +import android.database.AbstractCursor; + +public class ConversationCursor extends AbstractCursor { + + + protected ConversationList conversations; + + public static final String NAME = "conversationname"; + public static final String LAST_MSG = "lastmsg"; + public static final String DATE = "date"; + public static final String ID = "_id"; + + public ConversationCursor(ConversationList list) { + super(); + this.conversations = list; + } + + public ArrayList getConversationOverview() { + return this.conversations; + } + + public void setConversationOverview(ConversationList list) { + this.conversations = list; + } + + @Override + public String[] getColumnNames() { + return new String[]{ID,NAME,LAST_MSG,DATE}; + } + + @Override + public int getCount() { + return conversations.size(); + } + + @Override + public double getDouble(int column) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public float getFloat(int column) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getInt(int column) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public long getLong(int column) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public short getShort(int column) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public String getString(int column) { + Conversation conversation = conversations.get(getPosition()); + Message lastMessage = conversation.getLastMessages(1,0).get(0); + switch (column) { + case 1: + return conversation.getName(); + case 2: + return lastMessage.toString(); + case 3: + return lastMessage.getTimeReadable(); + default: + return null; + } + } + + @Override + public boolean isNull(int column) { + // TODO Auto-generated method stub + return false; + } + +} \ No newline at end of file diff --git a/src/de/gultsch/chat/ConversationList.java b/src/de/gultsch/chat/ConversationList.java new file mode 100644 index 00000000..b043f61a --- /dev/null +++ b/src/de/gultsch/chat/ConversationList.java @@ -0,0 +1,41 @@ +package de.gultsch.chat; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +public class ConversationList extends ArrayList { + + private static final long serialVersionUID = 3661496589984289968L; + + private int selectedConversationPosition = -1; + + private ConversationCursor cursor = new ConversationCursor(this); + + public ConversationCursor getCursor() { + return this.cursor; + } + + public Conversation getSelectedConversation() { + return this.get(this.selectedConversationPosition); + } + + public void setSelectedConversationPosition(int selectedConversation) { + this.selectedConversationPosition = selectedConversation; + } + + public void sort() { + Conversation selectedConversation = this.get(selectedConversationPosition); + //sort this + Collections.sort(this, new Comparator() { + + @Override + public int compare(Conversation lhs, Conversation rhs) { + // TODO Auto-generated method stub + return 0; + } + }); + + this.selectedConversationPosition = this.indexOf(selectedConversation); + } +} diff --git a/src/de/gultsch/chat/Message.java b/src/de/gultsch/chat/Message.java new file mode 100644 index 00000000..b4e5e7ca --- /dev/null +++ b/src/de/gultsch/chat/Message.java @@ -0,0 +1,19 @@ +package de.gultsch.chat; + +public class Message { + + String msg; + + public Message(String msg) { + this.msg = msg; + } + + public String toString() { + return msg; + } + + public String getTimeReadable() { + return "2 min"; + } + +} diff --git a/src/de/gultsch/chat/services/XmppConnectionService.java b/src/de/gultsch/chat/services/XmppConnectionService.java new file mode 100644 index 00000000..4477513d --- /dev/null +++ b/src/de/gultsch/chat/services/XmppConnectionService.java @@ -0,0 +1,29 @@ +package de.gultsch.chat.services; + +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; + +public class XmppConnectionService extends Service { + + // Binder given to clients + private final IBinder mBinder = new XmppConnectionBinder(); + + /** + * Class used for the client Binder. Because we know this service always + * runs in the same process as its clients, we don't need to deal with IPC. + */ + public class XmppConnectionBinder extends Binder { + XmppConnectionService getService() { + // Return this instance of LocalService so clients can call public methods + return XmppConnectionService.this; + } + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + +} diff --git a/src/de/gultsch/chat/ui/ConversationActivity.java b/src/de/gultsch/chat/ui/ConversationActivity.java new file mode 100644 index 00000000..446a0c97 --- /dev/null +++ b/src/de/gultsch/chat/ui/ConversationActivity.java @@ -0,0 +1,161 @@ +package de.gultsch.chat.ui; + +import java.util.HashMap; + +import de.gultsch.chat.Contact; +import de.gultsch.chat.Conversation; +import de.gultsch.chat.ConversationCursor; +import de.gultsch.chat.ConversationList; +import de.gultsch.chat.R; +import de.gultsch.chat.R.id; +import android.os.Bundle; +import android.app.Activity; +import android.app.FragmentTransaction; +import android.content.Context; +import android.content.Intent; +import android.support.v4.widget.SlidingPaneLayout; +import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; +import android.widget.SimpleCursorAdapter; + +public class ConversationActivity extends Activity { + + public static final String START_CONVERSATION = "startconversation"; + public static final String CONVERSATION_CONTACT = "conversationcontact"; + + protected SlidingPaneLayout spl; + + protected HashMap conversationFragments = new HashMap(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_conversations); + + final ConversationList conversationList = new ConversationList(); + + if (getIntent().getAction().equals(Intent.ACTION_VIEW)) { + if (getIntent().getType().equals(ConversationActivity.START_CONVERSATION)) { + Contact contact = (Contact) getIntent().getExtras().get(ConversationActivity.CONVERSATION_CONTACT); + Log.d("gultsch","start conversation with "+contact.getDisplayName()); + conversationList.add(new Conversation(contact.getDisplayName())); + } + } + String[] fromColumns = {ConversationCursor.NAME, + ConversationCursor.LAST_MSG}; + int[] toViews = {R.id.conversation_name, R.id.conversation_lastmsg}; + + final SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,R.layout.conversation_list_row, conversationList.getCursor(), fromColumns, toViews,0); + final ListView listView = (ListView) findViewById(R.id.list); + listView.setAdapter(adapter); + + listView.setOnItemClickListener(new OnItemClickListener() { + + @Override + public void onItemClick(AdapterView arg0, View clickedView, int position, + long arg3) { + conversationList.setSelectedConversationPosition(position); + swapConversationFragment(conversationList); + getActionBar().setTitle(conversationList.getSelectedConversation().getName()); + spl.closePane(); + } + }); + spl = (SlidingPaneLayout) findViewById(id.slidingpanelayout); + spl.setParallaxDistance(150); + spl.openPane(); + spl.setShadowResource(R.drawable.es_slidingpane_shadow); + spl.setSliderFadeColor(0); + spl.setPanelSlideListener(new PanelSlideListener() { + @Override + public void onPanelOpened(View arg0) { + getActionBar().setDisplayHomeAsUpEnabled(false); + getActionBar().setTitle(R.string.app_name); + invalidateOptionsMenu(); + + InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + + inputManager.hideSoftInputFromWindow( + getCurrentFocus().getWindowToken(), + InputMethodManager.HIDE_NOT_ALWAYS); + listView.requestFocus(); + } + + @Override + public void onPanelClosed(View arg0) { + getActionBar().setDisplayHomeAsUpEnabled(true); + getActionBar().setTitle(conversationList.getSelectedConversation().getName()); + invalidateOptionsMenu(); + + } + + @Override + public void onPanelSlide(View arg0, float arg1) { + // TODO Auto-generated method stub + + } + }); + if (conversationList.size() >= 1) { + conversationList.setSelectedConversationPosition(0); + swapConversationFragment(conversationList); + } else { + //start new conversation activity + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.conversations, menu); + + if (spl.isOpen()) { + ((MenuItem) menu.findItem(R.id.action_archive)).setVisible(false); + ((MenuItem) menu.findItem(R.id.action_details)).setVisible(false); + ((MenuItem) menu.findItem(R.id.action_security)).setVisible(false); + } else { + ((MenuItem) menu.findItem(R.id.action_add)).setVisible(false); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch(item.getItemId()) { + case android.R.id.home: + spl.openPane(); + break; + case R.id.action_settings: + startActivity(new Intent(this, SettingsActivity.class)); + break; + case R.id.action_accounts: + startActivity(new Intent(this, ManageAccountActivity.class)); + break; + case R.id.action_add: + startActivity(new Intent(this, NewConversationActivity.class)); + default: + break; + } + return super.onOptionsItemSelected(item); + } + + protected void swapConversationFragment( + final ConversationList conversationList) { + ConversationFragment selectedFragment; + if (conversationFragments.containsKey(conversationList.getSelectedConversation())) { + selectedFragment = conversationFragments.get(conversationList.getSelectedConversation()); + } else { + selectedFragment = new ConversationFragment(); + conversationFragments.put(conversationList.getSelectedConversation(), selectedFragment); + } + FragmentTransaction transaction = getFragmentManager().beginTransaction(); + transaction.replace(R.id.selected_conversation, selectedFragment); + transaction.commit(); + } + +} diff --git a/src/de/gultsch/chat/ui/ConversationFragment.java b/src/de/gultsch/chat/ui/ConversationFragment.java new file mode 100644 index 00000000..0f619176 --- /dev/null +++ b/src/de/gultsch/chat/ui/ConversationFragment.java @@ -0,0 +1,18 @@ +package de.gultsch.chat.ui; + +import de.gultsch.chat.R; +import android.app.Fragment; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; + +public class ConversationFragment extends Fragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.frament_conversation, container, false); + } +} diff --git a/src/de/gultsch/chat/ui/ManageAccountActivity.java b/src/de/gultsch/chat/ui/ManageAccountActivity.java new file mode 100644 index 00000000..741f60c3 --- /dev/null +++ b/src/de/gultsch/chat/ui/ManageAccountActivity.java @@ -0,0 +1,7 @@ +package de.gultsch.chat.ui; + +import android.app.Activity; + +public class ManageAccountActivity extends Activity { + +} diff --git a/src/de/gultsch/chat/ui/NewConversationActivity.java b/src/de/gultsch/chat/ui/NewConversationActivity.java new file mode 100644 index 00000000..261f0ebf --- /dev/null +++ b/src/de/gultsch/chat/ui/NewConversationActivity.java @@ -0,0 +1,197 @@ +package de.gultsch.chat.ui; + +import java.util.LinkedHashMap; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import de.gultsch.chat.Contact; +import de.gultsch.chat.R; +import android.os.Bundle; +import android.provider.ContactsContract; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.ImageView; +import android.app.Activity; +import android.content.Context; +import android.content.CursorLoader; +import android.content.Intent; +import android.content.Loader; +import android.content.Loader.OnLoadCompleteListener; +import android.database.Cursor; + +public class NewConversationActivity extends Activity { + + final protected LinkedHashMap availablePhoneContacts = new LinkedHashMap(); + final protected LinkedHashMap availableJabberContacts = new LinkedHashMap(); + protected View newContactView; + protected Contact newContact; + + public static final Pattern VALID_JID = + Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE); + + static final String[] PROJECTION = new String[] { + ContactsContract.Data.CONTACT_ID, + ContactsContract.Data.DISPLAY_NAME, + ContactsContract.Data.PHOTO_THUMBNAIL_URI, + ContactsContract.CommonDataKinds.Im.DATA }; + + // This is the select criteria + static final String SELECTION = "(" + ContactsContract.Data.MIMETYPE + + "=\"" + ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE + + "\") AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + + "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER + + "\")"; + + protected View getViewForContact(Contact contact) { + LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View view = (View) inflater.inflate(R.layout.contact,null); + ((TextView) view.findViewById(R.id.contact_display_name)).setText(contact.getDisplayName()); + ((TextView) view.findViewById(R.id.contact_jid)).setText(contact.getJid()); + if (contact.getProfilePhoto() != null) { + ((ImageView) view.findViewById(R.id.contact_photo)).setImageURI(contact.getProfilePhoto()); + } + view.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + Contact clickedContact = null; + for(Entry entry : availablePhoneContacts.entrySet()) { + if (entry.getValue() == v) { + clickedContact = entry.getKey(); + break; + } + } + for(Entry entry : availableJabberContacts.entrySet()) { + if (entry.getValue() == v) { + clickedContact = entry.getKey(); + break; + } + } + if (newContactView==v) { + clickedContact = newContact; + } + Log.d("gultsch","clicked on "+clickedContact.getDisplayName()); + Intent startConversationIntent = new Intent(v.getContext(),ConversationActivity.class); + startConversationIntent.setAction(Intent.ACTION_VIEW); + startConversationIntent.putExtra(ConversationActivity.CONVERSATION_CONTACT, clickedContact); + startConversationIntent.setType(ConversationActivity.START_CONVERSATION); + startActivity(startConversationIntent); + } + }); + return view; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_new_conversation); + CursorLoader mCursorLoader = new CursorLoader(this, + ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null, + null); + mCursorLoader.registerListener(0, new OnLoadCompleteListener() { + + @Override + public void onLoadComplete(Loader arg0, Cursor cursor) { + while (cursor.moveToNext()) { + Contact contact = new Contact( + cursor.getString(cursor + .getColumnIndex(ContactsContract.Data.DISPLAY_NAME)), + cursor.getString(cursor + .getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)), + cursor.getString(cursor + .getColumnIndex(ContactsContract.Data.PHOTO_THUMBNAIL_URI))); + View contactView = getViewForContact(contact); + availablePhoneContacts.put(contact, getViewForContact(contact)); + ((LinearLayout) findViewById(R.id.phone_contacts)).addView(contactView); + } + updateAvailableContacts(); + } + }); + mCursorLoader.startLoading(); + + ((TextView) findViewById(R.id.new_conversation_search)).addTextChangedListener(new TextWatcher() { + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + updateAvailableContacts(); + } + + @Override + public void afterTextChanged(Editable s) { + // TODO Auto-generated method stub + + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + // TODO Auto-generated method stub + + } + }); + + } + + protected void updateAvailableContacts() { + String search = ((TextView) findViewById(R.id.new_conversation_search)).getText().toString(); + + LinearLayout phoneContacts = (LinearLayout) findViewById(R.id.phone_contacts); + filterAvailableContacts(phoneContacts,this.availablePhoneContacts,search); + + if (phoneContacts.getChildCount() == 0) { + findViewById(R.id.phone_contacts_header).setVisibility(View.GONE); + } else { + findViewById(R.id.phone_contacts_header).setVisibility(View.VISIBLE); + } + + LinearLayout jabberContacts = (LinearLayout) findViewById(R.id.jabber_contacts); + filterAvailableContacts(jabberContacts,this.availableJabberContacts,search); + if (jabberContacts.getChildCount() == 0) { + findViewById(R.id.jabber_contacts_header).setVisibility(View.GONE); + } else { + findViewById(R.id.jabber_contacts_header).setVisibility(View.VISIBLE); + } + + LinearLayout createNewContact = (LinearLayout) findViewById(R.id.create_new_contact); + Matcher matcher = VALID_JID.matcher(search); + if (matcher.find()) { + createNewContact.removeAllViews(); + String name = search.split("@")[0]; + newContact = new Contact(name,search,null); + newContactView = getViewForContact(newContact); + newContactView.findViewById(R.id.contact_divider).setVisibility(View.GONE); + createNewContact.addView(newContactView); + createNewContact.setVisibility(View.VISIBLE); + ((TextView) findViewById(R.id.new_contact_header)).setVisibility(View.VISIBLE); + } else { + createNewContact.setVisibility(View.GONE); + ((TextView) findViewById(R.id.new_contact_header)).setVisibility(View.GONE); + } + } + + private void filterAvailableContacts( + LinearLayout layout, LinkedHashMap contacts, String search) { + layout.removeAllViews(); + for(Entry entry : contacts.entrySet()) { + + if (entry.getKey().match(search)) { + entry.getValue().setVisibility(View.VISIBLE); + entry.getValue().findViewById(R.id.contact_divider).setVisibility(View.VISIBLE); + layout.addView(entry.getValue()); + } + } + int contactsCount = layout.getChildCount(); + if (contactsCount>=1) { + View lastContact = layout.getChildAt(contactsCount - 1); + lastContact.findViewById(R.id.contact_divider).setVisibility(View.GONE); + } + } +} diff --git a/src/de/gultsch/chat/ui/SettingsActivity.java b/src/de/gultsch/chat/ui/SettingsActivity.java new file mode 100644 index 00000000..886c05cc --- /dev/null +++ b/src/de/gultsch/chat/ui/SettingsActivity.java @@ -0,0 +1,16 @@ +package de.gultsch.chat.ui; + +import android.app.Activity; +import android.os.Bundle; + +public class SettingsActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Display the fragment as the main content. + getFragmentManager().beginTransaction() + .replace(android.R.id.content, new SettingsFragment()).commit(); + } + +} diff --git a/src/de/gultsch/chat/ui/SettingsFragment.java b/src/de/gultsch/chat/ui/SettingsFragment.java new file mode 100644 index 00000000..3ca4841a --- /dev/null +++ b/src/de/gultsch/chat/ui/SettingsFragment.java @@ -0,0 +1,15 @@ +package de.gultsch.chat.ui; + +import de.gultsch.chat.R; +import android.os.Bundle; +import android.preference.PreferenceFragment; + +public class SettingsFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Load the preferences from an XML resource + addPreferencesFromResource(R.xml.preferences); + } +} -- cgit v1.2.3