adding and removing roster items now possible. basic error display on error messages

This commit is contained in:
Daniel Gultsch 2014-02-20 17:00:50 +01:00
parent 94ab61d5c0
commit c82179c0b8
12 changed files with 263 additions and 79 deletions

View file

@ -22,7 +22,8 @@
<activity
android:name="de.gultsch.chat.ui.ConversationActivity"
android:label="Secure Conversations"
android:windowSoftInputMode="stateHidden">
android:windowSoftInputMode="stateHidden"
android:configChanges="orientation|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

View file

@ -111,8 +111,9 @@ public final class R {
public static final int fragment_conversation=0x7f030007;
public static final int fragment_conversations_overview=0x7f030008;
public static final int manage_accounts=0x7f030009;
public static final int message_recieved=0x7f03000a;
public static final int message_sent=0x7f03000b;
public static final int message_error=0x7f03000a;
public static final int message_recieved=0x7f03000b;
public static final int message_sent=0x7f03000c;
}
public static final class menu {
public static final int conversations=0x7f090000;

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/message_border"
android:layout_toRightOf="@+id/message_photo"
android:layout_alignParentBottom="true"
android:minHeight="48dp"
>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="#ededed"
android:padding="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Username is already in use"
android:textSize="16sp"
android:id="@+id/message_body"
android:textColor="#e92727"
android:textStyle="bold"
android:typeface="monospace"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="1dp"
android:text="@string/sending"
android:textColor="#8e8e8e"
android:textSize="12sp"
android:id="@+id/message_time"/>
</LinearLayout>
</LinearLayout>
<ImageView
android:id="@+id/message_photo"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_marginRight="-1.5dp"
android:padding="0dp"
android:src="@drawable/ic_profile"
android:scaleType="fitXY"/>
</RelativeLayout>

View file

@ -44,6 +44,7 @@ public class Contact extends AbstractEntity implements Serializable {
} else {
this.accountUuid = account.getUuid();
}
this.account = account;
this.displayName = displayName;
this.jid = jid;
this.photoUri = photoUri;

View file

@ -12,6 +12,7 @@ public class Message extends AbstractEntity {
public static final int STATUS_RECIEVED = 0;
public static final int STATUS_UNSEND = 1;
public static final int STATUS_SEND = 2;
public static final int STATUS_ERROR = 3;
public static final int ENCRYPTION_NONE = 0;
public static final int ENCRYPTION_PGP = 1;

View file

@ -103,6 +103,8 @@ public class XmppConnectionService extends Service {
if (message != null) {
notify = (message.getStatus() == Message.STATUS_RECIEVED);
}
} else if (packet.getType() == MessagePacket.TYPE_ERROR) {
message = MessageParser.parseError(packet,account,service);
} else {
Log.d(LOGTAG, "unparsed message " + packet.toString());
}
@ -126,7 +128,9 @@ public class XmppConnectionService extends Service {
}
Conversation conversation = message.getConversation();
conversation.getMessages().add(message);
databaseBackend.createMessage(message);
if (packet.getType() != MessagePacket.TYPE_ERROR) {
databaseBackend.createMessage(message);
}
if (convChangedListener != null) {
convChangedListener.onConversationListChanged();
} else {
@ -171,7 +175,7 @@ public class XmppConnectionService extends Service {
Contact contact = findContact(account, fromParts[0]);
if (contact == null) {
// most likely muc, self or roster not synced
// Log.d(LOGTAG,"got presence for non contact "+packet.toString());
Log.d(LOGTAG,"got presence for non contact "+packet.toString());
return;
}
String type = packet.getAttribute("type");
@ -197,7 +201,7 @@ public class XmppConnectionService extends Service {
databaseBackend.updateContact(contact);
}
}
replaceContactInConversation(contact);
replaceContactInConversation(contact.getJid(),contact);
}
};
@ -233,21 +237,21 @@ public class XmppConnectionService extends Service {
} else {
if (subscription.equals("remove")) {
databaseBackend.deleteContact(contact);
replaceContactInConversation(contact.getJid(), null);
} else {
contact.setSubscription(subscription);
databaseBackend.updateContact(contact);
replaceContactInConversation(contact);
replaceContactInConversation(contact.getJid(),contact);
}
}
}
}
}
private void replaceContactInConversation(Contact contact) {
private void replaceContactInConversation(String jid, Contact contact) {
List<Conversation> conversations = getConversations();
for (int i = 0; i < conversations.size(); ++i) {
if ((conversations.get(i).getContact() != null)
&& (conversations.get(i).getContact().equals(contact))) {
if ((conversations.get(i).getContactJid().equals(jid))) {
conversations.get(i).setContact(contact);
break;
}
@ -458,6 +462,7 @@ public class XmppConnectionService extends Service {
List<Contact> contactsToDelete = databaseBackend.getContats(mWhere.toString());
for(Contact contact : contactsToDelete) {
databaseBackend.deleteContact(contact);
replaceContactInConversation(contact.getJid(), null);
}
}
mergePhoneContactsWithRoster(new OnPhoneContactsMerged() {
@ -517,10 +522,6 @@ public class XmppConnectionService extends Service {
});
}
public void addConversation(Conversation conversation) {
databaseBackend.createConversation(conversation);
}
public List<Conversation> getConversations() {
if (this.conversations == null) {
Hashtable<String, Account> accountLookupTable = new Hashtable<String, Account>();
@ -629,6 +630,20 @@ public class XmppConnectionService extends Service {
if (accountChangedListener != null)
accountChangedListener.onAccountListChangedListener();
}
public void deleteContact(Contact contact) {
IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
Element query = new Element("query");
query.setAttribute("xmlns", "jabber:iq:roster");
Element item = new Element("item");
item.setAttribute("jid", contact.getJid());
item.setAttribute("subscription", "remove");
query.addChild(item);
iq.addChild(query);
contact.getAccount().getXmppConnection().sendIqPacket(iq, null);
replaceContactInConversation(contact.getJid(), null);
databaseBackend.deleteContact(contact);
}
public void updateAccount(Account account) {
databaseBackend.updateAccount(account);
@ -735,4 +750,20 @@ public class XmppConnectionService extends Service {
public void updateContact(Contact contact) {
databaseBackend.updateContact(contact);
}
public void createContact(Contact contact) {
IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
Element query = new Element("query");
query.setAttribute("xmlns", "jabber:iq:roster");
Element item = new Element("item");
item.setAttribute("jid", contact.getJid());
item.setAttribute("name", contact.getJid());
query.addChild(item);
iq.addChild(query);
Account account = contact.getAccount();
Log.d(LOGTAG,account.getJid()+": adding "+contact.getJid()+" to roster");
account.getXmppConnection().sendIqPacket(iq, null);
replaceContactInConversation(contact.getJid(), contact);
databaseBackend.createContact(contact);
}
}

View file

@ -7,15 +7,18 @@ import java.util.List;
import de.gultsch.chat.R;
import de.gultsch.chat.R.id;
import de.gultsch.chat.entities.Account;
import de.gultsch.chat.entities.Contact;
import de.gultsch.chat.entities.Conversation;
import de.gultsch.chat.entities.Message;
import de.gultsch.chat.utils.UIHelper;
import android.net.Uri;
import android.os.Bundle;
import android.app.AlertDialog;
import android.app.FragmentTransaction;
import android.app.NotificationManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Typeface;
import android.support.v4.widget.SlidingPaneLayout;
@ -82,6 +85,18 @@ public class ConversationActivity extends XmppActivity {
});
}
};
private DialogInterface.OnClickListener addToRoster = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String jid = getSelectedConversation().getContactJid();
Account account = getSelectedConversation().getAccount();
String name = jid.split("@")[0];
Contact contact = new Contact(account, name, jid, null);
xmppConnectionService.createContact(contact);
}
};
private boolean contactInserted = false;
@ -288,7 +303,13 @@ public class ConversationActivity extends XmppActivity {
details.setContact(contact);
details.show(getFragmentManager(), "details");
} else {
Log.d("xmppService","contact was null - means not in roster");
String jid = getSelectedConversation().getContactJid();
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(jid);
builder.setMessage("The contact is not in your roster. Would you like to add it.");
builder.setNegativeButton("Cancel", null);
builder.setPositiveButton("Add",addToRoster);
builder.create().show();
}
break;
case R.id.action_security:

View file

@ -8,6 +8,8 @@ import java.util.Hashtable;
import java.util.List;
import java.util.Set;
import javax.crypto.spec.PSource;
import net.java.otr4j.OtrException;
import net.java.otr4j.session.SessionStatus;
@ -53,6 +55,8 @@ public class ConversationFragment extends Fragment {
protected BitmapCache mBitmapCache = new BitmapCache();
private EditText chatMsg;
protected Bitmap selfBitmap;
private OnClickListener sendMsgListener = new OnClickListener() {
@ -105,47 +109,26 @@ public class ConversationFragment extends Fragment {
sendButton.setOnClickListener(this.sendMsgListener);
messagesView = (ListView) view.findViewById(R.id.messages_view);
SharedPreferences sharedPref = PreferenceManager
.getDefaultSharedPreferences(getActivity()
.getApplicationContext());
boolean showPhoneSelfContactPicture = sharedPref.getBoolean(
"show_phone_selfcontact_picture", true);
Bitmap self;
if (showPhoneSelfContactPicture) {
Uri selfiUri = PhoneHelper.getSefliUri(getActivity());
try {
self = BitmapFactory.decodeStream(getActivity()
.getContentResolver().openInputStream(selfiUri));
} catch (FileNotFoundException e) {
self = UIHelper.getUnknownContactPicture(conversation
.getAccount().getJid(), 200);
}
} else {
self = UIHelper.getUnknownContactPicture(conversation.getAccount()
.getJid(), 200);
}
final Bitmap selfBitmap = self;
messageListAdapter = new ArrayAdapter<Message>(this.getActivity()
.getApplicationContext(), R.layout.message_sent,
this.messageList) {
private static final int SENT = 0;
private static final int RECIEVED = 1;
private static final int ERROR = 2;
@Override
public int getViewTypeCount() {
return 2;
return 3;
}
@Override
public int getItemViewType(int position) {
if (getItem(position).getStatus() == Message.STATUS_RECIEVED) {
return RECIEVED;
} else if (getItem(position).getStatus() == Message.STATUS_ERROR) {
return ERROR;
} else {
return SENT;
}
@ -167,7 +150,6 @@ public class ConversationFragment extends Fragment {
viewHolder.imageView.setImageBitmap(selfBitmap);
break;
case RECIEVED:
viewHolder = new ViewHolder();
view = (View) inflater.inflate(
R.layout.message_recieved, null);
viewHolder.imageView = (ImageView) view
@ -185,6 +167,12 @@ public class ConversationFragment extends Fragment {
}
}
break;
case ERROR:
view = (View) inflater.inflate(R.layout.message_error, null);
viewHolder.imageView = (ImageView) view
.findViewById(R.id.message_photo);
viewHolder.imageView.setImageBitmap(mBitmapCache.getError());
break;
default:
viewHolder = null;
break;
@ -193,7 +181,6 @@ public class ConversationFragment extends Fragment {
.findViewById(R.id.message_body);
viewHolder.time = (TextView) view
.findViewById(R.id.message_time);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
@ -238,31 +225,47 @@ public class ConversationFragment extends Fragment {
return view;
}
protected Bitmap findSelfPicture() {
SharedPreferences sharedPref = PreferenceManager
.getDefaultSharedPreferences(getActivity()
.getApplicationContext());
boolean showPhoneSelfContactPicture = sharedPref.getBoolean(
"show_phone_selfcontact_picture", true);
Bitmap self;
if (showPhoneSelfContactPicture) {
Uri selfiUri = PhoneHelper.getSefliUri(getActivity());
try {
self = BitmapFactory.decodeStream(getActivity()
.getContentResolver().openInputStream(selfiUri));
} catch (FileNotFoundException e) {
self = UIHelper.getUnknownContactPicture(conversation
.getAccount().getJid(), 200);
}
} else {
self = UIHelper.getUnknownContactPicture(conversation.getAccount()
.getJid(), 200);
}
final Bitmap selfBitmap = self;
return selfBitmap;
}
@Override
public void onStart() {
super.onStart();
final ConversationActivity activity = (ConversationActivity) getActivity();
ConversationActivity activity = (ConversationActivity) getActivity();
if (activity.xmppConnectionServiceBound) {
this.conversation = activity.getSelectedConversation();
updateMessages();
// rendering complete. now go tell activity to close pane
if (!activity.shouldPaneBeOpen()) {
activity.getSlidingPaneLayout().closePane();
activity.getActionBar().setDisplayHomeAsUpEnabled(true);
activity.getActionBar().setTitle(conversation.getName());
activity.invalidateOptionsMenu();
if (!conversation.isRead()) {
conversation.markRead();
activity.updateConversationList();
}
}
this.onBackendConnected();
}
}
public void onBackendConnected() {
final ConversationActivity activity = (ConversationActivity) getActivity();
this.conversation = activity.getSelectedConversation();
this.selfBitmap = findSelfPicture();
updateMessages();
// rendering complete. now go tell activity to close pane
if (!activity.shouldPaneBeOpen()) {
@ -353,7 +356,7 @@ public class ConversationFragment extends Fragment {
} else {
presences = null;
}
if ((presences != null) && (presences.size() == 0)) {
if ((presences == null) || (presences.size() == 0)) {
AlertDialog.Builder builder = new AlertDialog.Builder(
getActivity());
builder.setTitle("Contact is offline");
@ -412,6 +415,7 @@ public class ConversationFragment extends Fragment {
private class BitmapCache {
private HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>();
private Bitmap error = null;
public Bitmap get(String name, Uri uri) {
if (bitmaps.containsKey(name)) {
@ -432,5 +436,12 @@ public class ConversationFragment extends Fragment {
return bm;
}
}
public Bitmap getError() {
if (error == null) {
error = UIHelper.getErrorPicture(200);
}
return error;
}
}
}

View file

@ -9,7 +9,6 @@ import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.Contacts;
@ -18,7 +17,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.QuickContactBadge;
import android.widget.TextView;
@ -27,13 +25,53 @@ public class DialogContactDetails extends DialogFragment {
private Contact contact = null;
boolean displayingInRoster = false;
private DialogContactDetails mDetailsDialog = this;
private XmppActivity activity;
private DialogInterface.OnClickListener askRemoveFromRoster = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Delete from roster");
builder.setMessage("Do you want to delete "+contact.getJid()+" from your roster. The conversation assoziated with this account will not be removed.");
builder.setNegativeButton("Cancel", null);
builder.setPositiveButton("Delete",removeFromRoster);
builder.create().show();
}
};
private DialogInterface.OnClickListener removeFromRoster = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
activity.xmppConnectionService.deleteContact(contact);
mDetailsDialog.dismiss();
}
};
private DialogInterface.OnClickListener addToPhonebook = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
intent.setType(Contacts.CONTENT_ITEM_TYPE);
intent.putExtra(Intents.Insert.IM_HANDLE,contact.getJid());
intent.putExtra(Intents.Insert.IM_PROTOCOL,CommonDataKinds.Im.PROTOCOL_JABBER);
intent.putExtra("finishActivityOnSaveCompleted", true);
getActivity().startActivityForResult(intent,ConversationActivity.INSERT_CONTACT);
mDetailsDialog.dismiss();
}
};
public void setContact(Contact contact) {
this.contact = contact;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
this.activity = (XmppActivity) getActivity();
AlertDialog.Builder builder = new AlertDialog.Builder(this.activity);
LayoutInflater inflater = getActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.dialog_contact_details, null);
TextView contactJid = (TextView) view.findViewById(R.id.details_contactjid);
@ -96,28 +134,15 @@ public class DialogContactDetails extends DialogFragment {
UIHelper.prepareContactBadge(getActivity(), badge, contact);
if (contact.getSystemAccount()==null) {
final DialogContactDetails details = this;
badge.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Add to contacts");
builder.setMessage("Do you want to add "+contact.getJid()+" to your contact list?");
builder.setTitle("Add to phone book");
builder.setMessage("Do you want to add "+contact.getJid()+" to your phones contact list?");
builder.setNegativeButton("Cancel", null);
builder.setPositiveButton("Add", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
intent.setType(Contacts.CONTENT_ITEM_TYPE);
intent.putExtra(Intents.Insert.IM_HANDLE,contact.getJid());
intent.putExtra(Intents.Insert.IM_PROTOCOL,CommonDataKinds.Im.PROTOCOL_JABBER);
intent.putExtra("finishActivityOnSaveCompleted", true);
getActivity().startActivityForResult(intent,ConversationActivity.INSERT_CONTACT);
details.dismiss();
}
});
builder.setPositiveButton("Add",addToPhonebook);
builder.create().show();
}
});
@ -127,7 +152,7 @@ public class DialogContactDetails extends DialogFragment {
builder.setTitle(contact.getDisplayName());
builder.setNeutralButton("Done", null);
builder.setPositiveButton("Remove from roster", null);
builder.setPositiveButton("Remove from roster", this.askRemoveFromRoster);
return builder.create();
}
}

View file

@ -116,4 +116,19 @@ public class MessageParser {
Conversation conversation = service.findOrCreateConversation(account, parts[0],false);
return new Message(conversation,fullJid, message.findChild("body").getContent(), Message.ENCRYPTION_NONE,status);
}
public static Message parseError(MessagePacket packet, Account account, XmppConnectionService service) {
String[] fromParts = packet.getFrom().split("/");
Conversation conversation = service.findOrCreateConversation(account, fromParts[0],false);
Element error = packet.findChild("error");
String errorName = error.getChildren().get(0).getName();
String displayError;
if (errorName.equals("service-unavailable")) {
displayError = "Contact is offline and does not have offline storage";
} else {
displayError = errorName.replace("-", " ");
}
return new Message(conversation, packet.getFrom(), displayError, Message.ENCRYPTION_NONE, Message.STATUS_ERROR);
}
}

View file

@ -54,7 +54,7 @@ public class UIHelper {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
return sdf.format(date);
} else {
SimpleDateFormat sdf = new SimpleDateFormat("M/D");
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd");
return sdf.format(date);
}
}
@ -85,6 +85,26 @@ public class UIHelper {
return bitmap;
}
public static Bitmap getErrorPicture(int size) {
Bitmap bitmap = Bitmap
.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
bitmap.eraseColor(0xFFe92727);
Paint paint = new Paint();
paint.setColor(0xffe5e5e5);
paint.setTextSize((float) (size * 0.9));
paint.setAntiAlias(true);
Rect rect = new Rect();
paint.getTextBounds("!", 0, 1, rect);
float width = paint.measureText("!");
canvas.drawText("!", (size / 2) - (width / 2), (size / 2)
+ (rect.height() / 2), paint);
return bitmap;
}
public static Notification getUnreadMessageNotification(Context context,
Conversation conversation) {

View file

@ -7,6 +7,7 @@ public class MessagePacket extends Element {
public static final int TYPE_UNKNOWN = 1;
public static final int TYPE_NO = 2;
public static final int TYPE_GROUPCHAT = 3;
public static final int TYPE_ERROR = 4;
private MessagePacket(String name) {
super(name);
@ -71,6 +72,8 @@ public class MessagePacket extends Element {
return TYPE_CHAT;
} else if (type.equals("groupchat")) {
return TYPE_GROUPCHAT;
} else if (type.equals("error")) {
return TYPE_ERROR;
} else {
return TYPE_UNKNOWN;
}