Refactor ConversationAdapter and its ConversationItems

This commit is contained in:
Torsten Grote
2016-10-18 09:38:13 -02:00
parent e00219c15f
commit 5ffcdc4e46
34 changed files with 769 additions and 1181 deletions

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout <LinearLayout
android:id="@+id/msgLayout" android:id="@+id/layout"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -11,7 +11,7 @@
android:orientation="vertical"> android:orientation="vertical">
<org.thoughtcrime.securesms.components.emoji.EmojiTextView <org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/msgBody" android:id="@+id/text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@color/briar_text_primary" android:textColor="@color/briar_text_primary"
@@ -20,7 +20,7 @@
tools:text="Short message"/> tools:text="Short message"/>
<TextView <TextView
android:id="@+id/msgTime" android:id="@+id/time"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="right|end" android:layout_gravity="right|end"

View File

@@ -7,7 +7,7 @@
android:orientation="vertical"> android:orientation="vertical">
<RelativeLayout <RelativeLayout
android:id="@+id/msgLayout" android:id="@+id/layout"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="right|end" android:layout_gravity="right|end"
@@ -16,7 +16,7 @@
android:background="@drawable/msg_out"> android:background="@drawable/msg_out">
<org.thoughtcrime.securesms.components.emoji.EmojiTextView <org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/msgBody" android:id="@+id/text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@color/briar_text_primary_inverse" android:textColor="@color/briar_text_primary_inverse"
@@ -25,12 +25,12 @@
tools:text="This is a long long long message that spans over several lines.\n\nIt ends here."/> tools:text="This is a long long long message that spans over several lines.\n\nIt ends here."/>
<TextView <TextView
android:id="@+id/msgTime" android:id="@+id/time"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_below="@+id/msgBody" android:layout_below="@+id/text"
android:layout_marginTop="@dimen/message_bubble_timestamp_margin" android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
android:maxLines="1" android:maxLines="1"
android:textColor="@color/private_message_date_inverse" android:textColor="@color/private_message_date_inverse"
@@ -38,13 +38,13 @@
tools:text="Dec 24, 13:37"/> tools:text="Dec 24, 13:37"/>
<ImageView <ImageView
android:id="@+id/msgStatus" android:id="@+id/status"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignBottom="@+id/msgTime" android:layout_alignBottom="@+id/time"
android:layout_marginLeft="@dimen/margin_medium" android:layout_marginLeft="@dimen/margin_medium"
android:layout_toEndOf="@+id/msgTime" android:layout_toEndOf="@+id/time"
android:layout_toRightOf="@+id/msgTime" android:layout_toRightOf="@+id/time"
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
tools:src="@drawable/message_delivered_white"/> tools:src="@drawable/message_delivered_white"/>

View File

@@ -7,7 +7,7 @@
android:orientation="vertical"> android:orientation="vertical">
<org.thoughtcrime.securesms.components.emoji.EmojiTextView <org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/msgBody" android:id="@+id/msgText"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="left|start" android:layout_gravity="left|start"
@@ -20,7 +20,7 @@
tools:text="Short message"/> tools:text="Short message"/>
<RelativeLayout <RelativeLayout
android:id="@+id/noticeLayout" android:id="@+id/layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/message_bubble_margin_tail" android:layout_marginLeft="@dimen/message_bubble_margin_tail"
@@ -28,7 +28,7 @@
android:background="@drawable/notice_in_bottom"> android:background="@drawable/notice_in_bottom">
<org.thoughtcrime.securesms.components.emoji.EmojiTextView <org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/introductionText" android:id="@+id/text"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minWidth="80dp" android:minWidth="80dp"
@@ -39,28 +39,17 @@
tools:text="@string/forum_invitation_received"/> tools:text="@string/forum_invitation_received"/>
<TextView <TextView
android:id="@+id/introductionTime" android:id="@+id/time"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignEnd="@+id/introductionText" android:layout_alignEnd="@+id/text"
android:layout_alignRight="@+id/introductionText" android:layout_alignRight="@+id/text"
android:layout_below="@+id/showInvitationsButton" android:layout_below="@+id/text"
android:layout_marginTop="@dimen/message_bubble_timestamp_margin" android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
android:textColor="@color/private_message_date" android:textColor="@color/private_message_date"
android:textSize="@dimen/text_size_tiny" android:textSize="@dimen/text_size_tiny"
tools:text="Dec 24, 13:37"/> tools:text="Dec 24, 13:37"/>
<Button
android:id="@+id/showInvitationsButton"
style="@style/BriarButtonFlat.Positive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignEnd="@+id/introductionText"
android:layout_alignRight="@+id/introductionText"
android:layout_below="@+id/introductionText"
android:layout_marginBottom="-15dp"
tools:text="@string/forum_show_invitations"/>
</RelativeLayout> </RelativeLayout>
</LinearLayout> </LinearLayout>

View File

@@ -7,7 +7,7 @@
android:orientation="vertical"> android:orientation="vertical">
<org.thoughtcrime.securesms.components.emoji.EmojiTextView <org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/msgBody" android:id="@+id/msgText"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/message_bubble_margin_non_tail" android:layout_marginLeft="@dimen/message_bubble_margin_non_tail"
@@ -19,7 +19,7 @@
tools:text="This is a long long long message that spans over several lines.\n\nIt ends here."/> tools:text="This is a long long long message that spans over several lines.\n\nIt ends here."/>
<RelativeLayout <RelativeLayout
android:id="@+id/noticeLayout" android:id="@+id/layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/message_bubble_margin_non_tail" android:layout_marginLeft="@dimen/message_bubble_margin_non_tail"
@@ -27,7 +27,7 @@
android:background="@drawable/notice_out_bottom"> android:background="@drawable/notice_out_bottom">
<org.thoughtcrime.securesms.components.emoji.EmojiTextView <org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/introductionText" android:id="@+id/text"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@color/briar_text_secondary" android:textColor="@color/briar_text_secondary"
@@ -37,25 +37,25 @@
tools:text="@string/introduction_request_received"/> tools:text="@string/introduction_request_received"/>
<TextView <TextView
android:id="@+id/introductionTime" android:id="@+id/time"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_below="@+id/introductionText" android:layout_below="@+id/text"
android:layout_marginTop="@dimen/message_bubble_timestamp_margin" android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
android:textColor="@color/private_message_date" android:textColor="@color/private_message_date"
android:textSize="@dimen/text_size_tiny" android:textSize="@dimen/text_size_tiny"
tools:text="Dec 24, 13:37"/> tools:text="Dec 24, 13:37"/>
<ImageView <ImageView
android:id="@+id/introductionStatus" android:id="@+id/status"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignBottom="@+id/introductionTime" android:layout_alignBottom="@+id/time"
android:layout_marginLeft="@dimen/margin_medium" android:layout_marginLeft="@dimen/margin_medium"
android:layout_toEndOf="@+id/introductionTime" android:layout_toEndOf="@+id/time"
android:layout_toRightOf="@+id/introductionTime" android:layout_toRightOf="@+id/time"
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
tools:src="@drawable/message_delivered"/> tools:src="@drawable/message_delivered"/>

View File

@@ -7,7 +7,7 @@
android:orientation="vertical"> android:orientation="vertical">
<org.thoughtcrime.securesms.components.emoji.EmojiTextView <org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/msgBody" android:id="@+id/msgText"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/message_bubble_margin_tail" android:layout_marginLeft="@dimen/message_bubble_margin_tail"
@@ -19,7 +19,7 @@
tools:text="Short message"/> tools:text="Short message"/>
<RelativeLayout <RelativeLayout
android:id="@+id/noticeLayout" android:id="@+id/layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/message_bubble_margin_tail" android:layout_marginLeft="@dimen/message_bubble_margin_tail"
@@ -27,7 +27,7 @@
android:background="@drawable/notice_in_bottom"> android:background="@drawable/notice_in_bottom">
<org.thoughtcrime.securesms.components.emoji.EmojiTextView <org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/introductionText" android:id="@+id/text"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minWidth="80dp" android:minWidth="80dp"
@@ -38,11 +38,11 @@
tools:text="@string/introduction_request_received"/> tools:text="@string/introduction_request_received"/>
<TextView <TextView
android:id="@+id/introductionTime" android:id="@+id/time"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignEnd="@+id/introductionText" android:layout_alignEnd="@+id/text"
android:layout_alignRight="@+id/introductionText" android:layout_alignRight="@+id/text"
android:layout_below="@+id/declineButton" android:layout_below="@+id/declineButton"
android:layout_marginTop="@dimen/message_bubble_timestamp_margin" android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
android:textColor="@color/private_message_date" android:textColor="@color/private_message_date"
@@ -54,9 +54,9 @@
style="@style/BriarButtonFlat.Positive" style="@style/BriarButtonFlat.Positive"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignEnd="@+id/introductionText" android:layout_alignEnd="@+id/text"
android:layout_alignRight="@+id/introductionText" android:layout_alignRight="@+id/text"
android:layout_below="@+id/introductionText" android:layout_below="@+id/text"
android:text="@string/accept"/> android:text="@string/accept"/>
<Button <Button
@@ -64,7 +64,7 @@
style="@style/BriarButtonFlat.Negative" style="@style/BriarButtonFlat.Negative"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/introductionText" android:layout_below="@+id/text"
android:layout_marginBottom="-15dp" android:layout_marginBottom="-15dp"
android:layout_toLeftOf="@+id/acceptButton" android:layout_toLeftOf="@+id/acceptButton"
android:layout_toStartOf="@+id/acceptButton" android:layout_toStartOf="@+id/acceptButton"

View File

@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/notice_in"
android:orientation="vertical"
android:layout_marginLeft="@dimen/message_bubble_margin_tail"
android:layout_marginRight="@dimen/message_bubble_margin_non_tail">
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/noticeText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:textSize="@dimen/text_size_medium"
android:textStyle="italic"
android:textColor="@color/briar_text_secondary"
tools:text="@string/introduction_response_accepted_received"/>
<TextView
android:id="@+id/noticeTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|end"
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
android:maxLines="1"
android:textColor="@color/private_message_date"
android:textSize="@dimen/text_size_tiny"
tools:text="Dec 24, 13:37"/>
</LinearLayout>

View File

@@ -1,52 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|end"
android:background="@drawable/notice_out"
android:layout_marginLeft="@dimen/message_bubble_margin_non_tail"
android:layout_marginRight="@dimen/message_bubble_margin_tail">
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/noticeText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:textSize="@dimen/text_size_medium"
android:textStyle="italic"
android:textColor="@color/briar_text_secondary"
tools:text="@string/introduction_response_accepted_sent"/>
<TextView
android:id="@+id/noticeTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/noticeText"
android:textColor="@color/private_message_date"
android:textSize="@dimen/text_size_tiny"
tools:text="Dec 24, 13:37"/>
<ImageView
android:id="@+id/noticeStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/noticeTime"
android:layout_marginLeft="@dimen/margin_medium"
android:layout_toEndOf="@+id/noticeTime"
android:layout_toRightOf="@+id/noticeTime"
tools:ignore="ContentDescription"
tools:src="@drawable/message_delivered"/>
</RelativeLayout>
</LinearLayout>

View File

@@ -133,7 +133,6 @@
<string name="introduction_request_sent">You have asked to introduce %1$s to %2$s.</string> <string name="introduction_request_sent">You have asked to introduce %1$s to %2$s.</string>
<string name="introduction_request_received">%1$s has asked to introduce you to %2$s. Do you want to add %2$s to your contact list?</string> <string name="introduction_request_received">%1$s has asked to introduce you to %2$s. Do you want to add %2$s to your contact list?</string>
<string name="introduction_request_exists_received">%1$s has asked to introduce you to %2$s, but %2$s is already in your contact list. Since %1$s might not know that, you can still respond:</string> <string name="introduction_request_exists_received">%1$s has asked to introduce you to %2$s, but %2$s is already in your contact list. Since %1$s might not know that, you can still respond:</string>
<string name="introduction_request_for_our_identity_received">%1$s has asked to introduce you to %2$s, but %2$s is one of your other identities, so you cannot accept the introduction:</string>
<string name="introduction_request_answered_received">%1$s has asked to introduce you to %2$s.</string> <string name="introduction_request_answered_received">%1$s has asked to introduce you to %2$s.</string>
<string name="introduction_response_accepted_sent">You accepted the introduction to %1$s.</string> <string name="introduction_response_accepted_sent">You accepted the introduction to %1$s.</string>
<string name="introduction_response_declined_sent">You declined the introduction to %1$s.</string> <string name="introduction_response_declined_sent">You declined the introduction to %1$s.</string>
@@ -215,7 +214,6 @@
<string name="forum_share_error">There was an error sharing this forum.</string> <string name="forum_share_error">There was an error sharing this forum.</string>
<string name="forum_invitation_received">%1$s has shared the forum \"%2$s\" with you.</string> <string name="forum_invitation_received">%1$s has shared the forum \"%2$s\" with you.</string>
<string name="forum_invitation_sent">You have shared the forum \"%1$s\" with %2$s.</string> <string name="forum_invitation_sent">You have shared the forum \"%1$s\" with %2$s.</string>
<string name="forum_show_invitations">Show Forum Invitations</string>
<string name="forum_invitations_title">Forum Invitations</string> <string name="forum_invitations_title">Forum Invitations</string>
<string name="forum_invitation_exists">You accepted an invitation to this forum already. Accepting more invitations will grow and strengthen the communication in the forum.</string> <string name="forum_invitation_exists">You accepted an invitation to this forum already. Accepting more invitations will grow and strengthen the communication in the forum.</string>
<string name="forum_joined_toast">Joined Forum</string> <string name="forum_joined_toast">Joined Forum</string>
@@ -279,7 +277,6 @@
<string name="blogs_sharing_response_declined_received">%s declined the blog invitation.</string> <string name="blogs_sharing_response_declined_received">%s declined the blog invitation.</string>
<string name="blogs_sharing_invitation_received">%1$s has shared the personal blog of %2$s with you.</string> <string name="blogs_sharing_invitation_received">%1$s has shared the personal blog of %2$s with you.</string>
<string name="blogs_sharing_invitation_sent">You have shared the personal blog of %1$s with %2$s.</string> <string name="blogs_sharing_invitation_sent">You have shared the personal blog of %1$s with %2$s.</string>
<string name="blogs_sharing_show_invitations">Show Blog Invitations</string>
<string name="blogs_sharing_invitations_title">Blog Invitations</string> <string name="blogs_sharing_invitations_title">Blog Invitations</string>
<string name="blogs_sharing_exists">You are subscribed to this blog already. Accepting again can lead to faster blog post delivery.</string> <string name="blogs_sharing_exists">You are subscribed to this blog already. Accepting again can lead to faster blog post delivery.</string>
<string name="blogs_sharing_joined_toast">Subscribed to Blog</string> <string name="blogs_sharing_joined_toast">Subscribed to Blog</string>

View File

@@ -276,24 +276,28 @@ public class ContactListFragment extends BaseFragment implements EventListener {
IntroductionRequestReceivedEvent m = IntroductionRequestReceivedEvent m =
(IntroductionRequestReceivedEvent) e; (IntroductionRequestReceivedEvent) e;
IntroductionRequest ir = m.getIntroductionRequest(); IntroductionRequest ir = m.getIntroductionRequest();
updateItem(m.getContactId(), ConversationItem.from(ir)); updateItem(m.getContactId(),
ConversationItem.from(getContext(), "", ir));
} else if (e instanceof IntroductionResponseReceivedEvent) { } else if (e instanceof IntroductionResponseReceivedEvent) {
LOG.info("Introduction response received, updating item"); LOG.info("Introduction response received, updating item");
IntroductionResponseReceivedEvent m = IntroductionResponseReceivedEvent m =
(IntroductionResponseReceivedEvent) e; (IntroductionResponseReceivedEvent) e;
IntroductionResponse ir = m.getIntroductionResponse(); IntroductionResponse ir = m.getIntroductionResponse();
updateItem(m.getContactId(), ConversationItem.from(ir)); updateItem(m.getContactId(),
ConversationItem.from(getContext(), "", ir));
} else if (e instanceof InvitationRequestReceivedEvent) { } else if (e instanceof InvitationRequestReceivedEvent) {
LOG.info("Invitation request received, updating item"); LOG.info("Invitation request received, updating item");
InvitationRequestReceivedEvent m = (InvitationRequestReceivedEvent) e; InvitationRequestReceivedEvent m = (InvitationRequestReceivedEvent) e;
InvitationRequest ir = m.getRequest(); InvitationRequest ir = m.getRequest();
updateItem(m.getContactId(), ConversationItem.from(ir)); updateItem(m.getContactId(),
ConversationItem.from(getContext(), "", ir));
} else if (e instanceof InvitationResponseReceivedEvent) { } else if (e instanceof InvitationResponseReceivedEvent) {
LOG.info("Invitation response received, updating item"); LOG.info("Invitation response received, updating item");
InvitationResponseReceivedEvent m = InvitationResponseReceivedEvent m =
(InvitationResponseReceivedEvent) e; (InvitationResponseReceivedEvent) e;
InvitationResponse ir = m.getResponse(); InvitationResponse ir = m.getResponse();
updateItem(m.getContactId(), ConversationItem.from(ir)); updateItem(m.getContactId(),
ConversationItem.from(getContext(), "", ir));
} }
} }

View File

@@ -6,8 +6,6 @@ import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import static org.briarproject.android.contact.ConversationItem.IncomingItem;
// This class is NOT thread-safe // This class is NOT thread-safe
public class ContactListItem { public class ContactListItem {
@@ -34,8 +32,8 @@ public class ContactListItem {
empty = empty && message == null; empty = empty && message == null;
if (message != null) { if (message != null) {
if (message.getTime() > timestamp) timestamp = message.getTime(); if (message.getTime() > timestamp) timestamp = message.getTime();
if (message instanceof IncomingItem && if (message instanceof ConversationInItem &&
!((IncomingItem) message).isRead()) !((ConversationInItem) message).isRead())
unread++; unread++;
} }
} }

View File

@@ -3,6 +3,7 @@ package org.briarproject.android.contact;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.UiThread;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat; import android.support.v4.app.ActivityOptionsCompat;
@@ -27,7 +28,8 @@ import org.briarproject.R;
import org.briarproject.android.ActivityComponent; import org.briarproject.android.ActivityComponent;
import org.briarproject.android.BriarActivity; import org.briarproject.android.BriarActivity;
import org.briarproject.android.api.AndroidNotificationManager; import org.briarproject.android.api.AndroidNotificationManager;
import org.briarproject.android.contact.ConversationAdapter.IntroductionHandler; import org.briarproject.android.contact.ConversationAdapter.RequestListener;
import org.briarproject.android.contact.ConversationItem.PartialItem;
import org.briarproject.android.introduction.IntroductionActivity; import org.briarproject.android.introduction.IntroductionActivity;
import org.briarproject.android.view.BriarRecyclerView; import org.briarproject.android.view.BriarRecyclerView;
import org.briarproject.android.view.TextInputView; import org.briarproject.android.view.TextInputView;
@@ -39,6 +41,7 @@ import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager; import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.crypto.CryptoExecutor; import org.briarproject.api.crypto.CryptoExecutor;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchContactException; import org.briarproject.api.db.NoSuchContactException;
import org.briarproject.api.event.ContactConnectedEvent; import org.briarproject.api.event.ContactConnectedEvent;
@@ -92,16 +95,15 @@ import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.OnHidePromptListener; import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.OnHidePromptListener;
import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation; import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
import static android.support.v7.util.SortedList.INVALID_POSITION;
import static android.widget.Toast.LENGTH_SHORT; import static android.widget.Toast.LENGTH_SHORT;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.android.contact.ConversationItem.IncomingItem;
import static org.briarproject.android.contact.ConversationItem.OutgoingItem;
import static org.briarproject.android.fragment.SettingsFragment.SETTINGS_NAMESPACE; import static org.briarproject.android.fragment.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH; import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
public class ConversationActivity extends BriarActivity public class ConversationActivity extends BriarActivity
implements EventListener, IntroductionHandler, TextInputListener { implements EventListener, RequestListener, TextInputListener {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(ConversationActivity.class.getName()); Logger.getLogger(ConversationActivity.class.getName());
@@ -117,7 +119,7 @@ public class ConversationActivity extends BriarActivity
@CryptoExecutor @CryptoExecutor
Executor cryptoExecutor; Executor cryptoExecutor;
private final Map<MessageId, byte[]> bodyCache = new ConcurrentHashMap<>(); private final Map<MessageId, String> bodyCache = new ConcurrentHashMap<>();
private ConversationAdapter adapter; private ConversationAdapter adapter;
private Toolbar toolbar; private Toolbar toolbar;
@@ -325,7 +327,6 @@ public class ConversationActivity extends BriarActivity
toolbarStatus toolbarStatus
.setContentDescription(getString(R.string.offline)); .setContentDescription(getString(R.string.offline));
} }
adapter.setContactName(contactName);
} }
}); });
} }
@@ -399,33 +400,42 @@ public class ConversationActivity extends BriarActivity
Collection<InvitationMessage> invitations) { Collection<InvitationMessage> invitations) {
int size = headers.size() + introductions.size() + invitations.size(); int size = headers.size() + introductions.size() + invitations.size();
List<ConversationItem> items = new ArrayList<>(size); List<ConversationItem> items = new ArrayList<>(size);
for (PrivateMessageHeader h : headers) {
ConversationMessageItem item = ConversationItem.from(h); for (PrivateMessageHeader h : headers) {
byte[] body = bodyCache.get(h.getId()); ConversationItem item = ConversationItem.from(h);
if (body == null) loadMessageBody(h.getId()); String body = bodyCache.get(h.getId());
else item.setBody(body); if (body == null) loadMessageBody(h.getId());
items.add(item); else ((PartialItem) item).setText(body);
} items.add(item);
for (IntroductionMessage im : introductions) {
if (im instanceof IntroductionRequest) {
IntroductionRequest ir = (IntroductionRequest) im;
items.add(ConversationItem.from(ir));
} else {
IntroductionResponse ir = (IntroductionResponse) im;
items.add(ConversationItem.from(ConversationActivity.this,
contactName, ir));
} }
} for (IntroductionMessage m : introductions) {
for (InvitationMessage im : invitations) { ConversationItem item;
if (im instanceof InvitationRequest) { if (m instanceof IntroductionRequest) {
InvitationRequest ir = (InvitationRequest) im; item = ConversationItem
items.add(ConversationItem.from(ir)); .from(ConversationActivity.this,
} else if (im instanceof InvitationResponse) { contactName,
InvitationResponse ir = (InvitationResponse) im; (IntroductionRequest) m);
items.add(ConversationItem.from(ConversationActivity.this, } else {
contactName, ir)); item = ConversationItem
.from(ConversationActivity.this,
contactName,
(IntroductionResponse) m);
}
items.add(item);
}
for (InvitationMessage i : invitations) {
if (i instanceof InvitationRequest) {
InvitationRequest r = (InvitationRequest) i;
items.add(ConversationItem
.from(ConversationActivity.this,
contactName, r));
} else if (i instanceof InvitationResponse) {
InvitationResponse r = (InvitationResponse) i;
items.add(ConversationItem
.from(ConversationActivity.this,
contactName, r));
}
} }
}
return items; return items;
} }
@@ -439,7 +449,7 @@ public class ConversationActivity extends BriarActivity
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Loading body took " + duration + " ms"); LOG.info("Loading body took " + duration + " ms");
displayMessageBody(m, body); displayMessageBody(m, StringUtils.fromUtf8(body));
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
@@ -448,17 +458,17 @@ public class ConversationActivity extends BriarActivity
}); });
} }
private void displayMessageBody(final MessageId m, final byte[] body) { private void displayMessageBody(final MessageId m, final String body) {
runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
bodyCache.put(m, body); bodyCache.put(m, body);
SparseArray<ConversationMessageItem> messages = SparseArray<ConversationItem> messages =
adapter.getPrivateMessages(); adapter.getPrivateMessages();
for (int i = 0; i < messages.size(); i++) { for (int i = 0; i < messages.size(); i++) {
ConversationMessageItem item = messages.valueAt(i); ConversationItem item = messages.valueAt(i);
if (item.getId().equals(m)) { if (item.getId().equals(m)) {
item.setBody(body); ((PartialItem) item).setText(body);
adapter.notifyItemChanged(messages.keyAt(i)); adapter.notifyItemChanged(messages.keyAt(i));
list.scrollToPosition(adapter.getItemCount() - 1); list.scrollToPosition(adapter.getItemCount() - 1);
return; return;
@@ -482,9 +492,9 @@ public class ConversationActivity extends BriarActivity
private void markMessagesRead() { private void markMessagesRead() {
Map<MessageId, GroupId> unread = new HashMap<>(); Map<MessageId, GroupId> unread = new HashMap<>();
SparseArray<IncomingItem> list = adapter.getIncomingMessages(); SparseArray<ConversationInItem> list = adapter.getIncomingMessages();
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
IncomingItem item = list.valueAt(i); ConversationInItem item = list.valueAt(i);
if (!item.isRead()) if (!item.isRead())
unread.put(item.getId(), item.getGroupId()); unread.put(item.getId(), item.getGroupId());
} }
@@ -561,7 +571,8 @@ public class ConversationActivity extends BriarActivity
if (event.getContactId().equals(contactId)) { if (event.getContactId().equals(contactId)) {
LOG.info("Introduction request received, adding..."); LOG.info("Introduction request received, adding...");
IntroductionRequest ir = event.getIntroductionRequest(); IntroductionRequest ir = event.getIntroductionRequest();
ConversationItem item = new ConversationIntroductionInItem(ir); ConversationItem item =
ConversationRequestItem.from(this, contactName, ir);
addConversationItem(item); addConversationItem(item);
} }
} else if (e instanceof IntroductionResponseReceivedEvent) { } else if (e instanceof IntroductionResponseReceivedEvent) {
@@ -580,7 +591,8 @@ public class ConversationActivity extends BriarActivity
if (event.getContactId().equals(contactId)) { if (event.getContactId().equals(contactId)) {
LOG.info("Invitation received, adding..."); LOG.info("Invitation received, adding...");
InvitationRequest ir = event.getRequest(); InvitationRequest ir = event.getRequest();
ConversationItem item = ConversationItem.from(ir); ConversationItem item =
ConversationItem.from(this, contactName, ir);
addConversationItem(item); addConversationItem(item);
} }
} else if (e instanceof InvitationResponseReceivedEvent) { } else if (e instanceof InvitationResponseReceivedEvent) {
@@ -603,9 +615,10 @@ public class ConversationActivity extends BriarActivity
public void run() { public void run() {
adapter.incrementRevision(); adapter.incrementRevision();
Set<MessageId> messages = new HashSet<>(messageIds); Set<MessageId> messages = new HashSet<>(messageIds);
SparseArray<OutgoingItem> list = adapter.getOutgoingMessages(); SparseArray<ConversationOutItem> list =
adapter.getOutgoingMessages();
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
OutgoingItem item = list.valueAt(i); ConversationOutItem item = list.valueAt(i);
if (messages.contains(item.getId())) { if (messages.contains(item.getId())) {
item.setSent(sent); item.setSent(sent);
item.setSeen(seen); item.setSeen(seen);
@@ -622,7 +635,7 @@ public class ConversationActivity extends BriarActivity
text = StringUtils.truncateUtf8(text, MAX_PRIVATE_MESSAGE_BODY_LENGTH); text = StringUtils.truncateUtf8(text, MAX_PRIVATE_MESSAGE_BODY_LENGTH);
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
timestamp = Math.max(timestamp, getMinTimestampForNewMessage()); timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
createMessage(StringUtils.toUtf8(text), timestamp); createMessage(text, timestamp);
textInputView.setText(""); textInputView.setText("");
} }
@@ -632,14 +645,14 @@ public class ConversationActivity extends BriarActivity
return item == null ? 0 : item.getTime() + 1; return item == null ? 0 : item.getTime() + 1;
} }
private void createMessage(final byte[] body, final long timestamp) { private void createMessage(final String body, final long timestamp) {
cryptoExecutor.execute(new Runnable() { cryptoExecutor.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
storeMessage(privateMessageFactory.createPrivateMessage( storeMessage(privateMessageFactory.createPrivateMessage(
groupId, timestamp, null, "text/plain", body), groupId, timestamp, null, "text/plain",
body); StringUtils.toUtf8(body)), body);
} catch (FormatException e) { } catch (FormatException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -647,7 +660,7 @@ public class ConversationActivity extends BriarActivity
}); });
} }
private void storeMessage(final PrivateMessage m, final byte[] body) { private void storeMessage(final PrivateMessage m, final String body) {
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -661,8 +674,8 @@ public class ConversationActivity extends BriarActivity
PrivateMessageHeader h = new PrivateMessageHeader(id, PrivateMessageHeader h = new PrivateMessageHeader(id,
groupId, m.getMessage().getTimestamp(), groupId, m.getMessage().getTimestamp(),
m.getContentType(), true, false, false, false); m.getContentType(), true, false, false, false);
ConversationMessageItem item = ConversationItem.from(h); ConversationItem item = ConversationItem.from(h);
item.setBody(body); ((PartialItem) item).setText(body);
bodyCache.put(id, body); bodyCache.put(id, body);
addConversationItem(item); addConversationItem(item);
} catch (DbException e) { } catch (DbException e) {
@@ -812,23 +825,35 @@ public class ConversationActivity extends BriarActivity
}); });
} }
@UiThread
@Override @Override
public void respondToIntroduction(final SessionId sessionId, public void respondToRequest(final ConversationRequestItem item,
final boolean accept) { final boolean accept) {
int position = adapter.findItemPosition(item);
if (position != INVALID_POSITION) {
adapter.notifyItemChanged(position, item);
}
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
timestamp = Math.max(timestamp, getMinTimestampForNewMessage()); timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
try { try {
if (accept) { switch (item.getRequestType()) {
introductionManager.acceptIntroduction(contactId, case INTRODUCTION:
sessionId, timestamp); respondToIntroductionRequest(item.getSessionId(),
} else { accept, timestamp);
introductionManager.declineIntroduction(contactId, break;
sessionId, timestamp); case FORUM:
respondToForumRequest(item.getSessionId(), accept);
break;
case BLOG:
respondToBlogRequest(item.getSessionId(), accept);
break;
default:
throw new IllegalArgumentException(
"Unknown Request Type");
} }
loadMessages();
} catch (DbException | FormatException e) { } catch (DbException | FormatException e) {
introductionResponseError(); introductionResponseError();
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
@@ -839,6 +864,31 @@ public class ConversationActivity extends BriarActivity
}); });
} }
@DatabaseExecutor
private void respondToIntroductionRequest(SessionId sessionId,
boolean accept, long time) throws DbException, FormatException {
if (accept) {
introductionManager.acceptIntroduction(contactId, sessionId, time);
} else {
introductionManager.declineIntroduction(contactId, sessionId, time);
}
loadMessages();
}
@DatabaseExecutor
private void respondToForumRequest(SessionId id, boolean accept)
throws DbException {
forumSharingManager.respondToInvitation(id, accept);
loadMessages();
}
@DatabaseExecutor
private void respondToBlogRequest(SessionId id, boolean accept)
throws DbException {
blogSharingManager.respondToInvitation(id, accept);
loadMessages();
}
private void introductionResponseError() { private void introductionResponseError() {
runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override

View File

@@ -1,365 +1,74 @@
package org.briarproject.android.contact; package org.briarproject.android.contact;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.android.sharing.InvitationsBlogActivity;
import org.briarproject.android.sharing.InvitationsForumActivity;
import org.briarproject.android.util.AndroidUtils;
import org.briarproject.android.util.BriarAdapter; import org.briarproject.android.util.BriarAdapter;
import org.briarproject.api.blogs.BlogInvitationRequest;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.forum.ForumInvitationRequest;
import org.briarproject.api.introduction.IntroductionRequest;
import org.briarproject.api.messaging.PrivateMessageHeader;
import org.briarproject.api.sharing.InvitationRequest;
import org.briarproject.util.StringUtils;
import static android.support.v7.widget.RecyclerView.ViewHolder; class ConversationAdapter
import static android.view.View.GONE; extends BriarAdapter<ConversationItem, ConversationItemViewHolder> {
import static android.view.View.VISIBLE;
import static org.briarproject.android.contact.ConversationItem.BLOG_INVITATION_IN;
import static org.briarproject.android.contact.ConversationItem.BLOG_INVITATION_OUT;
import static org.briarproject.android.contact.ConversationItem.FORUM_INVITATION_IN;
import static org.briarproject.android.contact.ConversationItem.FORUM_INVITATION_OUT;
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.IncomingItem;
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;
class ConversationAdapter extends BriarAdapter<ConversationItem, ViewHolder> { private RequestListener listener;
private IntroductionHandler intro; ConversationAdapter(Context ctx, RequestListener requestListener) {
private String contactName;
ConversationAdapter(Context ctx, IntroductionHandler introductionHandler) {
super(ctx, ConversationItem.class); super(ctx, ConversationItem.class);
intro = introductionHandler; listener = requestListener;
}
void setContactName(String contactName) {
this.contactName = contactName;
notifyDataSetChanged();
} }
@LayoutRes
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
return items.get(position).getType(); ConversationItem item = items.get(position);
} if (item instanceof ConversationRequestItem) {
return R.layout.list_item_conversation_request;
@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_msg_notice_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);
} else if (type == FORUM_INVITATION_IN || type == BLOG_INVITATION_IN) {
v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_shareable_invitation_in, viewGroup,
false);
return new InvitationHolder(v, type);
} else if (type == FORUM_INVITATION_OUT ||
type == BLOG_INVITATION_OUT) {
v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_msg_notice_out, viewGroup, false);
return new InvitationHolder(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);
}
}
@Override
public void onBindViewHolder(ViewHolder ui, int position) {
ConversationItem item = getItemAt(position);
if (item instanceof ConversationMessageItem) {
bindMessage((MessageHolder) ui, (ConversationMessageItem) item);
} 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) { } else if (item instanceof ConversationNoticeOutItem) {
bindNotice((NoticeHolder) ui, (ConversationNoticeOutItem) item); return R.layout.list_item_conversation_notice_out;
} else if (item instanceof ConversationNoticeInItem) { } else if (item instanceof ConversationNoticeInItem) {
bindNotice((NoticeHolder) ui, (ConversationNoticeInItem) item); return R.layout.list_item_conversation_notice_in;
} else if (item instanceof ConversationShareableInvitationOutItem) { } else if (item instanceof ConversationMessageOutItem) {
bindInvitation((InvitationHolder) ui, return R.layout.list_item_conversation_msg_out;
(ConversationShareableInvitationOutItem) item); } else if (item instanceof ConversationMessageInItem) {
} else if (item instanceof ConversationShareableInvitationInItem) { return R.layout.list_item_conversation_msg_in;
bindInvitation((InvitationHolder) ui,
(ConversationShareableInvitationInItem) item);
} else { } else {
throw new IllegalArgumentException("Unhandled Conversation Item"); throw new IllegalArgumentException("Unknown ConversationItem");
} }
} }
private void bindMessage(MessageHolder ui, ConversationMessageItem item) { @Override
PrivateMessageHeader header = item.getHeader(); public ConversationItemViewHolder onCreateViewHolder(ViewGroup viewGroup,
@LayoutRes int type) {
if (item instanceof ConversationItem.OutgoingItem) { View v = LayoutInflater.from(viewGroup.getContext()).inflate(
if (((OutgoingItem) item).isSeen()) { type, viewGroup, false);
ui.status.setImageResource(R.drawable.message_delivered_white); switch (type) {
} else if (((OutgoingItem) item).isSent()) { case R.layout.list_item_conversation_msg_in:
ui.status.setImageResource(R.drawable.message_sent_white); return new ConversationItemViewHolder(v);
} else { case R.layout.list_item_conversation_msg_out:
ui.status.setImageResource(R.drawable.message_stored_white); return new ConversationMessageOutViewHolder(v);
} case R.layout.list_item_conversation_notice_in:
} else { return new ConversationNoticeInViewHolder(v);
if (item.getType() == MSG_IN_UNREAD) { case R.layout.list_item_conversation_notice_out:
// TODO implement new unread message highlight according to #232 return new ConversationNoticeOutViewHolder(v);
/* int left = ui.layout.getPaddingLeft(); case R.layout.list_item_conversation_request:
int top = ui.layout.getPaddingTop(); return new ConversationRequestViewHolder(v);
int right = ui.layout.getPaddingRight(); default:
int bottom = ui.layout.getPaddingBottom(); throw new IllegalArgumentException("Unknown ConversationItem");
// show unread messages in different color to not miss them
ui.layout.setBackgroundResource(R.drawable.msg_in_unread);
// 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);
*/
}
}
if (item.getBody() == null) {
ui.body.setText("\u2026");
} else if (header.getContentType().equals("text/plain")) {
ui.body.setText(
StringUtils.trim(StringUtils.fromUtf8(item.getBody())));
} else {
// TODO support other content types
}
long timestamp = header.getTimestamp();
ui.date.setText(AndroidUtils.formatDate(ctx, timestamp));
}
private void bindIntroduction(IntroductionHolder ui,
final ConversationIntroductionItem item, final int position) {
final IntroductionRequest ir = item.getIntroductionRequest();
int backgroundRes;
String message = ir.getMessage();
if (StringUtils.isNullOrEmpty(message)) {
ui.message.setVisibility(GONE);
if (item instanceof ConversationIntroductionOutItem) {
backgroundRes = R.drawable.notice_out;
} else {
backgroundRes = R.drawable.notice_in;
}
} else {
ui.message.setText(StringUtils.trim(message));
ui.message.setVisibility(VISIBLE);
if (item instanceof ConversationIntroductionOutItem) {
backgroundRes = R.drawable.notice_out_bottom;
} else {
backgroundRes = R.drawable.notice_in_bottom;
}
}
// 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()) {
//noinspection ConstantConditions
ui.status.setImageResource(R.drawable.message_delivered);
} else if (i.isSent()) {
//noinspection ConstantConditions
ui.status.setImageResource(R.drawable.message_sent);
} else {
//noinspection ConstantConditions
ui.status.setImageResource(R.drawable.message_stored);
}
}
// 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(GONE);
ui.declineButton.setVisibility(GONE);
}
// Incoming Introduction Request (Not Answered)
else {
if (item.getIntroductionRequest().contactExists()) {
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()));
}
if (item.getIntroductionRequest().doesIntroduceOtherIdentity()) {
// don't allow accept when one of our identities is introduced
ui.acceptButton.setVisibility(GONE);
ui.text.setText(ctx.getString(
R.string.introduction_request_for_our_identity_received,
contactName, ir.getName()));
} else {
ui.acceptButton.setVisibility(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(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(AndroidUtils.formatDate(ctx, item.getTime()));
ui.notice.setBackgroundResource(backgroundRes);
}
private void bindNotice(NoticeHolder ui, ConversationNoticeItem item) {
ui.text.setText(item.getText());
ui.date.setText(AndroidUtils.formatDate(ctx, item.getTime()));
if (item instanceof ConversationNoticeOutItem) {
ConversationNoticeOutItem n = (ConversationNoticeOutItem) item;
if (n.isSeen()) {
//noinspection ConstantConditions
ui.status.setImageResource(R.drawable.message_delivered);
} else if (n.isSent()) {
//noinspection ConstantConditions
ui.status.setImageResource(R.drawable.message_sent);
} else {
//noinspection ConstantConditions
ui.status.setImageResource(R.drawable.message_stored);
}
} }
} }
private void bindInvitation(InvitationHolder ui, @Override
final ConversationShareableInvitationItem item) { public void onBindViewHolder(ConversationItemViewHolder ui, int position) {
ConversationItem item = items.get(position);
final InvitationRequest ir = item.getInvitationRequest(); if (item instanceof ConversationRequestItem) {
String name = ""; ((ConversationRequestViewHolder) ui).bind(item, listener);
int receivedRes = 0, sentRes = 0, buttonRes = 0, backgroundRes;
if (ir instanceof ForumInvitationRequest) {
name = ((ForumInvitationRequest) ir).getForumName();
receivedRes = R.string.forum_invitation_received;
sentRes = R.string.forum_invitation_sent;
buttonRes = R.string.forum_show_invitations;
} else if (ir instanceof BlogInvitationRequest) {
name = ((BlogInvitationRequest) ir).getBlogAuthorName();
receivedRes = R.string.blogs_sharing_invitation_received;
sentRes = R.string.blogs_sharing_invitation_sent;
buttonRes = R.string.blogs_sharing_show_invitations;
}
String message = ir.getMessage();
if (StringUtils.isNullOrEmpty(message)) {
ui.message.setVisibility(GONE);
if (item instanceof ConversationShareableInvitationOutItem) {
backgroundRes = R.drawable.notice_out;
} else {
backgroundRes = R.drawable.notice_in;
}
} else { } else {
ui.message.setVisibility(VISIBLE); ui.bind(item);
ui.message.setText(StringUtils.trim(message));
if (item instanceof ConversationShareableInvitationOutItem) {
backgroundRes = R.drawable.notice_out_bottom;
} else {
backgroundRes = R.drawable.notice_in_bottom;
}
} }
// Outgoing Invitation
if (item instanceof ConversationShareableInvitationOutItem) {
ui.text.setText(ctx.getString(sentRes, name, contactName));
ConversationShareableInvitationOutItem i =
(ConversationShareableInvitationOutItem) item;
if (i.isSeen()) {
//noinspection ConstantConditions
ui.status.setImageResource(R.drawable.message_delivered);
} else if (i.isSent()) {
//noinspection ConstantConditions
ui.status.setImageResource(R.drawable.message_sent);
} else {
//noinspection ConstantConditions
ui.status.setImageResource(R.drawable.message_stored);
}
}
// Incoming Invitation
else {
ui.text.setText(ctx.getString(receivedRes, contactName, name));
if (ir.isAvailable()) {
final Class c = ir instanceof ForumInvitationRequest ?
InvitationsForumActivity.class :
InvitationsBlogActivity.class;
ui.showInvitationsButton.setText(ctx.getString(buttonRes));
ui.showInvitationsButton.setVisibility(VISIBLE);
ui.showInvitationsButton
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(ctx, c);
ctx.startActivity(i);
}
});
} else {
ui.showInvitationsButton.setVisibility(GONE);
}
}
ui.date.setText(AndroidUtils.formatDate(ctx, item.getTime()));
ui.notice.setBackgroundResource(backgroundRes);
} }
@Override @Override
@@ -393,138 +102,46 @@ class ConversationAdapter extends BriarAdapter<ConversationItem, ViewHolder> {
} }
} }
SparseArray<IncomingItem> getIncomingMessages() { SparseArray<ConversationInItem> getIncomingMessages() {
SparseArray<IncomingItem> messages = new SparseArray<>(); SparseArray<ConversationInItem> messages = new SparseArray<>();
for (int i = 0; i < items.size(); i++) { for (int i = 0; i < items.size(); i++) {
ConversationItem item = items.get(i); ConversationItem item = items.get(i);
if (item instanceof IncomingItem) { if (item instanceof ConversationInItem) {
messages.put(i, (IncomingItem) item); messages.put(i, (ConversationInItem) item);
} }
} }
return messages; return messages;
} }
SparseArray<OutgoingItem> getOutgoingMessages() { SparseArray<ConversationOutItem> getOutgoingMessages() {
SparseArray<OutgoingItem> messages = new SparseArray<>(); SparseArray<ConversationOutItem> messages = new SparseArray<>();
for (int i = 0; i < items.size(); i++) { for (int i = 0; i < items.size(); i++) {
ConversationItem item = items.get(i); ConversationItem item = items.get(i);
if (item instanceof OutgoingItem) { if (item instanceof ConversationOutItem) {
messages.put(i, (OutgoingItem) item); messages.put(i, (ConversationOutItem) item);
} }
} }
return messages; return messages;
} }
SparseArray<ConversationMessageItem> getPrivateMessages() { SparseArray<ConversationItem> getPrivateMessages() {
SparseArray<ConversationMessageItem> messages = new SparseArray<>(); SparseArray<ConversationItem> messages = new SparseArray<>();
for (int i = 0; i < items.size(); i++) { for (int i = 0; i < items.size(); i++) {
ConversationItem item = items.get(i); ConversationItem item = items.get(i);
if (item instanceof ConversationMessageItem) { if (item instanceof ConversationMessageInItem) {
messages.put(i, (ConversationMessageItem) item); messages.put(i, item);
} else if (item instanceof ConversationMessageOutItem) {
messages.put(i, item);
} }
} }
return messages; return messages;
} }
private static class MessageHolder extends RecyclerView.ViewHolder { interface RequestListener {
void respondToRequest(ConversationRequestItem item, boolean accept);
public ViewGroup layout;
public TextView body;
private TextView date;
public ImageView status;
private MessageHolder(View v, int type) {
super(v);
layout = (ViewGroup) v.findViewById(R.id.msgLayout);
body = (TextView) v.findViewById(R.id.msgBody);
date = (TextView) v.findViewById(R.id.msgTime);
// outgoing message (local)
if (type == MSG_OUT) {
status = (ImageView) v.findViewById(R.id.msgStatus);
}
}
} }
private static class IntroductionHolder extends RecyclerView.ViewHolder {
private final TextView message;
private final ViewGroup notice;
private final TextView text;
private final Button acceptButton;
private final Button declineButton;
private final TextView date;
private final ImageView status;
private IntroductionHolder(View v, int type) {
super(v);
message = (TextView) v.findViewById(R.id.msgBody);
notice = (ViewGroup) v.findViewById(R.id.noticeLayout);
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);
} else {
status = null;
}
}
}
private static class NoticeHolder extends RecyclerView.ViewHolder {
private final TextView text;
private final TextView date;
private final ImageView status;
private NoticeHolder(View v, int type) {
super(v);
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);
} else {
status = null;
}
}
}
private static class InvitationHolder extends RecyclerView.ViewHolder {
private final TextView message;
private final View notice;
private final TextView text;
private final Button showInvitationsButton;
private final TextView date;
private final ImageView status;
private InvitationHolder(View v, int type) {
super(v);
message = (TextView) v.findViewById(R.id.msgBody);
text = (TextView) v.findViewById(R.id.introductionText);
notice = v.findViewById(R.id.noticeLayout);
showInvitationsButton = (Button) v.findViewById(R.id.showInvitationsButton);
date = (TextView) v.findViewById(R.id.introductionTime);
if (type == FORUM_INVITATION_OUT || type == BLOG_INVITATION_OUT) {
status = (ImageView) v.findViewById(R.id.introductionStatus);
} else {
status = null;
}
}
}
interface IntroductionHandler {
void respondToIntroduction(SessionId sessionId, boolean accept);
}
} }

View File

@@ -0,0 +1,31 @@
package org.briarproject.android.contact;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import org.jetbrains.annotations.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe
@NotNullByDefault
abstract class ConversationInItem extends ConversationItem {
private boolean read;
ConversationInItem(MessageId id, GroupId groupId, @Nullable String text,
long time, boolean read) {
super(id, groupId, text, time);
this.read = read;
}
public boolean isRead() {
return read;
}
public void setRead(boolean read) {
this.read = read;
}
}

View File

@@ -1,33 +0,0 @@
package org.briarproject.android.contact;
import org.briarproject.android.contact.ConversationItem.IncomingItem;
import org.briarproject.api.introduction.IntroductionRequest;
import org.jetbrains.annotations.NotNull;
// This class is not thread-safe
class ConversationIntroductionInItem extends ConversationIntroductionItem
implements IncomingItem {
private boolean read;
ConversationIntroductionInItem(@NotNull IntroductionRequest ir) {
super(ir);
this.read = ir.isRead();
}
@Override
int getType() {
return INTRODUCTION_IN;
}
@Override
public boolean isRead() {
return read;
}
@Override
public void setRead(boolean read) {
this.read = read;
}
}

View File

@@ -1,31 +0,0 @@
package org.briarproject.android.contact;
import org.briarproject.api.introduction.IntroductionRequest;
import org.jetbrains.annotations.NotNull;
// This class is not thread-safe
abstract class ConversationIntroductionItem extends ConversationItem {
private final IntroductionRequest ir;
private boolean answered;
ConversationIntroductionItem(@NotNull IntroductionRequest ir) {
super(ir.getMessageId(), ir.getGroupId(), ir.getTimestamp());
this.ir = ir;
this.answered = ir.wasAnswered();
}
@NotNull
IntroductionRequest getIntroductionRequest() {
return ir;
}
boolean wasAnswered() {
return answered;
}
void setAnswered(boolean answered) {
this.answered = answered;
}
}

View File

@@ -1,47 +0,0 @@
package org.briarproject.android.contact;
import org.briarproject.api.introduction.IntroductionRequest;
/**
* This class is needed and can not be replaced by an ConversationNoticeOutItem,
* because it carries the optional introduction message
* to be displayed as a regular private message.
*
* This class is not thread-safe
*/
class ConversationIntroductionOutItem extends ConversationIntroductionItem
implements ConversationItem.OutgoingItem {
private boolean sent, seen;
ConversationIntroductionOutItem(IntroductionRequest ir) {
super(ir);
this.sent = ir.isSent();
this.seen = ir.isSeen();
}
@Override
int getType() {
return INTRODUCTION_OUT;
}
@Override
public boolean isSent() {
return sent;
}
@Override
public void setSent(boolean sent) {
this.sent = sent;
}
@Override
public boolean isSeen() {
return seen;
}
@Override
public void setSeen(boolean seen) {
this.seen = seen;
}
}

View File

@@ -1,65 +1,64 @@
package org.briarproject.android.contact; package org.briarproject.android.contact;
import android.content.Context; import android.content.Context;
import android.support.annotation.StringRes;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.api.blogs.BlogInvitationResponse; import org.briarproject.android.contact.ConversationRequestItem.RequestType;
import org.briarproject.api.blogs.BlogInvitationRequest;
import org.briarproject.api.forum.ForumInvitationRequest;
import org.briarproject.api.forum.ForumInvitationResponse; import org.briarproject.api.forum.ForumInvitationResponse;
import org.briarproject.api.introduction.IntroductionMessage;
import org.briarproject.api.introduction.IntroductionRequest; import org.briarproject.api.introduction.IntroductionRequest;
import org.briarproject.api.introduction.IntroductionResponse; import org.briarproject.api.introduction.IntroductionResponse;
import org.briarproject.api.messaging.PrivateMessageHeader; import org.briarproject.api.messaging.PrivateMessageHeader;
import org.briarproject.api.sharing.InvitationMessage; import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.sharing.InvitationRequest; import org.briarproject.api.sharing.InvitationRequest;
import org.briarproject.api.sharing.InvitationResponse; import org.briarproject.api.sharing.InvitationResponse;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable;
// This class is not thread-safe import javax.annotation.concurrent.NotThreadSafe;
public abstract class ConversationItem {
// this is needed for RecyclerView adapter which requires an int type import static org.briarproject.android.contact.ConversationRequestItem.RequestType.BLOG;
final static int MSG_IN = 0; import static org.briarproject.android.contact.ConversationRequestItem.RequestType.FORUM;
final static int MSG_IN_UNREAD = 1; import static org.briarproject.android.contact.ConversationRequestItem.RequestType.INTRODUCTION;
final static int MSG_OUT = 2;
final static int INTRODUCTION_IN = 3; @NotThreadSafe
final static int INTRODUCTION_OUT = 4; @NotNullByDefault
final static int NOTICE_IN = 5; abstract class ConversationItem {
final static int NOTICE_OUT = 6;
final static int FORUM_INVITATION_IN = 7;
final static int FORUM_INVITATION_OUT = 8;
final static int BLOG_INVITATION_IN = 9;
final static int BLOG_INVITATION_OUT = 10;
final private MessageId id; final private MessageId id;
final private GroupId groupId; final private GroupId groupId;
protected @Nullable String text;
final private long time; final private long time;
public ConversationItem(@NotNull MessageId id, @NotNull GroupId groupId, ConversationItem(MessageId id, GroupId groupId,
long time) { @Nullable String text, long time) {
this.id = id; this.id = id;
this.groupId = groupId; this.groupId = groupId;
this.text = text;
this.time = time; this.time = time;
} }
abstract int getType(); MessageId getId() {
@NotNull
public MessageId getId() {
return id; return id;
} }
@NotNull GroupId getGroupId() {
public GroupId getGroupId() {
return groupId; return groupId;
} }
@Nullable
public String getText() {
return text;
}
long getTime() { long getTime() {
return time; return time;
} }
public static ConversationMessageItem from(PrivateMessageHeader h) { static ConversationItem from(PrivateMessageHeader h) {
if (h.isLocal()) { if (h.isLocal()) {
return new ConversationMessageOutItem(h); return new ConversationMessageOutItem(h);
} else { } else {
@@ -67,17 +66,40 @@ public abstract class ConversationItem {
} }
} }
public static ConversationIntroductionItem from(IntroductionRequest ir) { static ConversationItem from(Context ctx, String contactName,
IntroductionRequest ir) {
if (ir.isLocal()) { if (ir.isLocal()) {
return new ConversationIntroductionOutItem(ir); String text = ctx.getString(R.string.introduction_request_sent,
contactName, ir.getName());
return new ConversationNoticeOutItem(ir.getMessageId(),
ir.getGroupId(), text, ir.getMessage(), ir.getTimestamp(),
ir.isSent(), ir.isSeen());
} else { } else {
return new ConversationIntroductionInItem(ir); String text;
if (ir.wasAnswered()) {
text = ctx.getString(
R.string.introduction_request_answered_received,
contactName, ir.getName());
return new ConversationNoticeInItem(ir.getMessageId(),
ir.getGroupId(), text, ir.getMessage(), ir.getTimestamp(),
ir.isRead());
} else if (ir.contactExists()){
text = ctx.getString(
R.string.introduction_request_exists_received,
contactName, ir.getName());
} else {
text = ctx.getString(R.string.introduction_request_received,
contactName, ir.getName());
}
return new ConversationRequestItem(ir.getMessageId(),
ir.getGroupId(), INTRODUCTION, ir.getSessionId(), text,
ir.getMessage(), ir.getTimestamp(), ir.isRead(),
ir.wasAnswered());
} }
} }
public static ConversationNoticeItem from(Context ctx, String contactName, static ConversationItem from(Context ctx, String contactName,
IntroductionResponse ir) { IntroductionResponse ir) {
if (ir.isLocal()) { if (ir.isLocal()) {
String text; String text;
if (ir.wasAccepted()) { if (ir.wasAccepted()) {
@@ -90,7 +112,7 @@ public abstract class ConversationItem {
ir.getName()); ir.getName());
} }
return new ConversationNoticeOutItem(ir.getMessageId(), return new ConversationNoticeOutItem(ir.getMessageId(),
ir.getGroupId(), text, ir.getTimestamp(), ir.isSent(), ir.getGroupId(), text, null, ir.getTimestamp(), ir.isSent(),
ir.isSeen()); ir.isSeen());
} else { } else {
String text; String text;
@@ -110,143 +132,99 @@ public abstract class ConversationItem {
} }
} }
return new ConversationNoticeInItem(ir.getMessageId(), return new ConversationNoticeInItem(ir.getMessageId(),
ir.getGroupId(), text, ir.getTimestamp(), ir.isRead()); ir.getGroupId(), text, null, ir.getTimestamp(),
ir.isRead());
} }
} }
public static ConversationShareableInvitationItem from( static ConversationItem from(Context ctx, String contactName,
InvitationRequest fim) { InvitationRequest ir) {
if (fim.isLocal()) { if (ir.isLocal()) {
return new ConversationShareableInvitationOutItem(fim); String text;
if (ir instanceof ForumInvitationRequest) {
text = ctx.getString(R.string.forum_invitation_sent,
((ForumInvitationRequest) ir).getForumName(),
contactName);
} else {
text = ctx.getString(R.string.blogs_sharing_invitation_sent,
((BlogInvitationRequest) ir).getBlogAuthorName(),
contactName);
}
return new ConversationNoticeOutItem(ir.getId(), ir.getGroupId(),
text, ir.getMessage(), ir.getTimestamp(), ir.isSent(),
ir.isSeen());
} else { } else {
return new ConversationShareableInvitationInItem(fim); String text;
RequestType type;
if (ir instanceof ForumInvitationRequest) {
text = ctx.getString(R.string.forum_invitation_received,
contactName,
((ForumInvitationRequest) ir).getForumName());
type = FORUM;
} else {
text = ctx.getString(R.string.blogs_sharing_invitation_received,
contactName,
((BlogInvitationRequest) ir).getBlogAuthorName());
type = BLOG;
}
if (!ir.isAvailable()) {
return new ConversationNoticeInItem(ir.getId(), ir.getGroupId(),
text, ir.getMessage(), ir.getTimestamp(), ir.isRead());
}
return new ConversationRequestItem(ir.getId(),
ir.getGroupId(), type, ir.getSessionId(), text,
ir.getMessage(), ir.getTimestamp(), ir.isRead(),
!ir.isAvailable());
} }
} }
public static ConversationNoticeItem from(Context ctx, String contactName, static ConversationItem from(Context ctx, String contactName,
InvitationResponse ir) { InvitationResponse ir) {
@StringRes int res;
if (ir instanceof ForumInvitationResponse) { if (ir.isLocal()) {
return from(ctx, contactName, (ForumInvitationResponse) ir); if (ir.wasAccepted()) {
} else if (ir instanceof BlogInvitationResponse) { if (ir instanceof ForumInvitationResponse) {
return from(ctx, contactName, (BlogInvitationResponse) ir); res = R.string.forum_invitation_response_accepted_sent;
} else {
res = R.string.blogs_sharing_response_accepted_sent;
}
} else {
if (ir instanceof ForumInvitationResponse) {
res = R.string.forum_invitation_response_declined_sent;
} else {
res = R.string.blogs_sharing_response_declined_sent;
}
}
String text = ctx.getString(res, contactName);
return new ConversationNoticeOutItem(ir.getId(), ir.getGroupId(),
text, null, ir.getTimestamp(), ir.isSent(), ir.isSeen());
} else { } else {
throw new IllegalArgumentException("Unknown Invitation Response."); if (ir.wasAccepted()) {
if (ir instanceof ForumInvitationResponse) {
res = R.string.forum_invitation_response_accepted_received;
} else {
res = R.string.blogs_sharing_response_accepted_received;
}
} else {
if (ir instanceof ForumInvitationResponse) {
res = R.string.forum_invitation_response_declined_received;
} else {
res = R.string.blogs_sharing_response_declined_received;
}
}
String text = ctx.getString(res, contactName);
return new ConversationNoticeInItem(ir.getId(), ir.getGroupId(),
text, null, ir.getTimestamp(), ir.isRead());
} }
} }
private static ConversationNoticeItem from(Context ctx, String contactName, interface PartialItem {
ForumInvitationResponse fir) {
@Nullable
String getText();
void setText(String text);
if (fir.isLocal()) {
String text;
if (fir.wasAccepted()) {
text = ctx.getString(
R.string.forum_invitation_response_accepted_sent,
contactName);
} else {
text = ctx.getString(
R.string.forum_invitation_response_declined_sent,
contactName);
}
return new ConversationNoticeOutItem(fir.getId(), fir.getGroupId(),
text, fir.getTimestamp(), fir.isSent(), fir.isSeen());
} else {
String text;
if (fir.wasAccepted()) {
text = ctx.getString(
R.string.forum_invitation_response_accepted_received,
contactName);
} else {
text = ctx.getString(
R.string.forum_invitation_response_declined_received,
contactName);
}
return new ConversationNoticeInItem(fir.getId(), fir.getGroupId(),
text, fir.getTimestamp(), fir.isRead());
}
} }
private static ConversationNoticeItem from(Context ctx, String contactName,
BlogInvitationResponse fir) {
if (fir.isLocal()) {
String text;
if (fir.wasAccepted()) {
text = ctx.getString(
R.string.blogs_sharing_response_accepted_sent,
contactName);
} else {
text = ctx.getString(
R.string.blogs_sharing_response_declined_sent,
contactName);
}
return new ConversationNoticeOutItem(fir.getId(), fir.getGroupId(),
text, fir.getTimestamp(), fir.isSent(), fir.isSeen());
} else {
String text;
if (fir.wasAccepted()) {
text = ctx.getString(
R.string.blogs_sharing_response_accepted_received,
contactName);
} else {
text = ctx.getString(
R.string.blogs_sharing_response_declined_received,
contactName);
}
return new ConversationNoticeInItem(fir.getId(), fir.getGroupId(),
text, fir.getTimestamp(), fir.isRead());
}
}
/**
* This method should not be used to get user-facing objects,
* Its purpose is only to provide data for the contact list.
*/
public static ConversationItem from(IntroductionMessage im) {
if (im.isLocal())
return new ConversationNoticeOutItem(im.getMessageId(),
im.getGroupId(), "", im.getTimestamp(), false, false);
return new ConversationNoticeInItem(im.getMessageId(), im.getGroupId(),
"", im.getTimestamp(), im.isRead());
}
/**
* This method should not be used to get user-facing objects,
* Its purpose is only to provide data for the contact list.
*/
public static ConversationItem from(InvitationMessage im) {
if (im.isLocal())
return new ConversationNoticeOutItem(im.getId(), im.getGroupId(),
"", im.getTimestamp(), false, false);
return new ConversationNoticeInItem(im.getId(), im.getGroupId(), "",
im.getTimestamp(), im.isRead());
}
interface OutgoingItem {
@NotNull
MessageId getId();
boolean isSent();
void setSent(boolean sent);
boolean isSeen();
void setSeen(boolean seen);
}
interface IncomingItem {
@NotNull
MessageId getId();
@NotNull
GroupId getGroupId();
boolean isRead();
void setRead(boolean read);
}
} }

View File

@@ -0,0 +1,42 @@
package org.briarproject.android.contact;
import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.android.util.AndroidUtils;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.util.StringUtils;
@UiThread
@NotNullByDefault
class ConversationItemViewHolder extends ViewHolder {
protected final ViewGroup layout;
private final TextView text;
private final TextView time;
ConversationItemViewHolder(View v) {
super(v);
layout = (ViewGroup) v.findViewById(R.id.layout);
text = (TextView) v.findViewById(R.id.text);
time = (TextView) v.findViewById(R.id.time);
}
@CallSuper
void bind(ConversationItem item) {
if (item.getText() == null) {
text.setText("\u2026");
} else {
text.setText(StringUtils.trim(item.getText()));
}
long timestamp = item.getTime();
time.setText(AndroidUtils.formatDate(time.getContext(), timestamp));
}
}

View File

@@ -1,31 +1,22 @@
package org.briarproject.android.contact; package org.briarproject.android.contact;
import org.briarproject.android.contact.ConversationItem.PartialItem;
import org.briarproject.api.messaging.PrivateMessageHeader; import org.briarproject.api.messaging.PrivateMessageHeader;
import org.briarproject.api.nullsafety.NotNullByDefault;
// This class is not thread-safe import javax.annotation.concurrent.NotThreadSafe;
class ConversationMessageInItem extends ConversationMessageItem
implements ConversationItem.IncomingItem {
private boolean read; @NotThreadSafe
@NotNullByDefault
class ConversationMessageInItem extends ConversationInItem
implements PartialItem {
ConversationMessageInItem(PrivateMessageHeader header) { ConversationMessageInItem(PrivateMessageHeader h) {
super(header); super(h.getId(), h.getGroupId(), null, h.getTimestamp(), h.isRead());
read = header.isRead();
} }
@Override public void setText(String body) {
int getType() { text = body;
return MSG_IN;
} }
@Override
public boolean isRead() {
return read;
}
@Override
public void setRead(boolean read) {
this.read = read;
}
} }

View File

@@ -1,29 +0,0 @@
package org.briarproject.android.contact;
import org.briarproject.api.messaging.PrivateMessageHeader;
// This class is not thread-safe
abstract class ConversationMessageItem extends ConversationItem {
private final PrivateMessageHeader header;
private byte[] body;
ConversationMessageItem(PrivateMessageHeader header) {
super(header.getId(), header.getGroupId(), header.getTimestamp());
this.header = header;
body = null;
}
PrivateMessageHeader getHeader() {
return header;
}
byte[] getBody() {
return body;
}
void setBody(byte[] body) {
this.body = body;
}
}

View File

@@ -1,42 +1,23 @@
package org.briarproject.android.contact; package org.briarproject.android.contact;
import org.briarproject.android.contact.ConversationItem.PartialItem;
import org.briarproject.api.messaging.PrivateMessageHeader; import org.briarproject.api.messaging.PrivateMessageHeader;
import org.briarproject.api.nullsafety.NotNullByDefault;
// This class is not thread-safe import javax.annotation.concurrent.NotThreadSafe;
class ConversationMessageOutItem extends ConversationMessageItem
implements ConversationItem.OutgoingItem {
private boolean sent, seen; @NotThreadSafe
@NotNullByDefault
class ConversationMessageOutItem extends ConversationOutItem
implements PartialItem {
ConversationMessageOutItem(PrivateMessageHeader header) { ConversationMessageOutItem(PrivateMessageHeader h) {
super(header); super(h.getId(), h.getGroupId(), null, h.getTimestamp(), h.isSent(),
h.isSeen());
sent = header.isSent();
seen = header.isSeen();
} }
@Override public void setText(String body) {
int getType() { text = body;
return MSG_OUT;
} }
@Override
public boolean isSent() {
return sent;
}
@Override
public void setSent(boolean sent) {
this.sent = sent;
}
@Override
public boolean isSeen() {
return seen;
}
@Override
public void setSeen(boolean seen) {
this.seen = seen;
}
} }

View File

@@ -0,0 +1,16 @@
package org.briarproject.android.contact;
import android.view.View;
class ConversationMessageOutViewHolder extends ConversationOutItemViewHolder {
ConversationMessageOutViewHolder(View v) {
super(v);
}
@Override
protected boolean hasDarkBackground() {
return true;
}
}

View File

@@ -1,33 +1,29 @@
package org.briarproject.android.contact; package org.briarproject.android.contact;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
import org.jetbrains.annotations.Nullable;
// This class is not thread-safe import javax.annotation.concurrent.NotThreadSafe;
class ConversationNoticeInItem extends ConversationNoticeItem
implements ConversationItem.IncomingItem {
private boolean read; @NotThreadSafe
@NotNullByDefault
class ConversationNoticeInItem extends ConversationInItem {
ConversationNoticeInItem(MessageId id, GroupId groupId, String text, @Nullable
long time, boolean read) { private final String msgText;
super(id, groupId, text, time);
this.read = read; ConversationNoticeInItem(MessageId id, GroupId groupId,
String text, @Nullable String msgText, long time,
boolean read) {
super(id, groupId, text, time, read);
this.msgText = msgText;
} }
@Override @Nullable
int getType() { public String getMsgText() {
return NOTICE_IN; return msgText;
} }
@Override
public boolean isRead() {
return read;
}
@Override
public void setRead(boolean read) {
this.read = read;
}
} }

View File

@@ -0,0 +1,43 @@
package org.briarproject.android.contact;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.util.StringUtils;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
@UiThread
@NotNullByDefault
class ConversationNoticeInViewHolder extends ConversationItemViewHolder {
private final TextView msgText;
ConversationNoticeInViewHolder(View v) {
super(v);
msgText = (TextView) v.findViewById(R.id.msgText);
}
@Override
void bind(ConversationItem conversationItem) {
super.bind(conversationItem);
ConversationNoticeInItem item =
(ConversationNoticeInItem) conversationItem;
String message = item.getMsgText();
if (StringUtils.isNullOrEmpty(message)) {
msgText.setVisibility(GONE);
layout.setBackgroundResource(R.drawable.notice_in);
} else {
msgText.setVisibility(VISIBLE);
msgText.setText(StringUtils.trim(message));
layout.setBackgroundResource(R.drawable.notice_in_bottom);
}
}
}

View File

@@ -1,20 +0,0 @@
package org.briarproject.android.contact;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
abstract class ConversationNoticeItem extends ConversationItem {
private final String text;
ConversationNoticeItem(MessageId id, GroupId groupId, String text,
long time) {
super(id, groupId, time);
this.text = text;
}
public String getText() {
return text;
}
}

View File

@@ -1,44 +1,29 @@
package org.briarproject.android.contact; package org.briarproject.android.contact;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
import org.jetbrains.annotations.Nullable;
// This class is not thread-safe import javax.annotation.concurrent.NotThreadSafe;
class ConversationNoticeOutItem extends ConversationNoticeItem
implements ConversationItem.OutgoingItem {
private boolean sent, seen; @NotThreadSafe
@NotNullByDefault
class ConversationNoticeOutItem extends ConversationOutItem {
ConversationNoticeOutItem(MessageId id, GroupId groupId, String text, @Nullable
long time, boolean sent, boolean seen) { private final String msgText;
super(id, groupId, text, time);
this.sent = sent; ConversationNoticeOutItem(MessageId id, GroupId groupId,
this.seen = seen; String text, @Nullable String msgText, long time,
boolean sent, boolean seen) {
super(id, groupId, text, time, sent, seen);
this.msgText = msgText;
} }
@Override @Nullable
int getType() { public String getMsgText() {
return NOTICE_OUT; return msgText;
} }
@Override
public boolean isSent() {
return sent;
}
@Override
public void setSent(boolean sent) {
this.sent = sent;
}
@Override
public boolean isSeen() {
return seen;
}
@Override
public void setSeen(boolean seen) {
this.seen = seen;
}
} }

View File

@@ -0,0 +1,48 @@
package org.briarproject.android.contact;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.util.StringUtils;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
@UiThread
@NotNullByDefault
class ConversationNoticeOutViewHolder extends ConversationOutItemViewHolder {
private final TextView msgText;
ConversationNoticeOutViewHolder(View v) {
super(v);
msgText = (TextView) v.findViewById(R.id.msgText);
}
@Override
void bind(ConversationItem conversationItem) {
super.bind(conversationItem);
ConversationNoticeOutItem item =
(ConversationNoticeOutItem) conversationItem;
String message = item.getMsgText();
if (StringUtils.isNullOrEmpty(message)) {
msgText.setVisibility(GONE);
layout.setBackgroundResource(R.drawable.notice_out);
} else {
msgText.setVisibility(VISIBLE);
msgText.setText(StringUtils.trim(message));
layout.setBackgroundResource(R.drawable.notice_out_bottom);
}
}
@Override
protected boolean hasDarkBackground() {
return false;
}
}

View File

@@ -0,0 +1,40 @@
package org.briarproject.android.contact;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import org.jetbrains.annotations.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe
@NotNullByDefault
abstract class ConversationOutItem extends ConversationItem {
private boolean sent, seen;
ConversationOutItem(MessageId id, GroupId groupId, @Nullable String text,
long time, boolean sent, boolean seen) {
super(id, groupId, text, time);
this.sent = sent;
this.seen = seen;
}
public boolean isSent() {
return sent;
}
public void setSent(boolean sent) {
this.sent = sent;
}
public boolean isSeen() {
return seen;
}
public void setSeen(boolean seen) {
this.seen = seen;
}
}

View File

@@ -0,0 +1,44 @@
package org.briarproject.android.contact;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.ImageView;
import org.briarproject.R;
import org.briarproject.api.nullsafety.NotNullByDefault;
@UiThread
@NotNullByDefault
abstract class ConversationOutItemViewHolder
extends ConversationItemViewHolder {
private final ImageView status;
ConversationOutItemViewHolder(View v) {
super(v);
status = (ImageView) v.findViewById(R.id.status);
}
@Override
void bind(ConversationItem conversationItem) {
super.bind(conversationItem);
ConversationOutItem item = (ConversationOutItem) conversationItem;
int res;
if (item.isSeen()) {
if (hasDarkBackground()) res = R.drawable.message_delivered_white;
else res = R.drawable.message_delivered;
} else if (item.isSent()) {
if (hasDarkBackground()) res = R.drawable.message_sent_white;
else res = R.drawable.message_sent;
} else {
if (hasDarkBackground()) res = R.drawable.message_stored_white;
else res = R.drawable.message_stored;
}
status.setImageResource(res);
}
protected abstract boolean hasDarkBackground();
}

View File

@@ -0,0 +1,46 @@
package org.briarproject.android.contact;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import org.jetbrains.annotations.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe
@NotNullByDefault
class ConversationRequestItem extends ConversationNoticeInItem {
enum RequestType { INTRODUCTION, FORUM, BLOG };
private final RequestType requestType;
private final SessionId sessionId;
private boolean answered;
ConversationRequestItem(MessageId id, GroupId groupId,
RequestType requestType, SessionId sessionId, String text,
@Nullable String msgText, long time, boolean read,
boolean answered) {
super(id, groupId, text, msgText, time, read);
this.requestType = requestType;
this.sessionId = sessionId;
this.answered = answered;
}
public RequestType getRequestType() {
return requestType;
}
public SessionId getSessionId() {
return sessionId;
}
boolean wasAnswered() {
return answered;
}
void setAnswered(boolean answered) {
this.answered = answered;
}
}

View File

@@ -0,0 +1,58 @@
package org.briarproject.android.contact;
import android.support.annotation.UiThread;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import org.briarproject.R;
import org.briarproject.android.contact.ConversationAdapter.RequestListener;
import org.briarproject.api.nullsafety.NotNullByDefault;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
@UiThread
@NotNullByDefault
class ConversationRequestViewHolder extends ConversationNoticeInViewHolder {
private final Button acceptButton;
private final Button declineButton;
ConversationRequestViewHolder(View v) {
super(v);
acceptButton = (Button) v.findViewById(R.id.acceptButton);
declineButton = (Button) v.findViewById(R.id.declineButton);
}
void bind(ConversationItem conversationItem,
final RequestListener listener) {
super.bind(conversationItem);
final ConversationRequestItem item =
(ConversationRequestItem) conversationItem;
if (item.wasAnswered()) {
acceptButton.setVisibility(GONE);
declineButton.setVisibility(GONE);
} else {
acceptButton.setVisibility(VISIBLE);
acceptButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
item.setAnswered(true);
listener.respondToRequest(item, true);
}
});
declineButton.setVisibility(VISIBLE);
declineButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
item.setAnswered(true);
listener.respondToRequest(item, false);
}
});
}
}
}

View File

@@ -1,43 +0,0 @@
package org.briarproject.android.contact;
import org.briarproject.api.blogs.BlogInvitationRequest;
import org.briarproject.api.forum.ForumInvitationRequest;
import org.briarproject.api.sharing.InvitationRequest;
// This class is not thread-safe
class ConversationShareableInvitationInItem
extends ConversationShareableInvitationItem
implements ConversationItem.IncomingItem {
private final int type;
private boolean read;
ConversationShareableInvitationInItem(InvitationRequest ir) {
super(ir);
if (ir instanceof ForumInvitationRequest) {
this.type = FORUM_INVITATION_IN;
} else if (ir instanceof BlogInvitationRequest) {
this.type = BLOG_INVITATION_IN;
} else {
throw new IllegalArgumentException("Unknown Invitation Type.");
}
this.read = ir.isRead();
}
@Override
int getType() {
return type;
}
@Override
public boolean isRead() {
return read;
}
@Override
public void setRead(boolean read) {
this.read = read;
}
}

View File

@@ -1,18 +0,0 @@
package org.briarproject.android.contact;
import org.briarproject.api.sharing.InvitationRequest;
abstract class ConversationShareableInvitationItem extends ConversationItem {
private final InvitationRequest fim;
ConversationShareableInvitationItem(InvitationRequest fim) {
super(fim.getId(), fim.getGroupId(), fim.getTimestamp());
this.fim = fim;
}
InvitationRequest getInvitationRequest() {
return fim;
}
}

View File

@@ -1,60 +0,0 @@
package org.briarproject.android.contact;
import org.briarproject.api.blogs.BlogInvitationRequest;
import org.briarproject.api.forum.ForumInvitationRequest;
import org.briarproject.api.sharing.InvitationRequest;
/**
* This class is needed and can not be replaced by an ConversationNoticeOutItem,
* because it carries the optional invitation message
* to be displayed as a regular private message.
* <p/>
* This class is not thread-safe
*/
class ConversationShareableInvitationOutItem
extends ConversationShareableInvitationItem
implements ConversationItem.OutgoingItem {
private final int type;
private boolean sent, seen;
ConversationShareableInvitationOutItem(InvitationRequest ir) {
super(ir);
if (ir instanceof ForumInvitationRequest) {
this.type = FORUM_INVITATION_OUT;
} else if (ir instanceof BlogInvitationRequest) {
this.type = BLOG_INVITATION_OUT;
} else {
throw new IllegalArgumentException("Unknown Invitation Type.");
}
this.sent = ir.isSent();
this.seen = ir.isSeen();
}
@Override
int getType() {
return type;
}
@Override
public boolean isSent() {
return sent;
}
@Override
public void setSent(boolean sent) {
this.sent = sent;
}
@Override
public boolean isSeen() {
return seen;
}
@Override
public void setSeen(boolean seen) {
this.seen = seen;
}
}