UI for introducing two contacts to each other

Show system notification for successful introductions
This commit is contained in:
Torsten Grote
2016-01-19 13:28:59 -02:00
parent 7c687736df
commit 5ea7ff2857
65 changed files with 2398 additions and 271 deletions

View File

@@ -4,29 +4,37 @@ import android.content.Context;
import android.support.v7.util.SortedList;
import android.support.v7.widget.RecyclerView;
import android.text.format.DateUtils;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.introduction.IntroductionRequest;
import org.briarproject.api.introduction.SessionId;
import org.briarproject.api.messaging.PrivateMessageHeader;
import org.briarproject.util.StringUtils;
import im.delight.android.identicons.IdenticonDrawable;
import static android.support.v7.util.SortedList.INVALID_POSITION;
import static android.support.v7.widget.RecyclerView.ViewHolder;
import static org.briarproject.android.contact.ConversationItem.INTRODUCTION_IN;
import static org.briarproject.android.contact.ConversationItem.INTRODUCTION_OUT;
import static org.briarproject.android.contact.ConversationItem.MSG_IN;
import static org.briarproject.android.contact.ConversationItem.MSG_IN_UNREAD;
import static org.briarproject.android.contact.ConversationItem.MSG_OUT;
import static org.briarproject.android.contact.ConversationItem.NOTICE_IN;
import static org.briarproject.android.contact.ConversationItem.NOTICE_OUT;
import static org.briarproject.android.contact.ConversationItem.OutgoingItem;
import static org.briarproject.android.contact.ConversationItem.IncomingItem;
class ConversationAdapter extends
RecyclerView.Adapter<ConversationAdapter.MessageHolder> {
class ConversationAdapter extends RecyclerView.Adapter {
private static final int MSG_OUT = 0;
private static final int MSG_IN = 1;
private static final int MSG_IN_UNREAD = 2;
private final SortedList<ConversationItem> messages =
private final SortedList<ConversationItem> items =
new SortedList<ConversationItem>(ConversationItem.class,
new SortedList.Callback<ConversationItem>() {
@Override
@@ -52,8 +60,8 @@ class ConversationAdapter extends
@Override
public int compare(ConversationItem c1,
ConversationItem c2) {
long time1 = c1.getHeader().getTimestamp();
long time2 = c2.getHeader().getTimestamp();
long time1 = c1.getTime();
long time2 = c2.getTime();
if (time1 < time2) return -1;
if (time1 > time2) return 1;
return 0;
@@ -62,8 +70,7 @@ class ConversationAdapter extends
@Override
public boolean areItemsTheSame(ConversationItem c1,
ConversationItem c2) {
return c1.getHeader().getId()
.equals(c2.getHeader().getId());
return c1.getId().equals(c2.getId());
}
@Override
@@ -73,67 +80,113 @@ class ConversationAdapter extends
}
});
private Context ctx;
private IntroductionHandler intro;
private byte[] identiconKey;
private String contactName;
public ConversationAdapter(Context context) {
public ConversationAdapter(Context context,
IntroductionHandler introductionHandler) {
ctx = context;
intro = introductionHandler;
setHasStableIds(true);
}
public void setIdenticonKey(byte[] key) {
public void setIdenticonKey(byte[] key, String contactName) {
this.identiconKey = key;
this.contactName = contactName;
// FIXME this breaks the progress animation because it is called early before data is loaded
notifyDataSetChanged();
}
@Override
public int getItemViewType(int position) {
// return different type for incoming and outgoing (local) messages
PrivateMessageHeader header = getItem(position).getHeader();
if (header.isLocal()) {
return MSG_OUT;
} else if (header.isRead()) {
return MSG_IN;
} else {
return MSG_IN_UNREAD;
}
public long getItemId(int position) {
return getItem(position).getId().hashCode();
}
@Override
public MessageHolder onCreateViewHolder(ViewGroup viewGroup, int type) {
public int getItemViewType(int position) {
return getItem(position).getType();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int type) {
View v;
// outgoing message (local)
if (type == MSG_OUT) {
v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.list_item_msg_out, viewGroup, false);
return new MessageHolder(v, type);
}
else if (type == INTRODUCTION_IN) {
v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.list_item_introduction_in, viewGroup, false);
return new IntroductionHolder(v, type);
}
else if (type == INTRODUCTION_OUT) {
v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.list_item_introduction_out, viewGroup, false);
return new IntroductionHolder(v, type);
}
else if (type == NOTICE_IN) {
v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.list_item_notice_in, viewGroup, false);
return new NoticeHolder(v, type);
}
else if (type == NOTICE_OUT) {
v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.list_item_notice_out, viewGroup, false);
return new NoticeHolder(v, type);
}
// incoming message (non-local)
else {
v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.list_item_msg_in, viewGroup, false);
return new MessageHolder(v, type);
}
return new MessageHolder(v, type);
}
@Override
public void onBindViewHolder(final MessageHolder ui, final int position) {
public void onBindViewHolder(final ViewHolder ui, final int position) {
ConversationItem item = getItem(position);
if (item instanceof ConversationMessageItem) {
bindMessage((MessageHolder) ui, (ConversationMessageItem) item,
position);
} else if (item instanceof ConversationIntroductionOutItem) {
bindIntroduction((IntroductionHolder) ui,
(ConversationIntroductionOutItem) item, position);
} else if (item instanceof ConversationIntroductionInItem) {
bindIntroduction((IntroductionHolder) ui,
(ConversationIntroductionInItem) item, position);
} else if (item instanceof ConversationNoticeOutItem) {
bindNotice((NoticeHolder) ui, (ConversationNoticeOutItem) item,
position);
} else if (item instanceof ConversationNoticeInItem) {
bindNotice((NoticeHolder) ui, (ConversationNoticeInItem) item,
position);
} else {
throw new IllegalArgumentException("Unhandled Conversation Item");
}
}
private void bindMessage(final MessageHolder ui,
ConversationMessageItem item, final int position) {
PrivateMessageHeader header = item.getHeader();
if (header.isLocal()) {
if (item.getType() == MSG_OUT) {
if (item.isSeen()) {
ui.status.setImageResource(R.drawable.message_delivered);
ui.status.setImageResource(R.drawable.message_delivered_white);
} else if (item.isSent()) {
ui.status.setImageResource(R.drawable.message_sent);
ui.status.setImageResource(R.drawable.message_sent_white);
} else {
ui.status.setImageResource(R.drawable.message_stored);
ui.status.setImageResource(R.drawable.message_stored_white);
}
} else {
if (identiconKey != null)
ui.avatar.setImageDrawable(
new IdenticonDrawable(identiconKey));
if (!header.isRead()) {
int left = ui.layout.getPaddingLeft();
ui.avatar.setImageDrawable(new IdenticonDrawable(identiconKey));
if (item.getType() == MSG_IN_UNREAD) {
// TODO implement new unread message highlight according to #232
/* int left = ui.layout.getPaddingLeft();
int top = ui.layout.getPaddingTop();
int right = ui.layout.getPaddingRight();
int bottom = ui.layout.getPaddingBottom();
@@ -144,6 +197,7 @@ class ConversationAdapter extends
// re-apply the previous padding due to bug in some Android versions
// see: https://code.google.com/p/android/issues/detail?id=17885
ui.layout.setPadding(left, top, right, bottom);
*/
}
}
@@ -159,42 +213,178 @@ class ConversationAdapter extends
ui.date.setText(DateUtils.getRelativeTimeSpanString(ctx, timestamp));
}
private void bindIntroduction(final IntroductionHolder ui,
final ConversationIntroductionInItem item, final int position) {
final IntroductionRequest ir = item.getIntroductionRequest();
final String message = ir.getMessage();
if (StringUtils.isNullOrEmpty(message)) {
ui.messageLayout.setVisibility(View.GONE);
} else {
ui.messageLayout.setVisibility(View.VISIBLE);
if (item.getType() == INTRODUCTION_IN && identiconKey != null) {
ui.message.avatar.setImageDrawable(
new IdenticonDrawable(identiconKey));
}
ui.message.body.setText(message);
ui.message.date.setText(
DateUtils.getRelativeTimeSpanString(ctx, item.getTime()));
}
// Outgoing Introduction Request
if (item instanceof ConversationIntroductionOutItem) {
ui.text.setText(ctx.getString(R.string.introduction_request_sent,
contactName, ir.getName()));
ConversationIntroductionOutItem i =
(ConversationIntroductionOutItem) item;
if (i.isSeen()) {
ui.status.setImageResource(R.drawable.message_delivered);
ui.message.status.setImageResource(R.drawable.message_delivered_white);
} else if (i.isSent()) {
ui.status.setImageResource(R.drawable.message_sent);
ui.message.status.setImageResource(R.drawable.message_sent_white);
} else {
ui.status.setImageResource(R.drawable.message_stored);
ui.message.status.setImageResource(R.drawable.message_stored_white);
}
}
// Incoming Introduction Request (Answered)
else if (item.wasAnswered()) {
ui.text.setText(ctx.getString(
R.string.introduction_request_answered_received,
contactName, ir.getName()));
ui.acceptButton.setVisibility(View.GONE);
ui.declineButton.setVisibility(View.GONE);
}
// Incoming Introduction Request (Not Answered)
else {
if (item.getIntroductionRequest().doesExist()) {
ui.text.setText(ctx.getString(
R.string.introduction_request_exists_received,
contactName, ir.getName()));
} else {
ui.text.setText(
ctx.getString(R.string.introduction_request_received,
contactName, ir.getName()));
}
ui.acceptButton.setVisibility(View.VISIBLE);
ui.acceptButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
intro.respondToIntroduction(ir.getSessionId(), true);
item.setAnswered(true);
notifyItemChanged(position);
}
});
ui.declineButton.setVisibility(View.VISIBLE);
ui.declineButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
intro.respondToIntroduction(ir.getSessionId(), false);
item.setAnswered(true);
notifyItemChanged(position);
}
});
}
ui.date.setText(
DateUtils.getRelativeTimeSpanString(ctx, item.getTime()));
}
private void bindNotice(final NoticeHolder ui,
final ConversationNoticeItem item, final int position) {
ui.text.setText(item.getText());
ui.date.setText(
DateUtils.getRelativeTimeSpanString(ctx, item.getTime()));
if (item instanceof ConversationNoticeOutItem) {
ConversationNoticeOutItem n = (ConversationNoticeOutItem) item;
if (n.isSeen()) {
ui.status.setImageResource(R.drawable.message_delivered);
} else if (n.isSent()) {
ui.status.setImageResource(R.drawable.message_sent);
} else {
ui.status.setImageResource(R.drawable.message_stored);
}
}
}
@Override
public int getItemCount() {
return messages.size();
return items.size();
}
public ConversationItem getItem(int position) {
if (position == INVALID_POSITION || messages.size() <= position) {
if (position == INVALID_POSITION || items.size() <= position) {
return null; // Not found
}
return messages.get(position);
return items.get(position);
}
public ConversationItem getLastItem() {
if (messages.size() > 0) {
return messages.get(messages.size() - 1);
if (items.size() > 0) {
return items.get(items.size() - 1);
} else {
return null;
}
}
public SparseArray<IncomingItem> getIncomingMessages() {
SparseArray<IncomingItem> messages =
new SparseArray<IncomingItem>();
for (int i = 0; i < items.size(); i++) {
ConversationItem item = items.get(i);
if (item instanceof IncomingItem) {
messages.put(i, (IncomingItem) item);
}
}
return messages;
}
public SparseArray<OutgoingItem> getOutgoingMessages() {
SparseArray<OutgoingItem> messages =
new SparseArray<OutgoingItem>();
for (int i = 0; i < items.size(); i++) {
ConversationItem item = items.get(i);
if (item instanceof OutgoingItem) {
messages.put(i, (OutgoingItem) item);
}
}
return messages;
}
public SparseArray<ConversationMessageItem> getPrivateMessages() {
SparseArray<ConversationMessageItem> messages =
new SparseArray<ConversationMessageItem>();
for (int i = 0; i < items.size(); i++) {
ConversationItem item = items.get(i);
if (item instanceof ConversationMessageItem) {
messages.put(i, (ConversationMessageItem) item);
}
}
return messages;
}
public void add(final ConversationItem message) {
this.messages.add(message);
this.items.add(message);
}
public void clear() {
this.messages.beginBatchedUpdates();
this.items.beginBatchedUpdates();
while(messages.size() != 0) {
messages.removeItemAt(0);
while(items.size() != 0) {
items.removeItemAt(0);
}
this.messages.endBatchedUpdates();
this.items.endBatchedUpdates();
}
// TODO: Does this class need to be public?
public static class MessageHolder extends RecyclerView.ViewHolder {
protected class MessageHolder extends RecyclerView.ViewHolder {
public ViewGroup layout;
public TextView body;
@@ -217,4 +407,59 @@ class ConversationAdapter extends
}
}
}
}
protected class IntroductionHolder extends RecyclerView.ViewHolder {
public ViewGroup layout;
public View messageLayout;
public MessageHolder message;
public TextView text;
public Button acceptButton;
public Button declineButton;
public TextView date;
public ImageView status;
public IntroductionHolder(View v, int type) {
super(v);
layout = (ViewGroup) v.findViewById(R.id.introductionLayout);
messageLayout = v.findViewById(R.id.messageLayout);
message = new MessageHolder(messageLayout,
type == INTRODUCTION_IN ? MSG_IN : MSG_OUT);
text = (TextView) v.findViewById(R.id.introductionText);
acceptButton = (Button) v.findViewById(R.id.acceptButton);
declineButton = (Button) v.findViewById(R.id.declineButton);
date = (TextView) v.findViewById(R.id.introductionTime);
if (type == INTRODUCTION_OUT) {
status = (ImageView) v.findViewById(R.id.introductionStatus);
}
}
}
protected class NoticeHolder extends RecyclerView.ViewHolder {
public ViewGroup layout;
public TextView text;
public TextView date;
public ImageView status;
public NoticeHolder(View v, int type) {
super(v);
layout = (ViewGroup) v.findViewById(R.id.noticeLayout);
text = (TextView) v.findViewById(R.id.noticeText);
date = (TextView) v.findViewById(R.id.noticeTime);
if (type == NOTICE_OUT) {
status = (ImageView) v.findViewById(R.id.noticeStatus);
}
}
}
public interface IntroductionHandler {
void respondToIntroduction(final SessionId sessionId,
final boolean accept);
}
}