diff --git a/briar-android-tests/src/test/java/org/briarproject/BlogSharingIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/BlogSharingIntegrationTest.java index bca45b25f81d71816dc280fd83ca0ad86252b88f..2cc0340bcd48593f8888a4734dbefab8c1920fcb 100644 --- a/briar-android-tests/src/test/java/org/briarproject/BlogSharingIntegrationTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/BlogSharingIntegrationTest.java @@ -547,7 +547,7 @@ public class BlogSharingIntegrationTest extends BriarIntegrationTest { BlogInvitationReceivedEvent event = (BlogInvitationReceivedEvent) e; eventWaiter.assertEquals(contactId1, event.getContactId()); - Blog b = event.getBlog(); + Blog b = event.getShareable(); try { Contact c = contactManager0.getContact(contactId1); blogSharingManager0.respondToInvitation(b, c, true); @@ -589,7 +589,7 @@ public class BlogSharingIntegrationTest extends BriarIntegrationTest { (BlogInvitationReceivedEvent) e; requestReceived = true; if (!answer) return; - Blog b = event.getBlog(); + Blog b = event.getShareable(); try { eventWaiter.assertEquals(1, blogSharingManager1.getInvitations().size()); diff --git a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java index ae3e770a4e944e48fc201490cee7e8965c0876e0..6d2184a6aec574ad6ffec86bd58ab02ab6105f2f 100644 --- a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java @@ -34,7 +34,7 @@ import org.briarproject.api.identity.AuthorFactory; import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.lifecycle.LifecycleManager; -import org.briarproject.api.sharing.InvitationItem; +import org.briarproject.api.sharing.SharingInvitationItem; import org.briarproject.api.sharing.InvitationMessage; import org.briarproject.api.sync.Group; import org.briarproject.api.sync.SyncSession; @@ -762,7 +762,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase { "Sharer2 to Invitee"); // make sure we now have two invitations to the same forum available - Collection<InvitationItem> forums = + Collection<SharingInvitationItem> forums = forumSharingManager1.getInvitations(); assertEquals(1, forums.size()); assertEquals(2, forums.iterator().next().getNewSharers().size()); @@ -939,7 +939,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase { (ForumInvitationReceivedEvent) e; eventWaiter.assertEquals(contactId1, event.getContactId()); requestReceived = true; - Forum f = event.getForum(); + Forum f = event.getShareable(); try { Contact c = contactManager0.getContact(contactId1); forumSharingManager0.respondToInvitation(f, c, true); @@ -982,11 +982,11 @@ public class ForumSharingIntegrationTest extends BriarTestCase { (ForumInvitationReceivedEvent) e; requestReceived = true; if (!answer) return; - Forum f = event.getForum(); + Forum f = event.getShareable(); try { eventWaiter.assertEquals(1, forumSharingManager1.getInvitations().size()); - InvitationItem invitation = + SharingInvitationItem invitation = forumSharingManager1.getInvitations().iterator() .next(); eventWaiter.assertEquals(f, invitation.getShareable()); diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml index aad00c6d9e747d81de25845bbd0493c0f2060861..7e8006f8f049ac505841726b175497c48c479090 100644 --- a/briar-android/AndroidManifest.xml +++ b/briar-android/AndroidManifest.xml @@ -123,7 +123,17 @@ </activity> <activity - android:name=".android.sharing.InvitationsForumActivity" + android:name=".android.privategroup.invitation.GroupInvitationActivity" + android:label="@string/groups_invitations_title" + android:parentActivityName=".android.NavDrawerActivity"> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value=".android.NavDrawerActivity" + /> + </activity> + + <activity + android:name=".android.sharing.ForumInvitationActivity" android:label="@string/forum_invitations_title" android:parentActivityName=".android.NavDrawerActivity"> <meta-data @@ -133,7 +143,7 @@ </activity> <activity - android:name=".android.sharing.InvitationsBlogActivity" + android:name=".android.sharing.BlogInvitationActivity" android:label="@string/blogs_sharing_invitations_title" android:parentActivityName=".android.contact.ConversationActivity"> <meta-data @@ -187,7 +197,7 @@ </activity> <activity - android:name=".android.sharing.SharingStatusForumActivity" + android:name=".android.sharing.ForumSharingStatusActivity" android:label="@string/sharing_status" android:parentActivityName=".android.forum.ForumActivity"> <meta-data @@ -197,7 +207,7 @@ </activity> <activity - android:name=".android.sharing.SharingStatusBlogActivity" + android:name=".android.sharing.BlogSharingStatusActivity" android:label="@string/sharing_status" android:parentActivityName=".android.blogs.BlogActivity"> <meta-data diff --git a/briar-android/res/layout/list_item_msg_in.xml b/briar-android/res/layout/list_item_conversation_msg_in.xml similarity index 92% rename from briar-android/res/layout/list_item_msg_in.xml rename to briar-android/res/layout/list_item_conversation_msg_in.xml index 3c18ca1a88c15d4e3f2c22a97c109343c68c6f43..ef5c7a953b04987859556f9939050b6e02a5c897 100644 --- a/briar-android/res/layout/list_item_msg_in.xml +++ b/briar-android/res/layout/list_item_conversation_msg_in.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout - android:id="@+id/msgLayout" + android:id="@+id/layout" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" @@ -11,7 +11,7 @@ android:orientation="vertical"> <org.thoughtcrime.securesms.components.emoji.EmojiTextView - android:id="@+id/msgBody" + android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/briar_text_primary" @@ -20,7 +20,7 @@ tools:text="Short message"/> <TextView - android:id="@+id/msgTime" + android:id="@+id/time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right|end" diff --git a/briar-android/res/layout/list_item_msg_out.xml b/briar-android/res/layout/list_item_conversation_msg_out.xml similarity index 84% rename from briar-android/res/layout/list_item_msg_out.xml rename to briar-android/res/layout/list_item_conversation_msg_out.xml index 7b7f4f65cee607313a5bee6079cd26806337d3e3..dd9c7b1f8ec0b288ad6bbef262ea6773db1a03eb 100644 --- a/briar-android/res/layout/list_item_msg_out.xml +++ b/briar-android/res/layout/list_item_conversation_msg_out.xml @@ -7,7 +7,7 @@ android:orientation="vertical"> <RelativeLayout - android:id="@+id/msgLayout" + android:id="@+id/layout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right|end" @@ -16,7 +16,7 @@ android:background="@drawable/msg_out"> <org.thoughtcrime.securesms.components.emoji.EmojiTextView - android:id="@+id/msgBody" + android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" 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."/> <TextView - android:id="@+id/msgTime" + android:id="@+id/time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" - android:layout_below="@+id/msgBody" + android:layout_below="@+id/text" android:layout_marginTop="@dimen/message_bubble_timestamp_margin" android:maxLines="1" android:textColor="@color/private_message_date_inverse" @@ -38,13 +38,13 @@ tools:text="Dec 24, 13:37"/> <ImageView - android:id="@+id/msgStatus" + android:id="@+id/status" android:layout_width="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_toEndOf="@+id/msgTime" - android:layout_toRightOf="@+id/msgTime" + android:layout_toEndOf="@+id/time" + android:layout_toRightOf="@+id/time" tools:ignore="ContentDescription" tools:src="@drawable/message_delivered_white"/> diff --git a/briar-android/res/layout/list_item_shareable_invitation_in.xml b/briar-android/res/layout/list_item_conversation_notice_in.xml similarity index 71% rename from briar-android/res/layout/list_item_shareable_invitation_in.xml rename to briar-android/res/layout/list_item_conversation_notice_in.xml index b7cc1bb36655ec47bffc0306847ca1f341ec3c5b..073edf537c67a9d86d955d59e7a581dd3544240a 100644 --- a/briar-android/res/layout/list_item_shareable_invitation_in.xml +++ b/briar-android/res/layout/list_item_conversation_notice_in.xml @@ -7,7 +7,7 @@ android:orientation="vertical"> <org.thoughtcrime.securesms.components.emoji.EmojiTextView - android:id="@+id/msgBody" + android:id="@+id/msgText" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="left|start" @@ -20,7 +20,7 @@ tools:text="Short message"/> <RelativeLayout - android:id="@+id/noticeLayout" + android:id="@+id/layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/message_bubble_margin_tail" @@ -28,7 +28,7 @@ android:background="@drawable/notice_in_bottom"> <org.thoughtcrime.securesms.components.emoji.EmojiTextView - android:id="@+id/introductionText" + android:id="@+id/text" android:layout_width="match_parent" android:layout_height="wrap_content" android:minWidth="80dp" @@ -39,28 +39,17 @@ tools:text="@string/forum_invitation_received"/> <TextView - android:id="@+id/introductionTime" + android:id="@+id/time" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignEnd="@+id/introductionText" - android:layout_alignRight="@+id/introductionText" - android:layout_below="@+id/showInvitationsButton" + android:layout_alignEnd="@+id/text" + android:layout_alignRight="@+id/text" + android:layout_below="@+id/text" android:layout_marginTop="@dimen/message_bubble_timestamp_margin" android:textColor="@color/private_message_date" android:textSize="@dimen/text_size_tiny" 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> </LinearLayout> \ No newline at end of file diff --git a/briar-android/res/layout/list_item_msg_notice_out.xml b/briar-android/res/layout/list_item_conversation_notice_out.xml similarity index 84% rename from briar-android/res/layout/list_item_msg_notice_out.xml rename to briar-android/res/layout/list_item_conversation_notice_out.xml index 0d328fe5c02963444f80853a6bbe3d160e5632a8..9ea5bd96772f6ffedf371bc7a70700e6a8ed68b3 100644 --- a/briar-android/res/layout/list_item_msg_notice_out.xml +++ b/briar-android/res/layout/list_item_conversation_notice_out.xml @@ -7,7 +7,7 @@ android:orientation="vertical"> <org.thoughtcrime.securesms.components.emoji.EmojiTextView - android:id="@+id/msgBody" + android:id="@+id/msgText" android:layout_width="match_parent" android:layout_height="wrap_content" 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."/> <RelativeLayout - android:id="@+id/noticeLayout" + android:id="@+id/layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/message_bubble_margin_non_tail" @@ -27,7 +27,7 @@ android:background="@drawable/notice_out_bottom"> <org.thoughtcrime.securesms.components.emoji.EmojiTextView - android:id="@+id/introductionText" + android:id="@+id/text" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@color/briar_text_secondary" @@ -37,25 +37,25 @@ tools:text="@string/introduction_request_received"/> <TextView - android:id="@+id/introductionTime" + android:id="@+id/time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" - android:layout_below="@+id/introductionText" + android:layout_below="@+id/text" android:layout_marginTop="@dimen/message_bubble_timestamp_margin" android:textColor="@color/private_message_date" android:textSize="@dimen/text_size_tiny" tools:text="Dec 24, 13:37"/> <ImageView - android:id="@+id/introductionStatus" + android:id="@+id/status" android:layout_width="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_toEndOf="@+id/introductionTime" - android:layout_toRightOf="@+id/introductionTime" + android:layout_toEndOf="@+id/time" + android:layout_toRightOf="@+id/time" tools:ignore="ContentDescription" tools:src="@drawable/message_delivered"/> diff --git a/briar-android/res/layout/list_item_introduction_in.xml b/briar-android/res/layout/list_item_conversation_request.xml similarity index 84% rename from briar-android/res/layout/list_item_introduction_in.xml rename to briar-android/res/layout/list_item_conversation_request.xml index 9094a1fc3c53203cd07fbf153735c4cbb33c608d..3932e82e535394583f6956d405f60281d95df874 100644 --- a/briar-android/res/layout/list_item_introduction_in.xml +++ b/briar-android/res/layout/list_item_conversation_request.xml @@ -7,7 +7,7 @@ android:orientation="vertical"> <org.thoughtcrime.securesms.components.emoji.EmojiTextView - android:id="@+id/msgBody" + android:id="@+id/msgText" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/message_bubble_margin_tail" @@ -19,7 +19,7 @@ tools:text="Short message"/> <RelativeLayout - android:id="@+id/noticeLayout" + android:id="@+id/layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/message_bubble_margin_tail" @@ -27,7 +27,7 @@ android:background="@drawable/notice_in_bottom"> <org.thoughtcrime.securesms.components.emoji.EmojiTextView - android:id="@+id/introductionText" + android:id="@+id/text" android:layout_width="match_parent" android:layout_height="wrap_content" android:minWidth="80dp" @@ -38,11 +38,11 @@ tools:text="@string/introduction_request_received"/> <TextView - android:id="@+id/introductionTime" + android:id="@+id/time" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignEnd="@+id/introductionText" - android:layout_alignRight="@+id/introductionText" + android:layout_alignEnd="@+id/text" + android:layout_alignRight="@+id/text" android:layout_below="@+id/declineButton" android:layout_marginTop="@dimen/message_bubble_timestamp_margin" android:textColor="@color/private_message_date" @@ -54,9 +54,9 @@ 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_alignEnd="@+id/text" + android:layout_alignRight="@+id/text" + android:layout_below="@+id/text" android:text="@string/accept"/> <Button @@ -64,7 +64,7 @@ style="@style/BriarButtonFlat.Negative" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_below="@+id/introductionText" + android:layout_below="@+id/text" android:layout_marginBottom="-15dp" android:layout_toLeftOf="@+id/acceptButton" android:layout_toStartOf="@+id/acceptButton" diff --git a/briar-android/res/layout/list_item_notice_in.xml b/briar-android/res/layout/list_item_notice_in.xml deleted file mode 100644 index 13994b47422388050c8f289ea309b5982cee43b8..0000000000000000000000000000000000000000 --- a/briar-android/res/layout/list_item_notice_in.xml +++ /dev/null @@ -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> \ No newline at end of file diff --git a/briar-android/res/layout/list_item_notice_out.xml b/briar-android/res/layout/list_item_notice_out.xml deleted file mode 100644 index ae327bb9959cdeea6e2e338ce17d1e5be5a092e5..0000000000000000000000000000000000000000 --- a/briar-android/res/layout/list_item_notice_out.xml +++ /dev/null @@ -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> \ No newline at end of file diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml index 872800c9f05297d0fa40ae400567dfe9d37069da..878b281e48a5de9ec154e3b7b0ca69a2bf8467fe 100644 --- a/briar-android/res/values/strings.xml +++ b/briar-android/res/values/strings.xml @@ -133,7 +133,6 @@ <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_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_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> @@ -168,6 +167,21 @@ <string name="groups_leave">Leave Group</string> <string name="groups_dissolve">Dissolve Group</string> + <!-- Private Group Invitations --> + <string name="groups_invitations_title">Group Invitations</string> + <string name="groups_invitations_invitation_sent">You have invited %1$s to your group "%2$s".</string> + <string name="groups_invitations_invitation_received">%1$s has invited you to join the group "%2$s".</string> + <string name="groups_invitations_joined">Joined group</string> + <string name="groups_invitations_declined">Group invitation declined</string> + <plurals name="groups_invitations_open"> + <item quantity="one">%d open group invitation</item> + <item quantity="other">%d open group invitations</item> + </plurals> + <string name="groups_invitations_response_accepted_sent">You accepted the group invitation from %s.</string> + <string name="groups_invitations_response_declined_sent">You declined the group invitation from %s.</string> + <string name="groups_invitations_response_accepted_received">%s accepted your group invitation.</string> + <string name="groups_invitations_response_declined_received">%s declined your group invitation.</string> + <!-- Forums --> <string name="no_forums">You don\'t have any forums yet.\n\nWhy don\'t you create a new one yourself by tapping the + icon at the top?\n\nYou can also ask your contacts to share forums with you.</string> <string name="create_forum_title">New Forum</string> @@ -206,7 +220,6 @@ <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_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_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> @@ -270,7 +283,6 @@ <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_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_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> diff --git a/briar-android/src/org/briarproject/android/ActivityComponent.java b/briar-android/src/org/briarproject/android/ActivityComponent.java index 1179905b6932ced492c512debc964cc53d0ff76a..0a7776b09e0b06731f864f51c48d138b606d1987 100644 --- a/briar-android/src/org/briarproject/android/ActivityComponent.java +++ b/briar-android/src/org/briarproject/android/ActivityComponent.java @@ -33,15 +33,16 @@ import org.briarproject.android.privategroup.creation.CreateGroupFragment; import org.briarproject.android.privategroup.conversation.GroupActivity; import org.briarproject.android.privategroup.creation.CreateGroupMessageFragment; import org.briarproject.android.privategroup.list.GroupListFragment; -import org.briarproject.android.sharing.ContactSelectorFragment; -import org.briarproject.android.sharing.InvitationsBlogActivity; -import org.briarproject.android.sharing.InvitationsForumActivity; +import org.briarproject.android.privategroup.invitation.GroupInvitationActivity; import org.briarproject.android.sharing.ShareBlogActivity; -import org.briarproject.android.sharing.ShareBlogMessageFragment; +import org.briarproject.android.sharing.BlogSharingStatusActivity; +import org.briarproject.android.sharing.ContactSelectorFragment; +import org.briarproject.android.sharing.BlogInvitationActivity; +import org.briarproject.android.sharing.ForumInvitationActivity; import org.briarproject.android.sharing.ShareForumActivity; import org.briarproject.android.sharing.ShareForumMessageFragment; -import org.briarproject.android.sharing.SharingStatusBlogActivity; -import org.briarproject.android.sharing.SharingStatusForumActivity; +import org.briarproject.android.sharing.ForumSharingStatusActivity; +import org.briarproject.android.sharing.ShareBlogMessageFragment; import org.thoughtcrime.securesms.components.emoji.EmojiProvider; import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel; @@ -72,13 +73,13 @@ public interface ActivityComponent { void inject(ConversationActivity activity); - void inject(InvitationsForumActivity activity); + void inject(ForumInvitationActivity activity); - void inject(InvitationsBlogActivity activity); + void inject(BlogInvitationActivity activity); void inject(CreateGroupActivity activity); - void inject(GroupActivity activity); + void inject(GroupInvitationActivity activity); void inject(CreateForumActivity activity); @@ -86,9 +87,9 @@ public interface ActivityComponent { void inject(ShareBlogActivity activity); - void inject(SharingStatusForumActivity activity); + void inject(ForumSharingStatusActivity activity); - void inject(SharingStatusBlogActivity activity); + void inject(BlogSharingStatusActivity activity); void inject(ForumActivity activity); diff --git a/briar-android/src/org/briarproject/android/ActivityModule.java b/briar-android/src/org/briarproject/android/ActivityModule.java index 69505d32f3ea0d2f7bbef96cd5e21e97470dc57b..174a5c94668a193735716965d889c99644626b21 100644 --- a/briar-android/src/org/briarproject/android/ActivityModule.java +++ b/briar-android/src/org/briarproject/android/ActivityModule.java @@ -25,8 +25,14 @@ import org.briarproject.android.privategroup.conversation.GroupController; import org.briarproject.android.privategroup.conversation.GroupControllerImpl; import org.briarproject.android.privategroup.creation.CreateGroupController; import org.briarproject.android.privategroup.creation.CreateGroupControllerImpl; +import org.briarproject.android.privategroup.invitation.GroupInvitationController; +import org.briarproject.android.privategroup.invitation.GroupInvitationControllerImpl; import org.briarproject.android.privategroup.list.GroupListController; import org.briarproject.android.privategroup.list.GroupListControllerImpl; +import org.briarproject.android.sharing.BlogInvitationController; +import org.briarproject.android.sharing.BlogInvitationControllerImpl; +import org.briarproject.android.sharing.ForumInvitationController; +import org.briarproject.android.sharing.ForumInvitationControllerImpl; import dagger.Module; import dagger.Provides; @@ -117,6 +123,13 @@ public class ActivityModule { return groupController; } + @ActivityScope + @Provides + protected GroupInvitationController provideInvitationGroupController( + GroupInvitationControllerImpl groupInvitationController) { + return groupInvitationController; + } + @ActivityScope @Provides protected ForumController provideForumController( @@ -125,6 +138,22 @@ public class ActivityModule { return forumController; } + @ActivityScope + @Provides + protected ForumInvitationController provideInvitationForumController( + ForumInvitationControllerImpl forumInvitationController) { + activity.addLifecycleController(forumInvitationController); + return forumInvitationController; + } + + @ActivityScope + @Provides + protected BlogInvitationController provideInvitationBlogController( + BlogInvitationControllerImpl blogInvitationController) { + activity.addLifecycleController(blogInvitationController); + return blogInvitationController; + } + @ActivityScope @Provides BlogController provideBlogController(BlogControllerImpl blogController) { diff --git a/briar-android/src/org/briarproject/android/AndroidComponent.java b/briar-android/src/org/briarproject/android/AndroidComponent.java index ceb3cd9e0b94be42eaa86acd8e2e24df8658b982..6081f1da9b5172bc1c1a6d2e7a74c47f37b6ad27 100644 --- a/briar-android/src/org/briarproject/android/AndroidComponent.java +++ b/briar-android/src/org/briarproject/android/AndroidComponent.java @@ -35,6 +35,7 @@ import org.briarproject.api.messaging.PrivateMessageFactory; import org.briarproject.api.plugins.ConnectionRegistry; import org.briarproject.api.plugins.PluginManager; import org.briarproject.api.privategroup.PrivateGroupManager; +import org.briarproject.api.privategroup.invitation.GroupInvitationManager; import org.briarproject.api.settings.SettingsManager; import org.briarproject.api.system.Clock; import org.briarproject.plugins.AndroidPluginsModule; @@ -96,6 +97,8 @@ public interface AndroidComponent extends CoreEagerSingletons { PrivateGroupManager privateGroupManager(); + GroupInvitationManager groupInvitationManager(); + ForumManager forumManager(); ForumSharingManager forumSharingManager(); diff --git a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java index 8809864d55e7103f684d1262bfcf017752b4a342..ac8e2816609e4719333ca6c0c4e6ffe2cb62ae6d 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java @@ -24,7 +24,7 @@ import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener; import org.briarproject.android.controller.handler.UiResultExceptionHandler; import org.briarproject.android.fragment.BaseFragment; import org.briarproject.android.sharing.ShareBlogActivity; -import org.briarproject.android.sharing.SharingStatusBlogActivity; +import org.briarproject.android.sharing.BlogSharingStatusActivity; import org.briarproject.android.view.BriarRecyclerView; import org.briarproject.api.blogs.BlogPostHeader; import org.briarproject.api.db.DbException; @@ -164,7 +164,7 @@ public class BlogFragment extends BaseFragment implements return true; case R.id.action_blog_sharing_status: Intent i3 = new Intent(getActivity(), - SharingStatusBlogActivity.class); + BlogSharingStatusActivity.class); i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP); i3.putExtra(GROUP_ID, groupId.getBytes()); startActivity(i3, options.toBundle()); diff --git a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java index c7114857c62297168ea4ddc3757996d58b9d44c7..9f5d23971e307f06a08e63055ab8c34aa957e530 100644 --- a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java +++ b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java @@ -22,6 +22,7 @@ import org.briarproject.android.api.AndroidNotificationManager; import org.briarproject.android.fragment.BaseFragment; import org.briarproject.android.keyagreement.KeyAgreementActivity; import org.briarproject.android.view.BriarRecyclerView; +import org.briarproject.api.clients.BaseMessageHeader; import org.briarproject.api.clients.MessageTracker.GroupCount; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; @@ -270,34 +271,35 @@ public class ContactListFragment extends BaseFragment implements EventListener { LOG.info("Private message received, updating item"); PrivateMessageReceivedEvent p = (PrivateMessageReceivedEvent) e; PrivateMessageHeader h = p.getMessageHeader(); - updateItem(p.getContactId(), ConversationItem.from(h)); + updateItem(p.getContactId(), h); } else if (e instanceof IntroductionRequestReceivedEvent) { LOG.info("Introduction request received, updating item"); IntroductionRequestReceivedEvent m = (IntroductionRequestReceivedEvent) e; IntroductionRequest ir = m.getIntroductionRequest(); - updateItem(m.getContactId(), ConversationItem.from(ir)); + updateItem(m.getContactId(), ir); } else if (e instanceof IntroductionResponseReceivedEvent) { LOG.info("Introduction response received, updating item"); IntroductionResponseReceivedEvent m = (IntroductionResponseReceivedEvent) e; IntroductionResponse ir = m.getIntroductionResponse(); - updateItem(m.getContactId(), ConversationItem.from(ir)); + updateItem(m.getContactId(), ir); } else if (e instanceof InvitationRequestReceivedEvent) { - LOG.info("Invitation request received, updating item"); - InvitationRequestReceivedEvent m = (InvitationRequestReceivedEvent) e; + LOG.info("Invitation Request received, update item"); + InvitationRequestReceivedEvent m = + (InvitationRequestReceivedEvent) e; InvitationRequest ir = m.getRequest(); - updateItem(m.getContactId(), ConversationItem.from(ir)); + updateItem(m.getContactId(), ir); } else if (e instanceof InvitationResponseReceivedEvent) { LOG.info("Invitation response received, updating item"); InvitationResponseReceivedEvent m = (InvitationResponseReceivedEvent) e; InvitationResponse ir = m.getResponse(); - updateItem(m.getContactId(), ConversationItem.from(ir)); + updateItem(m.getContactId(), ir); } } - private void updateItem(final ContactId c, final ConversationItem m) { + private void updateItem(final ContactId c, final BaseMessageHeader h) { listener.runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { @@ -305,7 +307,8 @@ public class ContactListFragment extends BaseFragment implements EventListener { int position = adapter.findItemPosition(c); ContactListItem item = adapter.getItemAt(position); if (item != null) { - item.addMessage(m); + ConversationItem i = ConversationItem.from(getContext(), h); + item.addMessage(i); adapter.updateItemAt(position, item); } } diff --git a/briar-android/src/org/briarproject/android/contact/ContactListItem.java b/briar-android/src/org/briarproject/android/contact/ContactListItem.java index e90cdf939b14f6a582594bcc36dd36d4b2aa7909..e9c9e568b2e9c8853d0a17020ac7e4bf754ae646 100644 --- a/briar-android/src/org/briarproject/android/contact/ContactListItem.java +++ b/briar-android/src/org/briarproject/android/contact/ContactListItem.java @@ -6,9 +6,9 @@ import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.sync.GroupId; import org.jetbrains.annotations.NotNull; -import static org.briarproject.android.contact.ConversationItem.IncomingItem; +import javax.annotation.concurrent.NotThreadSafe; -// This class is NOT thread-safe +@NotThreadSafe public class ContactListItem { private final Contact contact; @@ -34,8 +34,7 @@ public class ContactListItem { empty = empty && message == null; if (message != null) { if (message.getTime() > timestamp) timestamp = message.getTime(); - if (message instanceof IncomingItem && - !((IncomingItem) message).isRead()) + if (!message.isRead()) unread++; } } diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java index a5496d9c709b464113bfc49999552d9427ab8b2f..7bd1dfc45fad34d51221a1c5186b6520b4348803 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java @@ -3,6 +3,7 @@ package org.briarproject.android.contact; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; +import android.support.annotation.UiThread; import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityOptionsCompat; @@ -27,7 +28,7 @@ import org.briarproject.R; import org.briarproject.android.ActivityComponent; import org.briarproject.android.BriarActivity; import org.briarproject.android.api.AndroidNotificationManager; -import org.briarproject.android.contact.ConversationAdapter.IntroductionHandler; +import org.briarproject.android.contact.ConversationAdapter.RequestListener; import org.briarproject.android.introduction.IntroductionActivity; import org.briarproject.android.view.BriarRecyclerView; import org.briarproject.android.view.TextInputView; @@ -39,6 +40,7 @@ import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactManager; import org.briarproject.api.crypto.CryptoExecutor; +import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.db.DbException; import org.briarproject.api.db.NoSuchContactException; import org.briarproject.api.event.ContactConnectedEvent; @@ -64,6 +66,7 @@ import org.briarproject.api.messaging.PrivateMessage; import org.briarproject.api.messaging.PrivateMessageFactory; import org.briarproject.api.messaging.PrivateMessageHeader; import org.briarproject.api.plugins.ConnectionRegistry; +import org.briarproject.api.privategroup.invitation.GroupInvitationManager; import org.briarproject.api.settings.Settings; import org.briarproject.api.settings.SettingsManager; import org.briarproject.api.sharing.InvitationMessage; @@ -72,6 +75,7 @@ import org.briarproject.api.sharing.InvitationResponse; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.MessageId; import org.briarproject.util.StringUtils; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collection; @@ -92,16 +96,15 @@ import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt; import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.OnHidePromptListener; 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 java.util.logging.Level.INFO; 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.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH; public class ConversationActivity extends BriarActivity - implements EventListener, IntroductionHandler, TextInputListener { + implements EventListener, RequestListener, TextInputListener { private static final Logger LOG = Logger.getLogger(ConversationActivity.class.getName()); @@ -117,7 +120,7 @@ public class ConversationActivity extends BriarActivity @CryptoExecutor Executor cryptoExecutor; - private final Map<MessageId, byte[]> bodyCache = new ConcurrentHashMap<>(); + private final Map<MessageId, String> bodyCache = new ConcurrentHashMap<>(); private ConversationAdapter adapter; private Toolbar toolbar; @@ -144,6 +147,8 @@ public class ConversationActivity extends BriarActivity volatile ForumSharingManager forumSharingManager; @Inject volatile BlogSharingManager blogSharingManager; + @Inject + volatile GroupInvitationManager groupInvitationManager; private volatile GroupId groupId = null; private volatile ContactId contactId = null; @@ -325,7 +330,6 @@ public class ConversationActivity extends BriarActivity toolbarStatus .setContentDescription(getString(R.string.offline)); } - adapter.setContactName(contactName); } }); } @@ -350,10 +354,14 @@ public class ConversationActivity extends BriarActivity Collection<InvitationMessage> blogInvitations = blogSharingManager .getInvitationMessages(contactId); + Collection<InvitationMessage> groupInvitations = + groupInvitationManager + .getInvitationMessages(contactId); List<InvitationMessage> invitations = new ArrayList<>( forumInvitations.size() + blogInvitations.size()); invitations.addAll(forumInvitations); invitations.addAll(blogInvitations); + invitations.addAll(groupInvitations); long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Loading messages took " + duration + " ms"); @@ -397,34 +405,41 @@ public class ConversationActivity extends BriarActivity Collection<PrivateMessageHeader> headers, Collection<IntroductionMessage> introductions, 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); for (PrivateMessageHeader h : headers) { - ConversationMessageItem item = ConversationItem.from(h); - byte[] body = bodyCache.get(h.getId()); + ConversationItem item = ConversationItem.from(h); + String body = bodyCache.get(h.getId()); if (body == null) loadMessageBody(h.getId()); else item.setBody(body); items.add(item); } - for (IntroductionMessage im : introductions) { - if (im instanceof IntroductionRequest) { - IntroductionRequest ir = (IntroductionRequest) im; - items.add(ConversationItem.from(ir)); + for (IntroductionMessage m : introductions) { + ConversationItem item; + if (m instanceof IntroductionRequest) { + IntroductionRequest i = (IntroductionRequest) m; + item = ConversationItem + .from(ConversationActivity.this, contactName, i); } else { - IntroductionResponse ir = (IntroductionResponse) im; - items.add(ConversationItem.from(ConversationActivity.this, - contactName, ir)); + IntroductionResponse i = (IntroductionResponse) m; + item = ConversationItem + .from(ConversationActivity.this, contactName, i); } + items.add(item); } - for (InvitationMessage im : invitations) { - if (im instanceof InvitationRequest) { - InvitationRequest ir = (InvitationRequest) im; - items.add(ConversationItem.from(ir)); - } else if (im instanceof InvitationResponse) { - InvitationResponse ir = (InvitationResponse) im; - items.add(ConversationItem.from(ConversationActivity.this, - contactName, ir)); + for (InvitationMessage i : invitations) { + ConversationItem item; + if (i instanceof InvitationRequest) { + InvitationRequest r = (InvitationRequest) i; + item = ConversationItem + .from(ConversationActivity.this, contactName, r); + } else { + InvitationResponse r = (InvitationResponse) i; + item = ConversationItem + .from(ConversationActivity.this, contactName, r); } + items.add(item); } return items; } @@ -439,7 +454,7 @@ public class ConversationActivity extends BriarActivity long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Loading body took " + duration + " ms"); - displayMessageBody(m, body); + displayMessageBody(m, StringUtils.fromUtf8(body)); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -448,15 +463,15 @@ 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() { @Override public void run() { bodyCache.put(m, body); - SparseArray<ConversationMessageItem> messages = + SparseArray<ConversationItem> messages = adapter.getPrivateMessages(); for (int i = 0; i < messages.size(); i++) { - ConversationMessageItem item = messages.valueAt(i); + ConversationItem item = messages.valueAt(i); if (item.getId().equals(m)) { item.setBody(body); adapter.notifyItemChanged(messages.keyAt(i)); @@ -482,9 +497,9 @@ public class ConversationActivity extends BriarActivity private void markMessagesRead() { Map<MessageId, GroupId> unread = new HashMap<>(); - SparseArray<IncomingItem> list = adapter.getIncomingMessages(); + SparseArray<ConversationItem> list = adapter.getIncomingMessages(); for (int i = 0; i < list.size(); i++) { - IncomingItem item = list.valueAt(i); + ConversationItem item = list.valueAt(i); if (!item.isRead()) unread.put(item.getId(), item.getGroupId()); } @@ -561,7 +576,8 @@ public class ConversationActivity extends BriarActivity if (event.getContactId().equals(contactId)) { LOG.info("Introduction request received, adding..."); IntroductionRequest ir = event.getIntroductionRequest(); - ConversationItem item = new ConversationIntroductionInItem(ir); + ConversationItem item = + ConversationItem.from(this, contactName, ir); addConversationItem(item); } } else if (e instanceof IntroductionResponseReceivedEvent) { @@ -580,7 +596,8 @@ public class ConversationActivity extends BriarActivity if (event.getContactId().equals(contactId)) { LOG.info("Invitation received, adding..."); InvitationRequest ir = event.getRequest(); - ConversationItem item = ConversationItem.from(ir); + ConversationItem item = + ConversationItem.from(this, contactName, ir); addConversationItem(item); } } else if (e instanceof InvitationResponseReceivedEvent) { @@ -603,9 +620,10 @@ public class ConversationActivity extends BriarActivity public void run() { adapter.incrementRevision(); Set<MessageId> messages = new HashSet<>(messageIds); - SparseArray<OutgoingItem> list = adapter.getOutgoingMessages(); + SparseArray<ConversationOutItem> list = + adapter.getOutgoingMessages(); for (int i = 0; i < list.size(); i++) { - OutgoingItem item = list.valueAt(i); + ConversationOutItem item = list.valueAt(i); if (messages.contains(item.getId())) { item.setSent(sent); item.setSeen(seen); @@ -622,7 +640,7 @@ public class ConversationActivity extends BriarActivity text = StringUtils.truncateUtf8(text, MAX_PRIVATE_MESSAGE_BODY_LENGTH); long timestamp = System.currentTimeMillis(); timestamp = Math.max(timestamp, getMinTimestampForNewMessage()); - createMessage(StringUtils.toUtf8(text), timestamp); + createMessage(text, timestamp); textInputView.setText(""); } @@ -632,14 +650,14 @@ public class ConversationActivity extends BriarActivity 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() { @Override public void run() { try { storeMessage(privateMessageFactory.createPrivateMessage( - groupId, timestamp, null, "text/plain", body), - body); + groupId, timestamp, null, "text/plain", + StringUtils.toUtf8(body)), body); } catch (FormatException e) { throw new RuntimeException(e); } @@ -647,7 +665,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() { @Override public void run() { @@ -661,7 +679,7 @@ public class ConversationActivity extends BriarActivity PrivateMessageHeader h = new PrivateMessageHeader(id, groupId, m.getMessage().getTimestamp(), m.getContentType(), true, false, false, false); - ConversationMessageItem item = ConversationItem.from(h); + ConversationItem item = ConversationItem.from(h); item.setBody(body); bodyCache.put(id, body); addConversationItem(item); @@ -812,21 +830,37 @@ public class ConversationActivity extends BriarActivity }); } + @UiThread @Override - public void respondToIntroduction(final SessionId sessionId, + public void respondToRequest(@NotNull final ConversationRequestItem item, final boolean accept) { + int position = adapter.findItemPosition(item); + if (position != INVALID_POSITION) { + adapter.notifyItemChanged(position, item); + } runOnDbThread(new Runnable() { @Override public void run() { long timestamp = System.currentTimeMillis(); timestamp = Math.max(timestamp, getMinTimestampForNewMessage()); try { - if (accept) { - introductionManager.acceptIntroduction(contactId, - sessionId, timestamp); - } else { - introductionManager.declineIntroduction(contactId, - sessionId, timestamp); + switch (item.getRequestType()) { + case INTRODUCTION: + respondToIntroductionRequest(item.getSessionId(), + accept, timestamp); + break; + case FORUM: + respondToForumRequest(item.getSessionId(), accept); + break; + case BLOG: + respondToBlogRequest(item.getSessionId(), accept); + break; + case GROUP: + respondToGroupRequest(item.getSessionId(), accept); + break; + default: + throw new IllegalArgumentException( + "Unknown Request Type"); } loadMessages(); } catch (DbException | FormatException e) { @@ -839,6 +873,34 @@ 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); + } + } + + @DatabaseExecutor + private void respondToForumRequest(SessionId id, boolean accept) + throws DbException { + forumSharingManager.respondToInvitation(id, accept); + } + + @DatabaseExecutor + private void respondToBlogRequest(SessionId id, boolean accept) + throws DbException { + blogSharingManager.respondToInvitation(id, accept); + } + + @DatabaseExecutor + private void respondToGroupRequest(SessionId id, boolean accept) + throws DbException { + groupInvitationManager.respondToInvitation(id, accept); + } + private void introductionResponseError() { runOnUiThreadUnlessDestroyed(new Runnable() { @Override diff --git a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java index 3fa184bf2f8cd47da961d30e0bd083e65c7e814e..6798ff7d0a450fa60a98db863eee3a8f2c4eae5b 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java @@ -1,365 +1,64 @@ package org.briarproject.android.contact; import android.content.Context; -import android.content.Intent; +import android.support.annotation.LayoutRes; import android.support.annotation.Nullable; -import android.support.v7.widget.RecyclerView; +import android.support.annotation.UiThread; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.TextView; import org.briarproject.R; -import org.briarproject.android.sharing.InvitationsBlogActivity; -import org.briarproject.android.sharing.InvitationsForumActivity; -import org.briarproject.android.util.AndroidUtils; 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 org.briarproject.api.nullsafety.NotNullByDefault; -import static android.support.v7.widget.RecyclerView.ViewHolder; -import static android.view.View.GONE; -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, ConversationItemViewHolder> { -class ConversationAdapter extends BriarAdapter<ConversationItem, ViewHolder> { + private RequestListener listener; - private IntroductionHandler intro; - private String contactName; - - ConversationAdapter(Context ctx, IntroductionHandler introductionHandler) { + ConversationAdapter(Context ctx, RequestListener requestListener) { super(ctx, ConversationItem.class); - intro = introductionHandler; - } - - void setContactName(String contactName) { - this.contactName = contactName; - notifyDataSetChanged(); + listener = requestListener; } + @LayoutRes @Override public int getItemViewType(int position) { - return items.get(position).getType(); + ConversationItem item = items.get(position); + return item.getLayout(); } @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); + public ConversationItemViewHolder onCreateViewHolder(ViewGroup viewGroup, + @LayoutRes int type) { + View v = LayoutInflater.from(viewGroup.getContext()).inflate( + type, viewGroup, false); + switch (type) { + case R.layout.list_item_conversation_msg_in: + return new ConversationItemViewHolder(v); + case R.layout.list_item_conversation_msg_out: + return new ConversationMessageOutViewHolder(v); + case R.layout.list_item_conversation_notice_in: + return new ConversationNoticeInViewHolder(v); + case R.layout.list_item_conversation_notice_out: + return new ConversationNoticeOutViewHolder(v); + case R.layout.list_item_conversation_request: + return new ConversationRequestViewHolder(v); + default: + throw new IllegalArgumentException("Unknown ConversationItem"); } } @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) { - bindNotice((NoticeHolder) ui, (ConversationNoticeOutItem) item); - } else if (item instanceof ConversationNoticeInItem) { - bindNotice((NoticeHolder) ui, (ConversationNoticeInItem) item); - } else if (item instanceof ConversationShareableInvitationOutItem) { - bindInvitation((InvitationHolder) ui, - (ConversationShareableInvitationOutItem) item); - } else if (item instanceof ConversationShareableInvitationInItem) { - bindInvitation((InvitationHolder) ui, - (ConversationShareableInvitationInItem) item); - } else { - throw new IllegalArgumentException("Unhandled Conversation Item"); - } - } - - private void bindMessage(MessageHolder ui, ConversationMessageItem item) { - PrivateMessageHeader header = item.getHeader(); - - if (item instanceof ConversationItem.OutgoingItem) { - if (((OutgoingItem) item).isSeen()) { - ui.status.setImageResource(R.drawable.message_delivered_white); - } else if (((OutgoingItem) item).isSent()) { - ui.status.setImageResource(R.drawable.message_sent_white); - } else { - ui.status.setImageResource(R.drawable.message_stored_white); - } + public void onBindViewHolder(ConversationItemViewHolder ui, int position) { + ConversationItem item = items.get(position); + if (item instanceof ConversationRequestItem) { + ((ConversationRequestViewHolder) ui).bind(item, listener); } else { - if (item.getType() == MSG_IN_UNREAD) { - // TODO implement new unread message highlight according to #232 -/* int left = ui.layout.getPaddingLeft(); - int top = ui.layout.getPaddingTop(); - int right = ui.layout.getPaddingRight(); - int bottom = ui.layout.getPaddingBottom(); - - // 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 + ui.bind(item); } - - 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, - final ConversationShareableInvitationItem item) { - - final InvitationRequest ir = item.getInvitationRequest(); - String name = ""; - 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 { - ui.message.setVisibility(VISIBLE); - 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 @@ -393,138 +92,48 @@ class ConversationAdapter extends BriarAdapter<ConversationItem, ViewHolder> { } } - SparseArray<IncomingItem> getIncomingMessages() { - SparseArray<IncomingItem> messages = new SparseArray<>(); + SparseArray<ConversationItem> getIncomingMessages() { + SparseArray<ConversationItem> messages = new SparseArray<>(); for (int i = 0; i < items.size(); i++) { ConversationItem item = items.get(i); - if (item instanceof IncomingItem) { - messages.put(i, (IncomingItem) item); + if (item.isIncoming()) { + messages.put(i, item); } } return messages; } - SparseArray<OutgoingItem> getOutgoingMessages() { - SparseArray<OutgoingItem> messages = new SparseArray<>(); + SparseArray<ConversationOutItem> getOutgoingMessages() { + SparseArray<ConversationOutItem> messages = new SparseArray<>(); for (int i = 0; i < items.size(); i++) { ConversationItem item = items.get(i); - if (item instanceof OutgoingItem) { - messages.put(i, (OutgoingItem) item); + if (item instanceof ConversationOutItem) { + messages.put(i, (ConversationOutItem) item); } } return messages; } - SparseArray<ConversationMessageItem> getPrivateMessages() { - SparseArray<ConversationMessageItem> messages = new SparseArray<>(); + SparseArray<ConversationItem> getPrivateMessages() { + SparseArray<ConversationItem> messages = new SparseArray<>(); for (int i = 0; i < items.size(); i++) { ConversationItem item = items.get(i); - if (item instanceof ConversationMessageItem) { - messages.put(i, (ConversationMessageItem) item); + if (item instanceof ConversationMessageInItem) { + messages.put(i, item); + } else if (item instanceof ConversationMessageOutItem) { + messages.put(i, item); } } return messages; } - private static class MessageHolder extends RecyclerView.ViewHolder { - - 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; - } - } + @UiThread + @NotNullByDefault + interface RequestListener { + void respondToRequest(ConversationRequestItem item, boolean accept); } - interface IntroductionHandler { - void respondToIntroduction(SessionId sessionId, boolean accept); - } } diff --git a/briar-android/src/org/briarproject/android/contact/ConversationIntroductionInItem.java b/briar-android/src/org/briarproject/android/contact/ConversationIntroductionInItem.java deleted file mode 100644 index 6959d214b10abb5599c5d1e04d439ecc7ae1f296..0000000000000000000000000000000000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationIntroductionInItem.java +++ /dev/null @@ -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; - } -} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationIntroductionItem.java b/briar-android/src/org/briarproject/android/contact/ConversationIntroductionItem.java deleted file mode 100644 index d2cdef596ffdadd4c4b38a280f5536ce0d4ead6a..0000000000000000000000000000000000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationIntroductionItem.java +++ /dev/null @@ -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; - } -} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationIntroductionOutItem.java b/briar-android/src/org/briarproject/android/contact/ConversationIntroductionOutItem.java deleted file mode 100644 index 2729fea0cd549748c7cfccceb43f12813503505d..0000000000000000000000000000000000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationIntroductionOutItem.java +++ /dev/null @@ -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; - } -} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationItem.java b/briar-android/src/org/briarproject/android/contact/ConversationItem.java index d8c5e5aa746900f212744ae1423f811e5e09f818..1a2c118e24c751448772a42f5ea005317d8a2103 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationItem.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationItem.java @@ -1,65 +1,85 @@ package org.briarproject.android.contact; import android.content.Context; +import android.support.annotation.LayoutRes; +import android.support.annotation.StringRes; import org.briarproject.R; +import org.briarproject.android.contact.ConversationRequestItem.RequestType; +import org.briarproject.api.blogs.BlogInvitationRequest; import org.briarproject.api.blogs.BlogInvitationResponse; +import org.briarproject.api.clients.BaseMessageHeader; +import org.briarproject.api.forum.ForumInvitationRequest; import org.briarproject.api.forum.ForumInvitationResponse; -import org.briarproject.api.introduction.IntroductionMessage; import org.briarproject.api.introduction.IntroductionRequest; import org.briarproject.api.introduction.IntroductionResponse; import org.briarproject.api.messaging.PrivateMessageHeader; -import org.briarproject.api.sharing.InvitationMessage; +import org.briarproject.api.nullsafety.NotNullByDefault; +import org.briarproject.api.privategroup.invitation.GroupInvitationRequest; +import org.briarproject.api.privategroup.invitation.GroupInvitationResponse; import org.briarproject.api.sharing.InvitationRequest; import org.briarproject.api.sharing.InvitationResponse; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.MessageId; -import org.jetbrains.annotations.NotNull; - -// This class is not thread-safe -public abstract class ConversationItem { - - // this is needed for RecyclerView adapter which requires an int type - final static int MSG_IN = 0; - final static int MSG_IN_UNREAD = 1; - final static int MSG_OUT = 2; - final static int INTRODUCTION_IN = 3; - final static int INTRODUCTION_OUT = 4; - final static int NOTICE_IN = 5; - 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; +import org.jetbrains.annotations.Nullable; +import javax.annotation.concurrent.NotThreadSafe; + +import static org.briarproject.android.contact.ConversationRequestItem.RequestType.BLOG; +import static org.briarproject.android.contact.ConversationRequestItem.RequestType.FORUM; +import static org.briarproject.android.contact.ConversationRequestItem.RequestType.GROUP; +import static org.briarproject.android.contact.ConversationRequestItem.RequestType.INTRODUCTION; + +@NotThreadSafe +@NotNullByDefault +abstract class ConversationItem { + + protected @Nullable String body; final private MessageId id; final private GroupId groupId; final private long time; + private boolean read; - public ConversationItem(@NotNull MessageId id, @NotNull GroupId groupId, - long time) { + ConversationItem(MessageId id, GroupId groupId, @Nullable String body, + long time, boolean read) { this.id = id; this.groupId = groupId; + this.body = body; this.time = time; + this.read = read; } - abstract int getType(); - - @NotNull - public MessageId getId() { + MessageId getId() { return id; } - @NotNull - public GroupId getGroupId() { + GroupId getGroupId() { return groupId; } + void setBody(String body) { + this.body = body; + } + + @Nullable + public String getBody() { + return body; + } + long getTime() { return time; } - public static ConversationMessageItem from(PrivateMessageHeader h) { + public boolean isRead() { + return read; + } + + abstract public boolean isIncoming(); + + @LayoutRes + abstract public int getLayout(); + + static ConversationItem from(PrivateMessageHeader h) { if (h.isLocal()) { return new ConversationMessageOutItem(h); } else { @@ -67,17 +87,40 @@ public abstract class ConversationItem { } } - public static ConversationIntroductionItem from(IntroductionRequest ir) { + static ConversationItem from(Context ctx, String contactName, + IntroductionRequest ir) { 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 { - 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) { - if (ir.isLocal()) { String text; if (ir.wasAccepted()) { @@ -90,7 +133,7 @@ public abstract class ConversationItem { ir.getName()); } return new ConversationNoticeOutItem(ir.getMessageId(), - ir.getGroupId(), text, ir.getTimestamp(), ir.isSent(), + ir.getGroupId(), text, null, ir.getTimestamp(), ir.isSent(), ir.isSeen()); } else { String text; @@ -110,143 +153,146 @@ public abstract class ConversationItem { } } return new ConversationNoticeInItem(ir.getMessageId(), - ir.getGroupId(), text, ir.getTimestamp(), ir.isRead()); - } - } - - public static ConversationShareableInvitationItem from( - InvitationRequest fim) { - if (fim.isLocal()) { - return new ConversationShareableInvitationOutItem(fim); - } else { - return new ConversationShareableInvitationInItem(fim); - } - } - - public static ConversationNoticeItem from(Context ctx, String contactName, - InvitationResponse ir) { - - if (ir instanceof ForumInvitationResponse) { - return from(ctx, contactName, (ForumInvitationResponse) ir); - } else if (ir instanceof BlogInvitationResponse) { - return from(ctx, contactName, (BlogInvitationResponse) ir); - } else { - throw new IllegalArgumentException("Unknown Invitation Response."); + ir.getGroupId(), text, null, ir.getTimestamp(), + ir.isRead()); } } - private static ConversationNoticeItem from(Context ctx, String contactName, - ForumInvitationResponse fir) { - - if (fir.isLocal()) { + static ConversationItem from(Context ctx, String contactName, + InvitationRequest ir) { + if (ir.isLocal()) { String text; - if (fir.wasAccepted()) { - text = ctx.getString( - R.string.forum_invitation_response_accepted_sent, + if (ir instanceof ForumInvitationRequest) { + text = ctx.getString(R.string.forum_invitation_sent, + ((ForumInvitationRequest) ir).getForumName(), contactName); - } else { - text = ctx.getString( - R.string.forum_invitation_response_declined_sent, + } else if (ir instanceof BlogInvitationRequest) { + text = ctx.getString(R.string.blogs_sharing_invitation_sent, + ((BlogInvitationRequest) ir).getBlogAuthorName(), contactName); + } else if (ir instanceof GroupInvitationRequest) { + text = ctx.getString( + R.string.groups_invitations_invitation_sent, + contactName, + ((GroupInvitationRequest) ir).getGroupName()); + } else { + throw new IllegalArgumentException("Unknown InvitationRequest"); } - return new ConversationNoticeOutItem(fir.getId(), fir.getGroupId(), - text, fir.getTimestamp(), fir.isSent(), fir.isSeen()); + return new ConversationNoticeOutItem(ir.getId(), ir.getGroupId(), + text, ir.getMessage(), ir.getTimestamp(), ir.isSent(), + ir.isSeen()); } else { String text; - if (fir.wasAccepted()) { + RequestType type; + if (ir instanceof ForumInvitationRequest) { + text = ctx.getString(R.string.forum_invitation_received, + contactName, + ((ForumInvitationRequest) ir).getForumName()); + type = FORUM; + } else if (ir instanceof BlogInvitationRequest) { + text = ctx.getString(R.string.blogs_sharing_invitation_received, + contactName, + ((BlogInvitationRequest) ir).getBlogAuthorName()); + type = BLOG; + } else if (ir instanceof GroupInvitationRequest) { text = ctx.getString( - R.string.forum_invitation_response_accepted_received, - contactName); + R.string.groups_invitations_invitation_received, + contactName, + ((GroupInvitationRequest) ir).getGroupName()); + type = GROUP; } else { - text = ctx.getString( - R.string.forum_invitation_response_declined_received, - contactName); + throw new IllegalArgumentException("Unknown InvitationRequest"); + } + if (!ir.isAvailable()) { + return new ConversationNoticeInItem(ir.getId(), ir.getGroupId(), + text, ir.getMessage(), ir.getTimestamp(), ir.isRead()); } - return new ConversationNoticeInItem(fir.getId(), fir.getGroupId(), - text, fir.getTimestamp(), fir.isRead()); + return new ConversationRequestItem(ir.getId(), + ir.getGroupId(), type, ir.getSessionId(), text, + ir.getMessage(), ir.getTimestamp(), ir.isRead(), + !ir.isAvailable()); } } - 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); + static ConversationItem from(Context ctx, String contactName, + InvitationResponse ir) { + @StringRes int res; + if (ir.isLocal()) { + if (ir.wasAccepted()) { + if (ir instanceof ForumInvitationResponse) { + res = R.string.forum_invitation_response_accepted_sent; + } else if (ir instanceof BlogInvitationResponse) { + res = R.string.blogs_sharing_response_accepted_sent; + } else if (ir instanceof GroupInvitationResponse) { + res = R.string.groups_invitations_response_accepted_sent; + } else { + throw new IllegalArgumentException( + "Unknown InvitationResponse"); + } } else { - text = ctx.getString( - R.string.blogs_sharing_response_declined_sent, - contactName); + if (ir instanceof ForumInvitationResponse) { + res = R.string.forum_invitation_response_declined_sent; + } else if (ir instanceof BlogInvitationResponse) { + res = R.string.blogs_sharing_response_declined_sent; + } else if (ir instanceof GroupInvitationResponse) { + res = R.string.groups_invitations_response_declined_sent; + } else { + throw new IllegalArgumentException( + "Unknown InvitationResponse"); + } } - return new ConversationNoticeOutItem(fir.getId(), fir.getGroupId(), - text, fir.getTimestamp(), fir.isSent(), fir.isSeen()); + String text = ctx.getString(res, contactName); + return new ConversationNoticeOutItem(ir.getId(), ir.getGroupId(), + text, null, ir.getTimestamp(), ir.isSent(), ir.isSeen()); } else { - String text; - if (fir.wasAccepted()) { - text = ctx.getString( - R.string.blogs_sharing_response_accepted_received, - contactName); + if (ir.wasAccepted()) { + if (ir instanceof ForumInvitationResponse) { + res = R.string.forum_invitation_response_accepted_received; + } else if (ir instanceof BlogInvitationResponse) { + res = R.string.blogs_sharing_response_accepted_received; + } else if (ir instanceof GroupInvitationResponse) { + res = R.string.groups_invitations_response_accepted_received; + } else { + throw new IllegalArgumentException( + "Unknown InvitationResponse"); + } } else { - text = ctx.getString( - R.string.blogs_sharing_response_declined_received, - contactName); + if (ir instanceof ForumInvitationResponse) { + res = R.string.forum_invitation_response_declined_received; + } else if (ir instanceof BlogInvitationResponse) { + res = R.string.blogs_sharing_response_declined_received; + } else if (ir instanceof GroupInvitationResponse) { + res = R.string.groups_invitations_response_declined_received; + } else { + throw new IllegalArgumentException( + "Unknown InvitationResponse"); + } } - return new ConversationNoticeInItem(fir.getId(), fir.getGroupId(), - text, fir.getTimestamp(), fir.isRead()); + String text = ctx.getString(res, contactName); + return new ConversationNoticeInItem(ir.getId(), ir.getGroupId(), + text, null, ir.getTimestamp(), ir.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); + * This method should not be used to display the resulting ConversationItem + * in the UI, but only to update list information based on the + * BaseMessageHeader. + **/ + static ConversationItem from(Context ctx, BaseMessageHeader h) { + if (h instanceof PrivateMessageHeader) { + return from((PrivateMessageHeader) h); + } else if(h instanceof IntroductionRequest) { + return from(ctx, "", (IntroductionRequest) h); + } else if(h instanceof IntroductionResponse) { + return from(ctx, "", (IntroductionResponse) h); + } else if(h instanceof InvitationRequest) { + return from(ctx, "", (InvitationRequest) h); + } else if(h instanceof InvitationResponse) { + return from(ctx, "", (InvitationResponse) h); + } else { + throw new IllegalArgumentException("Unknown message header"); + } } - interface IncomingItem { - - @NotNull - MessageId getId(); - - @NotNull - GroupId getGroupId(); - - boolean isRead(); - - void setRead(boolean read); - } } diff --git a/briar-android/src/org/briarproject/android/contact/ConversationItemViewHolder.java b/briar-android/src/org/briarproject/android/contact/ConversationItemViewHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..58a42951b98c2a9f8a92df2fb4969cdc5d120549 --- /dev/null +++ b/briar-android/src/org/briarproject/android/contact/ConversationItemViewHolder.java @@ -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.getBody() == null) { + text.setText("\u2026"); + } else { + text.setText(StringUtils.trim(item.getBody())); + } + + long timestamp = item.getTime(); + time.setText(AndroidUtils.formatDate(time.getContext(), timestamp)); + } + +} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationMessageInItem.java b/briar-android/src/org/briarproject/android/contact/ConversationMessageInItem.java index eefef09b4ef4dc3c38d8d2896ce405af9948e61b..23d8571366e27d46c2838e05c04d2cb796c078cc 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationMessageInItem.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationMessageInItem.java @@ -1,31 +1,30 @@ package org.briarproject.android.contact; -import org.briarproject.api.messaging.PrivateMessageHeader; +import android.support.annotation.LayoutRes; -// This class is not thread-safe -class ConversationMessageInItem extends ConversationMessageItem - implements ConversationItem.IncomingItem { +import org.briarproject.R; +import org.briarproject.api.messaging.PrivateMessageHeader; +import org.briarproject.api.nullsafety.NotNullByDefault; - private boolean read; +import javax.annotation.concurrent.NotThreadSafe; - ConversationMessageInItem(PrivateMessageHeader header) { - super(header); +@NotThreadSafe +@NotNullByDefault +class ConversationMessageInItem extends ConversationItem { - read = header.isRead(); + ConversationMessageInItem(PrivateMessageHeader h) { + super(h.getId(), h.getGroupId(), null, h.getTimestamp(), h.isRead()); } @Override - int getType() { - return MSG_IN; + public boolean isIncoming() { + return true; } + @LayoutRes @Override - public boolean isRead() { - return read; + public int getLayout() { + return R.layout.list_item_conversation_msg_in; } - @Override - public void setRead(boolean read) { - this.read = read; - } } diff --git a/briar-android/src/org/briarproject/android/contact/ConversationMessageItem.java b/briar-android/src/org/briarproject/android/contact/ConversationMessageItem.java deleted file mode 100644 index b4dc64b6cc8c2aa86e729f545b19a7a78776b04d..0000000000000000000000000000000000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationMessageItem.java +++ /dev/null @@ -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; - } -} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationMessageOutItem.java b/briar-android/src/org/briarproject/android/contact/ConversationMessageOutItem.java index 550219e9b3e5cd9aadc71405bb0488b9513fbc4b..ee2d1a11b95884cb5b4aad0c117a46146b91dbec 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationMessageOutItem.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationMessageOutItem.java @@ -1,42 +1,26 @@ package org.briarproject.android.contact; -import org.briarproject.api.messaging.PrivateMessageHeader; +import android.support.annotation.LayoutRes; -// This class is not thread-safe -class ConversationMessageOutItem extends ConversationMessageItem - implements ConversationItem.OutgoingItem { +import org.briarproject.R; +import org.briarproject.api.messaging.PrivateMessageHeader; +import org.briarproject.api.nullsafety.NotNullByDefault; - private boolean sent, seen; +import javax.annotation.concurrent.NotThreadSafe; - ConversationMessageOutItem(PrivateMessageHeader header) { - super(header); +@NotThreadSafe +@NotNullByDefault +class ConversationMessageOutItem extends ConversationOutItem { - sent = header.isSent(); - seen = header.isSeen(); + ConversationMessageOutItem(PrivateMessageHeader h) { + super(h.getId(), h.getGroupId(), null, h.getTimestamp(), h.isSent(), + h.isSeen()); } + @LayoutRes @Override - int getType() { - return MSG_OUT; + public int getLayout() { + return R.layout.list_item_conversation_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; - } } diff --git a/briar-android/src/org/briarproject/android/contact/ConversationMessageOutViewHolder.java b/briar-android/src/org/briarproject/android/contact/ConversationMessageOutViewHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..83d58dcee413315435edef53f126f4e86d8366ed --- /dev/null +++ b/briar-android/src/org/briarproject/android/contact/ConversationMessageOutViewHolder.java @@ -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; + } + +} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationNoticeInItem.java b/briar-android/src/org/briarproject/android/contact/ConversationNoticeInItem.java index 6e8fbfc8bcb3331b817bea7793f3295eb30b1cfa..0e70e66312ee213190994e40c67a7f7cef2f9c56 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationNoticeInItem.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationNoticeInItem.java @@ -1,33 +1,43 @@ package org.briarproject.android.contact; +import android.support.annotation.LayoutRes; + +import org.briarproject.R; +import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.MessageId; +import org.jetbrains.annotations.Nullable; -// This class is not thread-safe -class ConversationNoticeInItem extends ConversationNoticeItem - implements ConversationItem.IncomingItem { +import javax.annotation.concurrent.NotThreadSafe; - private boolean read; +@NotThreadSafe +@NotNullByDefault +class ConversationNoticeInItem extends ConversationItem { - ConversationNoticeInItem(MessageId id, GroupId groupId, String text, - long time, boolean read) { - super(id, groupId, text, time); + @Nullable + private final String msgText; - 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 - int getType() { - return NOTICE_IN; + @Nullable + public String getMsgText() { + return msgText; } @Override - public boolean isRead() { - return read; + public boolean isIncoming() { + return true; } + @LayoutRes @Override - public void setRead(boolean read) { - this.read = read; + public int getLayout() { + return R.layout.list_item_conversation_notice_in; } + } diff --git a/briar-android/src/org/briarproject/android/contact/ConversationNoticeInViewHolder.java b/briar-android/src/org/briarproject/android/contact/ConversationNoticeInViewHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..7e5a60824db789619a1ca6323dfd9ffc867a425c --- /dev/null +++ b/briar-android/src/org/briarproject/android/contact/ConversationNoticeInViewHolder.java @@ -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); + } + } + +} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationNoticeItem.java b/briar-android/src/org/briarproject/android/contact/ConversationNoticeItem.java deleted file mode 100644 index 758c311d01f57e9611e1a5b7411c7dcc41d4d689..0000000000000000000000000000000000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationNoticeItem.java +++ /dev/null @@ -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; - } -} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationNoticeOutItem.java b/briar-android/src/org/briarproject/android/contact/ConversationNoticeOutItem.java index 95ed78510fba0d585b51044422d660a0f1d3a7ad..78cddf3a766ea7a3402fa0ca1262001c7e93e56b 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationNoticeOutItem.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationNoticeOutItem.java @@ -1,44 +1,38 @@ package org.briarproject.android.contact; +import android.support.annotation.LayoutRes; + +import org.briarproject.R; +import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.MessageId; +import org.jetbrains.annotations.Nullable; -// This class is not thread-safe -class ConversationNoticeOutItem extends ConversationNoticeItem - implements ConversationItem.OutgoingItem { - - private boolean sent, seen; +import javax.annotation.concurrent.NotThreadSafe; - ConversationNoticeOutItem(MessageId id, GroupId groupId, String text, - long time, boolean sent, boolean seen) { - super(id, groupId, text, time); +@NotThreadSafe +@NotNullByDefault +class ConversationNoticeOutItem extends ConversationOutItem { - this.sent = sent; - this.seen = seen; - } - - @Override - int getType() { - return NOTICE_OUT; - } + @Nullable + private final String msgText; - @Override - public boolean isSent() { - return sent; + ConversationNoticeOutItem(MessageId id, GroupId groupId, + String text, @Nullable String msgText, long time, + boolean sent, boolean seen) { + super(id, groupId, text, time, sent, seen); + this.msgText = msgText; } - @Override - public void setSent(boolean sent) { - this.sent = sent; + @Nullable + public String getMsgText() { + return msgText; } + @LayoutRes @Override - public boolean isSeen() { - return seen; + public int getLayout() { + return R.layout.list_item_conversation_notice_out; } - @Override - public void setSeen(boolean seen) { - this.seen = seen; - } } diff --git a/briar-android/src/org/briarproject/android/contact/ConversationNoticeOutViewHolder.java b/briar-android/src/org/briarproject/android/contact/ConversationNoticeOutViewHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..c0bdf7fcf758c42956bacd0b76c4c0b4869af560 --- /dev/null +++ b/briar-android/src/org/briarproject/android/contact/ConversationNoticeOutViewHolder.java @@ -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; + } + +} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationOutItem.java b/briar-android/src/org/briarproject/android/contact/ConversationOutItem.java new file mode 100644 index 0000000000000000000000000000000000000000..469f4b0f448fc5f6ad5610700cf308c00ee45b9a --- /dev/null +++ b/briar-android/src/org/briarproject/android/contact/ConversationOutItem.java @@ -0,0 +1,45 @@ +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, true); + + 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; + } + + @Override + public boolean isIncoming() { + return false; + } + +} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationOutItemViewHolder.java b/briar-android/src/org/briarproject/android/contact/ConversationOutItemViewHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..4c10ba402499c155e48d70fa40202b96ef1b018e --- /dev/null +++ b/briar-android/src/org/briarproject/android/contact/ConversationOutItemViewHolder.java @@ -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(); + +} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationRequestItem.java b/briar-android/src/org/briarproject/android/contact/ConversationRequestItem.java new file mode 100644 index 0000000000000000000000000000000000000000..f0a1cb08b40fdd73c909bae2a0912fdde6ab3c6c --- /dev/null +++ b/briar-android/src/org/briarproject/android/contact/ConversationRequestItem.java @@ -0,0 +1,55 @@ +package org.briarproject.android.contact; + +import android.support.annotation.LayoutRes; + +import org.briarproject.R; +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, GROUP }; + 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; + } + + @LayoutRes + @Override + public int getLayout() { + return R.layout.list_item_conversation_request; + } + +} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationRequestViewHolder.java b/briar-android/src/org/briarproject/android/contact/ConversationRequestViewHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..972884ada22976df853dce81c8b5651df13d62e1 --- /dev/null +++ b/briar-android/src/org/briarproject/android/contact/ConversationRequestViewHolder.java @@ -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); + } + }); + } + } + +} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationShareableInvitationInItem.java b/briar-android/src/org/briarproject/android/contact/ConversationShareableInvitationInItem.java deleted file mode 100644 index 6e46b7b4160174929712bf959921830e9c12f88a..0000000000000000000000000000000000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationShareableInvitationInItem.java +++ /dev/null @@ -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; - } -} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationShareableInvitationItem.java b/briar-android/src/org/briarproject/android/contact/ConversationShareableInvitationItem.java deleted file mode 100644 index 60f8a70b1ca4bd1f331925cb771c35813e1f422e..0000000000000000000000000000000000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationShareableInvitationItem.java +++ /dev/null @@ -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; - } -} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationShareableInvitationOutItem.java b/briar-android/src/org/briarproject/android/contact/ConversationShareableInvitationOutItem.java deleted file mode 100644 index 426403ab36c488cb94af91a76ef0f52b010be671..0000000000000000000000000000000000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationShareableInvitationOutItem.java +++ /dev/null @@ -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; - } -} diff --git a/briar-android/src/org/briarproject/android/forum/ForumActivity.java b/briar-android/src/org/briarproject/android/forum/ForumActivity.java index 12487efc946fa28c551df033daf29f9a76920687..307a7c5e158407d4a0cd173974607da181716181 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumActivity.java +++ b/briar-android/src/org/briarproject/android/forum/ForumActivity.java @@ -19,7 +19,7 @@ import org.briarproject.R; import org.briarproject.android.ActivityComponent; import org.briarproject.android.controller.handler.UiResultExceptionHandler; import org.briarproject.android.sharing.ShareForumActivity; -import org.briarproject.android.sharing.SharingStatusForumActivity; +import org.briarproject.android.sharing.ForumSharingStatusActivity; import org.briarproject.android.threaded.ThreadListActivity; import org.briarproject.android.threaded.ThreadListController; import org.briarproject.api.db.DbException; @@ -114,7 +114,7 @@ public class ForumActivity extends REQUEST_FORUM_SHARED, options.toBundle()); return true; case R.id.action_forum_sharing_status: - Intent i3 = new Intent(this, SharingStatusForumActivity.class); + Intent i3 = new Intent(this, ForumSharingStatusActivity.class); i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP); i3.putExtra(GROUP_ID, groupId.getBytes()); ActivityCompat.startActivity(this, i3, options.toBundle()); diff --git a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java index f1faecd46d260908e5c7b7e89428df6b2aed2b8b..bb5d1eee0a231c2983aaa4c093c0ae0ac923519f 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java +++ b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java @@ -18,7 +18,7 @@ import org.briarproject.R; import org.briarproject.android.ActivityComponent; import org.briarproject.android.api.AndroidNotificationManager; import org.briarproject.android.fragment.BaseEventFragment; -import org.briarproject.android.sharing.InvitationsForumActivity; +import org.briarproject.android.sharing.ForumInvitationActivity; import org.briarproject.android.view.BriarRecyclerView; import org.briarproject.api.clients.MessageTracker.GroupCount; import org.briarproject.api.db.DbException; @@ -220,10 +220,10 @@ public class ForumListFragment extends BaseEventFragment implements if (availableCount == 0) { snackbar.dismiss(); } else { - snackbar.show(); snackbar.setText(getResources().getQuantityString( R.plurals.forums_shared, availableCount, availableCount)); + if (!snackbar.isShownOrQueued()) snackbar.show(); } } }); @@ -286,7 +286,7 @@ public class ForumListFragment extends BaseEventFragment implements @Override public void onClick(View view) { // snackbar click - Intent i = new Intent(getContext(), InvitationsForumActivity.class); + Intent i = new Intent(getContext(), ForumInvitationActivity.class); startActivity(i); } } diff --git a/briar-android/src/org/briarproject/android/privategroup/invitation/GroupInvitationActivity.java b/briar-android/src/org/briarproject/android/privategroup/invitation/GroupInvitationActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..658a611d4d31856f7f82485d929438791b13acbf --- /dev/null +++ b/briar-android/src/org/briarproject/android/privategroup/invitation/GroupInvitationActivity.java @@ -0,0 +1,47 @@ +package org.briarproject.android.privategroup.invitation; + +import android.content.Context; + +import org.briarproject.R; +import org.briarproject.android.ActivityComponent; +import org.briarproject.android.sharing.InvitationActivity; +import org.briarproject.android.sharing.InvitationAdapter; +import org.briarproject.api.privategroup.invitation.GroupInvitationItem; + +import javax.inject.Inject; + +import static org.briarproject.android.sharing.InvitationAdapter.InvitationClickListener; + +public class GroupInvitationActivity + extends InvitationActivity<GroupInvitationItem> { + + @Inject + protected GroupInvitationController controller; + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + @Override + protected GroupInvitationController getController() { + return controller; + } + + @Override + protected InvitationAdapter<GroupInvitationItem, ?> getAdapter(Context ctx, + InvitationClickListener<GroupInvitationItem> listener) { + return new GroupInvitationAdapter(ctx, listener); + } + + @Override + protected int getAcceptRes() { + return R.string.groups_invitations_joined; + } + + @Override + protected int getDeclineRes() { + return R.string.groups_invitations_declined; + } + +} diff --git a/briar-android/src/org/briarproject/android/privategroup/invitation/GroupInvitationAdapter.java b/briar-android/src/org/briarproject/android/privategroup/invitation/GroupInvitationAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..9c818b68070b26af9db47e26e71e88b8e02dd030 --- /dev/null +++ b/briar-android/src/org/briarproject/android/privategroup/invitation/GroupInvitationAdapter.java @@ -0,0 +1,28 @@ +package org.briarproject.android.privategroup.invitation; + +import android.content.Context; +import android.view.ViewGroup; + +import org.briarproject.android.sharing.InvitationAdapter; +import org.briarproject.api.privategroup.invitation.GroupInvitationItem; + +class GroupInvitationAdapter extends + InvitationAdapter<GroupInvitationItem, GroupInvitationViewHolder> { + + GroupInvitationAdapter(Context ctx, + InvitationClickListener<GroupInvitationItem> listener) { + super(ctx, GroupInvitationItem.class, listener); + } + + @Override + public GroupInvitationViewHolder onCreateViewHolder(ViewGroup parent, + int viewType) { + return new GroupInvitationViewHolder(getView(parent)); + } + + @Override + public boolean areContentsTheSame(GroupInvitationItem item1, + GroupInvitationItem item2) { + return item1.isSubscribed() == item2.isSubscribed(); + } +} diff --git a/briar-android/src/org/briarproject/android/privategroup/invitation/GroupInvitationController.java b/briar-android/src/org/briarproject/android/privategroup/invitation/GroupInvitationController.java new file mode 100644 index 0000000000000000000000000000000000000000..54318f31b2dc5b96e4852e1650d8cd27696e556b --- /dev/null +++ b/briar-android/src/org/briarproject/android/privategroup/invitation/GroupInvitationController.java @@ -0,0 +1,8 @@ +package org.briarproject.android.privategroup.invitation; + +import org.briarproject.android.sharing.InvitationController; +import org.briarproject.api.privategroup.invitation.GroupInvitationItem; + +public interface GroupInvitationController + extends InvitationController<GroupInvitationItem> { +} diff --git a/briar-android/src/org/briarproject/android/privategroup/invitation/GroupInvitationControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/invitation/GroupInvitationControllerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..b48a8743774d67ac6b5f0195a95bceb4c375723d --- /dev/null +++ b/briar-android/src/org/briarproject/android/privategroup/invitation/GroupInvitationControllerImpl.java @@ -0,0 +1,83 @@ +package org.briarproject.android.privategroup.invitation; + +import org.briarproject.android.controller.handler.ResultExceptionHandler; +import org.briarproject.android.sharing.InvitationControllerImpl; +import org.briarproject.api.contact.Contact; +import org.briarproject.api.db.DatabaseExecutor; +import org.briarproject.api.db.DbException; +import org.briarproject.api.event.Event; +import org.briarproject.api.event.EventBus; +import org.briarproject.api.event.GroupInvitationReceivedEvent; +import org.briarproject.api.lifecycle.LifecycleManager; +import org.briarproject.api.privategroup.PrivateGroup; +import org.briarproject.api.privategroup.PrivateGroupManager; +import org.briarproject.api.privategroup.invitation.GroupInvitationItem; +import org.briarproject.api.privategroup.invitation.GroupInvitationManager; +import org.briarproject.api.sync.ClientId; + +import java.util.Collection; +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +import static java.util.logging.Level.WARNING; + +public class GroupInvitationControllerImpl + extends InvitationControllerImpl<GroupInvitationItem> + implements GroupInvitationController { + + private final PrivateGroupManager privateGroupManager; + private final GroupInvitationManager groupInvitationManager; + + @Inject + GroupInvitationControllerImpl(@DatabaseExecutor Executor dbExecutor, + LifecycleManager lifecycleManager, EventBus eventBus, + PrivateGroupManager privateGroupManager, + GroupInvitationManager groupInvitationManager) { + super(dbExecutor, lifecycleManager, eventBus); + this.privateGroupManager = privateGroupManager; + this.groupInvitationManager = groupInvitationManager; + } + + @Override + public void eventOccurred(Event e) { + super.eventOccurred(e); + + if (e instanceof GroupInvitationReceivedEvent) { + LOG.info("Group invitation received, reloading"); + listener.loadInvitations(false); + } + } + + @Override + protected ClientId getShareableClientId() { + return privateGroupManager.getClientId(); + } + + @Override + protected Collection<GroupInvitationItem> getInvitations() + throws DbException { + return groupInvitationManager.getInvitations(); + } + + @Override + public void respondToInvitation(final GroupInvitationItem item, + final boolean accept, + final ResultExceptionHandler<Void, DbException> handler) { + runOnDbThread(new Runnable() { + @Override + public void run() { + try { + PrivateGroup g = item.getShareable(); + Contact c = item.getCreator(); + groupInvitationManager.respondToInvitation(g, c, accept); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + handler.onException(e); + } + } + }); + } + +} diff --git a/briar-android/src/org/briarproject/android/privategroup/invitation/GroupInvitationViewHolder.java b/briar-android/src/org/briarproject/android/privategroup/invitation/GroupInvitationViewHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..25aef8ce8d2df68022ee0c7dd4e0286b85347c99 --- /dev/null +++ b/briar-android/src/org/briarproject/android/privategroup/invitation/GroupInvitationViewHolder.java @@ -0,0 +1,28 @@ +package org.briarproject.android.privategroup.invitation; + +import android.support.annotation.Nullable; +import android.view.View; + +import org.briarproject.R; +import org.briarproject.android.sharing.InvitationAdapter.InvitationClickListener; +import org.briarproject.android.sharing.InvitationViewHolder; +import org.briarproject.api.privategroup.invitation.GroupInvitationItem; + +public class GroupInvitationViewHolder extends InvitationViewHolder<GroupInvitationItem> { + + public GroupInvitationViewHolder(View v) { + super(v); + } + + @Override + public void onBind(@Nullable final GroupInvitationItem item, + final InvitationClickListener<GroupInvitationItem> listener) { + super.onBind(item, listener); + if (item == null) return; + + sharedBy.setText( + sharedBy.getContext().getString(R.string.groups_created_by, + item.getCreator().getAuthor().getName())); + } + +} \ No newline at end of file diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupListController.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListController.java index fbe05315d01fcb316fb57ef3f74b95e27f5d107a..f102f82e49dd0641709f6b52a85f59e39a6aecb5 100644 --- a/briar-android/src/org/briarproject/android/privategroup/list/GroupListController.java +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListController.java @@ -30,6 +30,9 @@ public interface GroupListController extends DbController { void removeGroup(GroupId g, ResultExceptionHandler<Void, DbException> result); + void loadAvailableGroups( + ResultExceptionHandler<Integer, DbException> result); + interface GroupListListener extends DestroyableContext { @UiThread diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java index efa36fe5f2079e2bc818e0d0baad8b53e428d4c7..76856f244d627722d468826586a55b92dbc56bb6 100644 --- a/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java @@ -20,6 +20,7 @@ import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.privategroup.GroupMessageHeader; import org.briarproject.api.privategroup.PrivateGroup; import org.briarproject.api.privategroup.PrivateGroupManager; +import org.briarproject.api.privategroup.invitation.GroupInvitationManager; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.GroupId; @@ -41,6 +42,7 @@ public class GroupListControllerImpl extends DbControllerImpl Logger.getLogger(GroupListControllerImpl.class.getName()); private final PrivateGroupManager groupManager; + private final GroupInvitationManager groupInvitationManager; private final EventBus eventBus; private final AndroidNotificationManager notificationManager; private final IdentityManager identityManager; @@ -50,10 +52,12 @@ public class GroupListControllerImpl extends DbControllerImpl @Inject GroupListControllerImpl(@DatabaseExecutor Executor dbExecutor, LifecycleManager lifecycleManager, PrivateGroupManager groupManager, - EventBus eventBus, AndroidNotificationManager notificationManager, + GroupInvitationManager groupInvitationManager, EventBus eventBus, + AndroidNotificationManager notificationManager, IdentityManager identityManager) { super(dbExecutor, lifecycleManager); this.groupManager = groupManager; + this.groupInvitationManager = groupInvitationManager; this.eventBus = eventBus; this.notificationManager = notificationManager; this.identityManager = identityManager; @@ -187,4 +191,22 @@ public class GroupListControllerImpl extends DbControllerImpl }); } + @Override + public void loadAvailableGroups( + final ResultExceptionHandler<Integer, DbException> handler) { + runOnDbThread(new Runnable() { + @Override + public void run() { + try { + handler.onResult( + groupInvitationManager.getInvitations().size()); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + handler.onException(e); + } + } + }); + } + } diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java index 9e77003acedb1f79f17c76cac2df0309848ed2ad..64727401dc37727b4b04e227267afaa6277924d0 100644 --- a/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java @@ -4,13 +4,16 @@ import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.annotation.UiThread; +import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityOptionsCompat; +import android.support.v4.content.ContextCompat; import android.support.v7.widget.LinearLayoutManager; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; +import android.view.View.OnClickListener; import android.view.ViewGroup; import org.briarproject.R; @@ -18,6 +21,7 @@ import org.briarproject.android.ActivityComponent; import org.briarproject.android.controller.handler.UiResultExceptionHandler; import org.briarproject.android.fragment.BaseFragment; import org.briarproject.android.privategroup.creation.CreateGroupActivity; +import org.briarproject.android.privategroup.invitation.GroupInvitationActivity; import org.briarproject.android.privategroup.list.GroupListController.GroupListListener; import org.briarproject.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener; import org.briarproject.android.view.BriarRecyclerView; @@ -30,10 +34,11 @@ import java.util.logging.Logger; import javax.inject.Inject; +import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE; import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation; public class GroupListFragment extends BaseFragment implements - GroupListListener, OnGroupRemoveClickListener { + GroupListListener, OnGroupRemoveClickListener, OnClickListener { public final static String TAG = GroupListFragment.class.getName(); private static final Logger LOG = Logger.getLogger(TAG); @@ -47,6 +52,7 @@ public class GroupListFragment extends BaseFragment implements private BriarRecyclerView list; private GroupListAdapter adapter; + private Snackbar snackbar; @Nullable @Override @@ -61,6 +67,12 @@ public class GroupListFragment extends BaseFragment implements list.setLayoutManager(new LinearLayoutManager(getContext())); list.setAdapter(adapter); + snackbar = Snackbar.make(list, "", LENGTH_INDEFINITE); + snackbar.getView().setBackgroundResource(R.color.briar_primary); + snackbar.setAction(R.string.show, this); + snackbar.setActionTextColor(ContextCompat + .getColor(getContext(), R.color.briar_button_positive)); + return v; } @@ -76,6 +88,7 @@ public class GroupListFragment extends BaseFragment implements controller.onStart(); list.startPeriodicUpdate(); loadGroups(); + loadAvailableGroups(); } @Override @@ -180,4 +193,40 @@ public class GroupListFragment extends BaseFragment implements }); } + private void loadAvailableGroups() { + controller.loadAvailableGroups( + new UiResultExceptionHandler<Integer, DbException>(this) { + @Override + public void onResultUi(Integer num) { + if (num == 0) { + snackbar.dismiss(); + } else { + snackbar.setText(getResources().getQuantityString( + R.plurals.groups_invitations_open, num, + num)); + if (!snackbar.isShownOrQueued()) snackbar.show(); + } + } + + @Override + public void onExceptionUi(DbException exception) { + // TODO handle this error + finish(); + } + }); + } + + /** + * This method is handling the available groups snackbar action + */ + @Override + public void onClick(View v) { + Intent i = new Intent(getContext(), GroupInvitationActivity.class); + ActivityOptionsCompat options = + makeCustomAnimation(getActivity(), + android.R.anim.slide_in_left, + android.R.anim.slide_out_right); + startActivity(i, options.toBundle()); + } + } diff --git a/briar-android/src/org/briarproject/android/sharing/BlogInvitationActivity.java b/briar-android/src/org/briarproject/android/sharing/BlogInvitationActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..54ffa984dd6fdc812c3d646f890607158a61bca8 --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/BlogInvitationActivity.java @@ -0,0 +1,46 @@ +package org.briarproject.android.sharing; + +import android.content.Context; + +import org.briarproject.R; +import org.briarproject.android.ActivityComponent; +import org.briarproject.api.sharing.SharingInvitationItem; + +import javax.inject.Inject; + +import static org.briarproject.android.sharing.InvitationAdapter.InvitationClickListener; + +public class BlogInvitationActivity + extends InvitationActivity<SharingInvitationItem> { + + @Inject + BlogInvitationController controller; + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + @Override + protected InvitationController<SharingInvitationItem> getController() { + return controller; + } + + @Override + protected InvitationAdapter<SharingInvitationItem, ?> getAdapter( + Context ctx, + InvitationClickListener<SharingInvitationItem> listener) { + return new SharingInvitationAdapter(ctx, listener); + } + + @Override + protected int getAcceptRes() { + return R.string.blogs_sharing_joined_toast; + } + + @Override + protected int getDeclineRes() { + return R.string.blogs_sharing_declined_toast; + } + +} diff --git a/briar-android/src/org/briarproject/android/sharing/BlogInvitationAdapter.java b/briar-android/src/org/briarproject/android/sharing/BlogInvitationAdapter.java deleted file mode 100644 index 7b0e7dfa6e108067aed9470e96f88921216518de..0000000000000000000000000000000000000000 --- a/briar-android/src/org/briarproject/android/sharing/BlogInvitationAdapter.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.briarproject.android.sharing; - -import android.content.Context; - -import org.briarproject.R; -import org.briarproject.api.blogs.Blog; -import org.briarproject.api.sharing.InvitationItem; - -class BlogInvitationAdapter extends InvitationAdapter { - - BlogInvitationAdapter(Context ctx, AvailableForumClickListener listener) { - super(ctx, listener); - } - - @Override - public void onBindViewHolder(InvitationsViewHolder ui, int position) { - super.onBindViewHolder(ui, position); - InvitationItem item = getItemAt(position); - if (item == null) return; - - Blog blog = (Blog) item.getShareable(); - - ui.avatar.setAuthorAvatar(blog.getAuthor()); - - ui.name.setText(ctx.getString(R.string.blogs_personal_blog, - blog.getAuthor().getName())); - - if (item.isSubscribed()) { - ui.subscribed.setText(ctx.getString(R.string.blogs_sharing_exists)); - } - } - - @Override - public int compare(InvitationItem o1, InvitationItem o2) { - return String.CASE_INSENSITIVE_ORDER - .compare(((Blog) o1.getShareable()).getAuthor().getName(), - ((Blog) o2.getShareable()).getAuthor().getName()); - } - -} diff --git a/briar-android/src/org/briarproject/android/sharing/BlogInvitationController.java b/briar-android/src/org/briarproject/android/sharing/BlogInvitationController.java new file mode 100644 index 0000000000000000000000000000000000000000..ebd447369e9ea28e1a22be632729a546ddf29430 --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/BlogInvitationController.java @@ -0,0 +1,7 @@ +package org.briarproject.android.sharing; + +import org.briarproject.api.sharing.SharingInvitationItem; + +public interface BlogInvitationController + extends InvitationController<SharingInvitationItem> { +} diff --git a/briar-android/src/org/briarproject/android/sharing/BlogInvitationControllerImpl.java b/briar-android/src/org/briarproject/android/sharing/BlogInvitationControllerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..49507b4f4036bf7d0bb7444266ed180482d0181b --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/BlogInvitationControllerImpl.java @@ -0,0 +1,82 @@ +package org.briarproject.android.sharing; + +import org.briarproject.android.controller.handler.ResultExceptionHandler; +import org.briarproject.api.blogs.Blog; +import org.briarproject.api.blogs.BlogManager; +import org.briarproject.api.blogs.BlogSharingManager; +import org.briarproject.api.contact.Contact; +import org.briarproject.api.db.DatabaseExecutor; +import org.briarproject.api.db.DbException; +import org.briarproject.api.event.BlogInvitationReceivedEvent; +import org.briarproject.api.event.Event; +import org.briarproject.api.event.EventBus; +import org.briarproject.api.lifecycle.LifecycleManager; +import org.briarproject.api.sharing.SharingInvitationItem; +import org.briarproject.api.sync.ClientId; + +import java.util.Collection; +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +import static java.util.logging.Level.WARNING; + +public class BlogInvitationControllerImpl + extends InvitationControllerImpl<SharingInvitationItem> + implements BlogInvitationController { + + private final BlogManager blogManager; + private final BlogSharingManager blogSharingManager; + + @Inject + BlogInvitationControllerImpl(@DatabaseExecutor Executor dbExecutor, + LifecycleManager lifecycleManager, EventBus eventBus, + BlogManager blogManager, BlogSharingManager blogSharingManager) { + super(dbExecutor, lifecycleManager, eventBus); + this.blogManager = blogManager; + this.blogSharingManager = blogSharingManager; + } + + @Override + public void eventOccurred(Event e) { + super.eventOccurred(e); + + if (e instanceof BlogInvitationReceivedEvent) { + LOG.info("Blog invitation received, reloading"); + listener.loadInvitations(false); + } + } + + @Override + protected ClientId getShareableClientId() { + return blogManager.getClientId(); + } + + @Override + protected Collection<SharingInvitationItem> getInvitations() throws DbException { + return blogSharingManager.getInvitations(); + } + + @Override + public void respondToInvitation(final SharingInvitationItem item, + final boolean accept, + final ResultExceptionHandler<Void, DbException> handler) { + runOnDbThread(new Runnable() { + @Override + public void run() { + try { + Blog f = (Blog) item.getShareable(); + for (Contact c : item.getNewSharers()) { + // TODO: What happens if a contact has been removed? + blogSharingManager.respondToInvitation(f, c, accept); + } + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + handler.onException(e); + } + } + }); + } + +} diff --git a/briar-android/src/org/briarproject/android/sharing/SharingStatusBlogActivity.java b/briar-android/src/org/briarproject/android/sharing/BlogSharingStatusActivity.java similarity index 93% rename from briar-android/src/org/briarproject/android/sharing/SharingStatusBlogActivity.java rename to briar-android/src/org/briarproject/android/sharing/BlogSharingStatusActivity.java index 5c7be6deea8fe71ce783d8315b5e6fe406b200a4..fed2c2a5af49d217e6c9b1b8dfe9733a3b38b2d9 100644 --- a/briar-android/src/org/briarproject/android/sharing/SharingStatusBlogActivity.java +++ b/briar-android/src/org/briarproject/android/sharing/BlogSharingStatusActivity.java @@ -9,7 +9,7 @@ import java.util.Collection; import javax.inject.Inject; -public class SharingStatusBlogActivity extends SharingStatusActivity { +public class BlogSharingStatusActivity extends SharingStatusActivity { // Fields that are accessed from background threads must be volatile @Inject diff --git a/briar-android/src/org/briarproject/android/sharing/ForumInvitationActivity.java b/briar-android/src/org/briarproject/android/sharing/ForumInvitationActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..37dccd442617edd04b0d61e33bb26fdb216c7f3d --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/ForumInvitationActivity.java @@ -0,0 +1,46 @@ +package org.briarproject.android.sharing; + +import android.content.Context; + +import org.briarproject.R; +import org.briarproject.android.ActivityComponent; +import org.briarproject.api.sharing.SharingInvitationItem; + +import javax.inject.Inject; + +import static org.briarproject.android.sharing.InvitationAdapter.InvitationClickListener; + +public class ForumInvitationActivity + extends InvitationActivity<SharingInvitationItem> { + + @Inject + ForumInvitationController controller; + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + @Override + protected InvitationController<SharingInvitationItem> getController() { + return controller; + } + + @Override + protected InvitationAdapter<SharingInvitationItem, ?> getAdapter( + Context ctx, + InvitationClickListener<SharingInvitationItem> listener) { + return new SharingInvitationAdapter(ctx, listener); + } + + @Override + protected int getAcceptRes() { + return R.string.forum_joined_toast; + } + + @Override + protected int getDeclineRes() { + return R.string.forum_declined_toast; + } + +} diff --git a/briar-android/src/org/briarproject/android/sharing/ForumInvitationAdapter.java b/briar-android/src/org/briarproject/android/sharing/ForumInvitationAdapter.java deleted file mode 100644 index cfe914a9c2949f7b74b42dc508232f2aae2f865a..0000000000000000000000000000000000000000 --- a/briar-android/src/org/briarproject/android/sharing/ForumInvitationAdapter.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.briarproject.android.sharing; - -import android.content.Context; - -import org.briarproject.api.forum.Forum; -import org.briarproject.api.sharing.InvitationItem; - -class ForumInvitationAdapter extends InvitationAdapter { - - ForumInvitationAdapter(Context ctx, AvailableForumClickListener listener) { - super(ctx, listener); - } - - @Override - public void onBindViewHolder(InvitationsViewHolder ui, int position) { - super.onBindViewHolder(ui, position); - InvitationItem item = getItemAt(position); - if (item == null) return; - - Forum forum = (Forum) item.getShareable(); - - ui.avatar.setText(forum.getName().substring(0, 1)); - ui.avatar.setBackgroundBytes(item.getShareable().getId().getBytes()); - - ui.name.setText(forum.getName()); - } - - @Override - public int compare(InvitationItem o1, InvitationItem o2) { - return String.CASE_INSENSITIVE_ORDER - .compare(((Forum) o1.getShareable()).getName(), - ((Forum) o2.getShareable()).getName()); - } - -} diff --git a/briar-android/src/org/briarproject/android/sharing/ForumInvitationController.java b/briar-android/src/org/briarproject/android/sharing/ForumInvitationController.java new file mode 100644 index 0000000000000000000000000000000000000000..74ee19ee27f25a5ab6c0c0320861d3028378aa01 --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/ForumInvitationController.java @@ -0,0 +1,7 @@ +package org.briarproject.android.sharing; + +import org.briarproject.api.sharing.SharingInvitationItem; + +public interface ForumInvitationController + extends InvitationController<SharingInvitationItem> { +} diff --git a/briar-android/src/org/briarproject/android/sharing/ForumInvitationControllerImpl.java b/briar-android/src/org/briarproject/android/sharing/ForumInvitationControllerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..4418900b821007374e46e6aca24fe70364e19ba9 --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/ForumInvitationControllerImpl.java @@ -0,0 +1,83 @@ +package org.briarproject.android.sharing; + +import org.briarproject.android.controller.handler.ResultExceptionHandler; +import org.briarproject.api.contact.Contact; +import org.briarproject.api.db.DatabaseExecutor; +import org.briarproject.api.db.DbException; +import org.briarproject.api.event.Event; +import org.briarproject.api.event.EventBus; +import org.briarproject.api.event.ForumInvitationReceivedEvent; +import org.briarproject.api.forum.Forum; +import org.briarproject.api.forum.ForumManager; +import org.briarproject.api.forum.ForumSharingManager; +import org.briarproject.api.lifecycle.LifecycleManager; +import org.briarproject.api.sharing.SharingInvitationItem; +import org.briarproject.api.sync.ClientId; + +import java.util.Collection; +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +import static java.util.logging.Level.WARNING; + +public class ForumInvitationControllerImpl + extends InvitationControllerImpl<SharingInvitationItem> + implements ForumInvitationController { + + private final ForumManager forumManager; + private final ForumSharingManager forumSharingManager; + + @Inject + ForumInvitationControllerImpl(@DatabaseExecutor Executor dbExecutor, + LifecycleManager lifecycleManager, EventBus eventBus, + ForumManager forumManager, + ForumSharingManager forumSharingManager) { + super(dbExecutor, lifecycleManager, eventBus); + this.forumManager = forumManager; + this.forumSharingManager = forumSharingManager; + } + + @Override + public void eventOccurred(Event e) { + super.eventOccurred(e); + + if (e instanceof ForumInvitationReceivedEvent) { + LOG.info("Forum invitation received, reloading"); + listener.loadInvitations(false); + } + } + + @Override + protected ClientId getShareableClientId() { + return forumManager.getClientId(); + } + + @Override + protected Collection<SharingInvitationItem> getInvitations() throws DbException { + return forumSharingManager.getInvitations(); + } + + @Override + public void respondToInvitation(final SharingInvitationItem item, + final boolean accept, + final ResultExceptionHandler<Void, DbException> handler) { + runOnDbThread(new Runnable() { + @Override + public void run() { + try { + Forum f = (Forum) item.getShareable(); + for (Contact c : item.getNewSharers()) { + // TODO: What happens if a contact has been removed? + forumSharingManager.respondToInvitation(f, c, accept); + } + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + handler.onException(e); + } + } + }); + } + +} diff --git a/briar-android/src/org/briarproject/android/sharing/SharingStatusForumActivity.java b/briar-android/src/org/briarproject/android/sharing/ForumSharingStatusActivity.java similarity index 93% rename from briar-android/src/org/briarproject/android/sharing/SharingStatusForumActivity.java rename to briar-android/src/org/briarproject/android/sharing/ForumSharingStatusActivity.java index ef3b333a0a41b62a071069613106902ef2e647a4..be51ab2cf4648090fc4c0d23bdfc0fbc642578fb 100644 --- a/briar-android/src/org/briarproject/android/sharing/SharingStatusForumActivity.java +++ b/briar-android/src/org/briarproject/android/sharing/ForumSharingStatusActivity.java @@ -9,7 +9,7 @@ import java.util.Collection; import javax.inject.Inject; -public class SharingStatusForumActivity extends SharingStatusActivity { +public class ForumSharingStatusActivity extends SharingStatusActivity { // Fields that are accessed from background threads must be volatile @Inject diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java b/briar-android/src/org/briarproject/android/sharing/InvitationActivity.java similarity index 54% rename from briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java rename to briar-android/src/org/briarproject/android/sharing/InvitationActivity.java index 772be9bf605e7dd472ffb152a2f395b360147988..5a17037134a6174dbdb114e118a8f01a47d38351 100644 --- a/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java +++ b/briar-android/src/org/briarproject/android/sharing/InvitationActivity.java @@ -2,39 +2,34 @@ package org.briarproject.android.sharing; import android.content.Context; import android.os.Bundle; -import android.support.annotation.CallSuper; +import android.support.annotation.StringRes; import android.support.v7.widget.LinearLayoutManager; import android.widget.Toast; import org.briarproject.R; import org.briarproject.android.BriarActivity; +import org.briarproject.android.controller.handler.UiResultExceptionHandler; +import org.briarproject.android.sharing.InvitationController.InvitationListener; import org.briarproject.android.view.BriarRecyclerView; -import org.briarproject.api.event.ContactRemovedEvent; -import org.briarproject.api.event.Event; -import org.briarproject.api.event.EventBus; -import org.briarproject.api.event.EventListener; +import org.briarproject.api.db.DbException; import org.briarproject.api.sharing.InvitationItem; import java.util.Collection; import java.util.logging.Logger; -import javax.inject.Inject; - import static android.widget.Toast.LENGTH_SHORT; -import static org.briarproject.android.sharing.InvitationAdapter.AvailableForumClickListener; +import static org.briarproject.android.sharing.InvitationAdapter.InvitationClickListener; -abstract class InvitationsActivity extends BriarActivity - implements EventListener, AvailableForumClickListener { +public abstract class InvitationActivity<I extends InvitationItem> + extends BriarActivity + implements InvitationListener, InvitationClickListener<I> { protected static final Logger LOG = - Logger.getLogger(InvitationsActivity.class.getName()); + Logger.getLogger(InvitationActivity.class.getName()); - protected InvitationAdapter adapter; + private InvitationAdapter<I, ?> adapter; private BriarRecyclerView list; - @Inject - EventBus eventBus; - @Override public void onCreate(Bundle state) { super.onCreate(state); @@ -42,7 +37,6 @@ abstract class InvitationsActivity extends BriarActivity setContentView(R.layout.list); adapter = getAdapter(this, this); - list = (BriarRecyclerView) findViewById(R.id.list); if (list != null) { list.setLayoutManager(new LinearLayoutManager(this)); @@ -50,32 +44,24 @@ abstract class InvitationsActivity extends BriarActivity } } + abstract protected InvitationAdapter<I, ?> getAdapter(Context ctx, + InvitationClickListener<I> listener); + @Override public void onStart() { super.onStart(); - eventBus.addListener(this); loadInvitations(false); } @Override public void onStop() { super.onStop(); - eventBus.removeListener(this); adapter.clear(); list.showProgressBar(); } @Override - @CallSuper - public void eventOccurred(Event e) { - if (e instanceof ContactRemovedEvent) { - LOG.info("Contact removed, reloading..."); - loadInvitations(true); - } - } - - @Override - public void onItemClick(InvitationItem item, boolean accept) { + public void onItemClick(I item, boolean accept) { respondToInvitation(item, accept); // show toast @@ -91,26 +77,58 @@ abstract class InvitationsActivity extends BriarActivity } } - abstract protected InvitationAdapter getAdapter(Context ctx, - AvailableForumClickListener listener); + @Override + public void loadInvitations(final boolean clear) { + final int revision = adapter.getRevision(); + getController().loadInvitations(clear, + new UiResultExceptionHandler<Collection<I>, DbException>( + this) { + @Override + public void onResultUi(Collection<I> items) { + displayInvitations(revision, items, clear); + } + + @Override + public void onExceptionUi(DbException exception) { + // TODO proper error handling + finish(); + } + }); + } + + abstract protected InvitationController<I> getController(); + + protected void respondToInvitation(final I item, + final boolean accept) { + getController().respondToInvitation(item, accept, + new UiResultExceptionHandler<Void, DbException>(this) { + @Override + public void onResultUi(Void result) { - abstract protected void loadInvitations(boolean clear); + } - abstract protected void respondToInvitation(final InvitationItem item, - final boolean accept); + @Override + public void onExceptionUi(DbException exception) { + // TODO proper error handling + finish(); + } + }); + } + @StringRes abstract protected int getAcceptRes(); + @StringRes abstract protected int getDeclineRes(); protected void displayInvitations(final int revision, - final Collection<InvitationItem> invitations, final boolean clear) { + final Collection<I> invitations, final boolean clear) { runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { if (invitations.isEmpty()) { LOG.info("No more invitations available, finishing"); - finish(); + supportFinishAfterTransition(); } else if (revision == adapter.getRevision()) { adapter.incrementRevision(); if (clear) adapter.setItems(invitations); diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationAdapter.java b/briar-android/src/org/briarproject/android/sharing/InvitationAdapter.java index db35fd55bc4dbd0ee3a416d6112167eee42514f1..5afd24c07c66b2750810dc195a6505dcc44e234e 100644 --- a/briar-android/src/org/briarproject/android/sharing/InvitationAdapter.java +++ b/briar-android/src/org/briarproject/android/sharing/InvitationAdapter.java @@ -1,111 +1,51 @@ package org.briarproject.android.sharing; import android.content.Context; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; -import android.widget.TextView; import org.briarproject.R; import org.briarproject.android.util.BriarAdapter; -import org.briarproject.android.view.TextAvatarView; -import org.briarproject.api.contact.Contact; import org.briarproject.api.sharing.InvitationItem; -import org.briarproject.util.StringUtils; -import java.util.ArrayList; -import java.util.Collection; +public abstract class InvitationAdapter<I extends InvitationItem, VH extends InvitationViewHolder<I>> + extends BriarAdapter<I, VH> { -import static android.view.View.GONE; -import static android.view.View.VISIBLE; + private final InvitationClickListener<I> listener; -abstract class InvitationAdapter extends - BriarAdapter<InvitationItem, InvitationAdapter.InvitationsViewHolder> { - - private final AvailableForumClickListener listener; - - InvitationAdapter(Context ctx, AvailableForumClickListener listener) { - super(ctx, InvitationItem.class); + public InvitationAdapter(Context ctx, Class<I> c, + InvitationClickListener<I> listener) { + super(ctx, c); this.listener = listener; } - @Override - public InvitationsViewHolder onCreateViewHolder(ViewGroup parent, - int viewType) { - - View v = LayoutInflater.from(ctx) - .inflate(R.layout.list_item_invitations, parent, false); - return new InvitationsViewHolder(v); + protected View getView(ViewGroup parent) { + return LayoutInflater.from(ctx) + .inflate(R.layout.list_item_invitations, parent, false); } @Override - public void onBindViewHolder(InvitationsViewHolder ui, int position) { - final InvitationItem item = getItemAt(position); + public void onBindViewHolder(VH ui, int position) { + final I item = getItemAt(position); if (item == null) return; - - Collection<String> names = new ArrayList<>(); - for (Contact c : item.getNewSharers()) - names.add(c.getAuthor().getName()); - ui.sharedBy.setText(ctx.getString(R.string.shared_by_format, - StringUtils.join(names, ", "))); - - if (item.isSubscribed()) { - ui.subscribed.setVisibility(VISIBLE); - } else { - ui.subscribed.setVisibility(GONE); - } - - ui.accept.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - listener.onItemClick(item, true); - } - }); - ui.decline.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - listener.onItemClick(item, false); - } - }); + ui.onBind(item, listener); } @Override - public boolean areContentsTheSame(InvitationItem oldItem, - InvitationItem newItem) { - return oldItem.isSubscribed() == newItem.isSubscribed() && - oldItem.getNewSharers().equals(newItem.getNewSharers()); + public boolean areItemsTheSame(I oldItem, I newItem) { + return oldItem.getShareable().equals(newItem.getShareable()); } @Override - public boolean areItemsTheSame(InvitationItem oldItem, - InvitationItem newItem) { - return oldItem.getShareable().equals(newItem.getShareable()); + public int compare(I o1, I o2) { + return String.CASE_INSENSITIVE_ORDER + .compare((o1.getShareable()).getName(), + (o2.getShareable()).getName()); } - static class InvitationsViewHolder extends RecyclerView.ViewHolder { - - final TextAvatarView avatar; - final TextView name; - private final TextView sharedBy; - final TextView subscribed; - private final Button accept; - private final Button decline; - - private InvitationsViewHolder(View v) { - super(v); - - avatar = (TextAvatarView) v.findViewById(R.id.avatarView); - name = (TextView) v.findViewById(R.id.forumNameView); - sharedBy = (TextView) v.findViewById(R.id.sharedByView); - subscribed = (TextView) v.findViewById(R.id.forumSubscribedView); - accept = (Button) v.findViewById(R.id.acceptButton); - decline = (Button) v.findViewById(R.id.declineButton); - } + public interface InvitationClickListener<I> { + void onItemClick(I item, boolean accept); } - interface AvailableForumClickListener { - void onItemClick(InvitationItem item, boolean accept); - } } diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationController.java b/briar-android/src/org/briarproject/android/sharing/InvitationController.java new file mode 100644 index 0000000000000000000000000000000000000000..5ea5c88e362d7ca84d6338e7e01bab1cd60592d4 --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/InvitationController.java @@ -0,0 +1,25 @@ +package org.briarproject.android.sharing; + +import org.briarproject.android.controller.ActivityLifecycleController; +import org.briarproject.android.controller.handler.ResultExceptionHandler; +import org.briarproject.api.db.DbException; +import org.briarproject.api.sharing.InvitationItem; + +import java.util.Collection; + +public interface InvitationController<I extends InvitationItem> + extends ActivityLifecycleController { + + void loadInvitations(boolean clear, + ResultExceptionHandler<Collection<I>, DbException> handler); + + void respondToInvitation(I item, boolean accept, + ResultExceptionHandler<Void, DbException> handler); + + interface InvitationListener { + + void loadInvitations(boolean clear); + + } + +} diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationControllerImpl.java b/briar-android/src/org/briarproject/android/sharing/InvitationControllerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..b1b52aca58da293c7fe0a19fc9b488fe9a4242d2 --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/InvitationControllerImpl.java @@ -0,0 +1,116 @@ +package org.briarproject.android.sharing; + +import android.app.Activity; +import android.support.annotation.CallSuper; + +import org.briarproject.android.controller.DbControllerImpl; +import org.briarproject.android.controller.handler.ResultExceptionHandler; +import org.briarproject.api.db.DatabaseExecutor; +import org.briarproject.api.db.DbException; +import org.briarproject.api.event.ContactRemovedEvent; +import org.briarproject.api.event.Event; +import org.briarproject.api.event.EventBus; +import org.briarproject.api.event.EventListener; +import org.briarproject.api.event.GroupAddedEvent; +import org.briarproject.api.event.GroupRemovedEvent; +import org.briarproject.api.lifecycle.LifecycleManager; +import org.briarproject.api.sharing.InvitationItem; +import org.briarproject.api.sync.ClientId; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; + +public abstract class InvitationControllerImpl<I extends InvitationItem> + extends DbControllerImpl + implements InvitationController<I>, EventListener { + + protected static final Logger LOG = + Logger.getLogger(InvitationControllerImpl.class.getName()); + + private final EventBus eventBus; + protected InvitationListener listener; + + public InvitationControllerImpl(@DatabaseExecutor Executor dbExecutor, + LifecycleManager lifecycleManager, EventBus eventBus) { + super(dbExecutor, lifecycleManager); + this.eventBus = eventBus; + } + + @Override + public void onActivityCreate(Activity activity) { + listener = (InvitationListener) activity; + } + + @Override + public void onActivityStart() { + eventBus.addListener(this); + } + + @Override + public void onActivityStop() { + eventBus.removeListener(this); + } + + @Override + public void onActivityDestroy() { + + } + + @CallSuper + @Override + public void eventOccurred(Event e) { + if (e instanceof ContactRemovedEvent) { + LOG.info("Contact removed, reloading..."); + listener.loadInvitations(true); + } else if (e instanceof GroupAddedEvent) { + GroupAddedEvent g = (GroupAddedEvent) e; + ClientId cId = g.getGroup().getClientId(); + if (cId.equals(getShareableClientId())) { + LOG.info("Group added, reloading"); + listener.loadInvitations(false); + } + } else if (e instanceof GroupRemovedEvent) { + GroupRemovedEvent g = (GroupRemovedEvent) e; + ClientId cId = g.getGroup().getClientId(); + if (cId.equals(getShareableClientId())) { + LOG.info("Group removed, reloading"); + listener.loadInvitations(false); + } + } + } + + protected abstract ClientId getShareableClientId(); + + @Override + public void loadInvitations(final boolean clear, + final ResultExceptionHandler<Collection<I>, DbException> handler) { + runOnDbThread(new Runnable() { + @Override + public void run() { + Collection<I> invitations = new ArrayList<>(); + try { + long now = System.currentTimeMillis(); + invitations.addAll(getInvitations()); + long duration = System.currentTimeMillis() - now; + if (LOG.isLoggable(INFO)) + LOG.info( + "Loading invitations took " + duration + " ms"); + handler.onResult(invitations); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + handler.onException(e); + } + } + }); + } + + @DatabaseExecutor + protected abstract Collection<I> getInvitations() throws DbException; + +} diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationViewHolder.java b/briar-android/src/org/briarproject/android/sharing/InvitationViewHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..2c8337c13c3271ac3eb95d601f6bb45739c55e97 --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/InvitationViewHolder.java @@ -0,0 +1,69 @@ +package org.briarproject.android.sharing; + +import android.support.annotation.CallSuper; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import org.briarproject.R; +import org.briarproject.android.sharing.InvitationAdapter.InvitationClickListener; +import org.briarproject.android.view.TextAvatarView; +import org.briarproject.api.sharing.InvitationItem; + +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + +public class InvitationViewHolder<I extends InvitationItem> + extends RecyclerView.ViewHolder { + + private final TextAvatarView avatar; + private final TextView name; + protected final TextView sharedBy; + private final TextView subscribed; + private final Button accept; + private final Button decline; + + public InvitationViewHolder(View v) { + super(v); + + avatar = (TextAvatarView) v.findViewById(R.id.avatarView); + name = (TextView) v.findViewById(R.id.forumNameView); + sharedBy = (TextView) v.findViewById(R.id.sharedByView); + subscribed = (TextView) v.findViewById(R.id.forumSubscribedView); + accept = (Button) v.findViewById(R.id.acceptButton); + decline = (Button) v.findViewById(R.id.declineButton); + } + + @CallSuper + public void onBind(@Nullable final I item, + final InvitationClickListener<I> listener) { + if (item == null) return; + + avatar.setText(item.getShareable().getName().substring(0, 1)); + avatar.setBackgroundBytes(item.getShareable().getId().getBytes()); + + name.setText(item.getShareable().getName()); + + if (item.isSubscribed()) { + subscribed.setVisibility(VISIBLE); + } else { + subscribed.setVisibility(GONE); + } + + accept.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.onItemClick(item, true); + } + }); + decline.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.onItemClick(item, false); + } + }); + } + +} \ No newline at end of file diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsBlogActivity.java b/briar-android/src/org/briarproject/android/sharing/InvitationsBlogActivity.java deleted file mode 100644 index 1b0e5f3bd7be5ce10c72991abb40792584d66bd0..0000000000000000000000000000000000000000 --- a/briar-android/src/org/briarproject/android/sharing/InvitationsBlogActivity.java +++ /dev/null @@ -1,122 +0,0 @@ -package org.briarproject.android.sharing; - -import android.content.Context; - -import org.briarproject.R; -import org.briarproject.android.ActivityComponent; -import org.briarproject.api.blogs.Blog; -import org.briarproject.api.blogs.BlogManager; -import org.briarproject.api.blogs.BlogSharingManager; -import org.briarproject.api.contact.Contact; -import org.briarproject.api.db.DbException; -import org.briarproject.api.event.BlogInvitationReceivedEvent; -import org.briarproject.api.event.Event; -import org.briarproject.api.event.GroupAddedEvent; -import org.briarproject.api.event.GroupRemovedEvent; -import org.briarproject.api.sharing.InvitationItem; -import org.briarproject.api.sync.ClientId; - -import java.util.ArrayList; -import java.util.Collection; - -import javax.inject.Inject; - -import static java.util.logging.Level.INFO; -import static java.util.logging.Level.WARNING; -import static org.briarproject.android.sharing.InvitationAdapter.AvailableForumClickListener; - -public class InvitationsBlogActivity extends InvitationsActivity { - - // Fields that are accessed from background threads must be volatile - @Inject - volatile BlogManager blogManager; - @Inject - volatile BlogSharingManager blogSharingManager; - - @Override - public void injectActivity(ActivityComponent component) { - component.inject(this); - } - - @Override - public void eventOccurred(Event e) { - super.eventOccurred(e); - - if (e instanceof GroupAddedEvent) { - GroupAddedEvent g = (GroupAddedEvent) e; - ClientId cId = g.getGroup().getClientId(); - if (cId.equals(blogManager.getClientId())) { - LOG.info("Blog added, reloading"); - loadInvitations(false); - } - } else if (e instanceof GroupRemovedEvent) { - GroupRemovedEvent g = (GroupRemovedEvent) e; - ClientId cId = g.getGroup().getClientId(); - if (cId.equals(blogManager.getClientId())) { - LOG.info("Blog removed, reloading"); - loadInvitations(false); - } - } else if (e instanceof BlogInvitationReceivedEvent) { - LOG.info("Blog invitation received, reloading"); - loadInvitations(false); - } - } - - @Override - protected InvitationAdapter getAdapter(Context ctx, - AvailableForumClickListener listener) { - return new BlogInvitationAdapter(ctx, listener); - } - - @Override - protected void loadInvitations(final boolean clear) { - final int revision = adapter.getRevision(); - runOnDbThread(new Runnable() { - @Override - public void run() { - try { - Collection<InvitationItem> invitations = new ArrayList<>(); - long now = System.currentTimeMillis(); - invitations.addAll(blogSharingManager.getInvitations()); - long duration = System.currentTimeMillis() - now; - if (LOG.isLoggable(INFO)) - LOG.info("Load took " + duration + " ms"); - displayInvitations(revision, invitations, clear); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - } - } - }); - } - - @Override - protected void respondToInvitation(final InvitationItem item, - final boolean accept) { - runOnDbThread(new Runnable() { - @Override - public void run() { - try { - Blog b = (Blog) item.getShareable(); - for (Contact c : item.getNewSharers()) { - // TODO: What happens if a contact has been removed? - blogSharingManager.respondToInvitation(b, c, accept); - } - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - } - } - }); - } - - @Override - protected int getAcceptRes() { - return R.string.blogs_sharing_joined_toast; - } - - @Override - protected int getDeclineRes() { - return R.string.blogs_sharing_declined_toast; - } -} diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsForumActivity.java b/briar-android/src/org/briarproject/android/sharing/InvitationsForumActivity.java deleted file mode 100644 index d64dab69f04af496aa5469aa79464c8eaa82d5a9..0000000000000000000000000000000000000000 --- a/briar-android/src/org/briarproject/android/sharing/InvitationsForumActivity.java +++ /dev/null @@ -1,122 +0,0 @@ -package org.briarproject.android.sharing; - -import android.content.Context; - -import org.briarproject.R; -import org.briarproject.android.ActivityComponent; -import org.briarproject.api.contact.Contact; -import org.briarproject.api.db.DbException; -import org.briarproject.api.event.Event; -import org.briarproject.api.event.ForumInvitationReceivedEvent; -import org.briarproject.api.event.GroupAddedEvent; -import org.briarproject.api.event.GroupRemovedEvent; -import org.briarproject.api.forum.Forum; -import org.briarproject.api.forum.ForumManager; -import org.briarproject.api.forum.ForumSharingManager; -import org.briarproject.api.sharing.InvitationItem; -import org.briarproject.api.sync.ClientId; - -import java.util.ArrayList; -import java.util.Collection; - -import javax.inject.Inject; - -import static java.util.logging.Level.INFO; -import static java.util.logging.Level.WARNING; -import static org.briarproject.android.sharing.InvitationAdapter.AvailableForumClickListener; - -public class InvitationsForumActivity extends InvitationsActivity { - - // Fields that are accessed from background threads must be volatile - @Inject - volatile ForumManager forumManager; - @Inject - volatile ForumSharingManager forumSharingManager; - - @Override - public void injectActivity(ActivityComponent component) { - component.inject(this); - } - - @Override - public void eventOccurred(Event e) { - super.eventOccurred(e); - - if (e instanceof GroupAddedEvent) { - GroupAddedEvent g = (GroupAddedEvent) e; - ClientId cId = g.getGroup().getClientId(); - if (cId.equals(forumManager.getClientId())) { - LOG.info("Forum added, reloading"); - loadInvitations(false); - } - } else if (e instanceof GroupRemovedEvent) { - GroupRemovedEvent g = (GroupRemovedEvent) e; - ClientId cId = g.getGroup().getClientId(); - if (cId.equals(forumManager.getClientId())) { - LOG.info("Forum removed, reloading"); - loadInvitations(false); - } - } else if (e instanceof ForumInvitationReceivedEvent) { - LOG.info("Forum invitation received, reloading"); - loadInvitations(false); - } - } - - @Override - protected InvitationAdapter getAdapter(Context ctx, - AvailableForumClickListener listener) { - return new ForumInvitationAdapter(ctx, listener); - } - - @Override - protected void loadInvitations(final boolean clear) { - final int revision = adapter.getRevision(); - runOnDbThread(new Runnable() { - @Override - public void run() { - try { - Collection<InvitationItem> invitations = new ArrayList<>(); - long now = System.currentTimeMillis(); - invitations.addAll(forumSharingManager.getInvitations()); - long duration = System.currentTimeMillis() - now; - if (LOG.isLoggable(INFO)) - LOG.info("Load took " + duration + " ms"); - displayInvitations(revision, invitations, clear); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - } - } - }); - } - - @Override - protected void respondToInvitation(final InvitationItem item, - final boolean accept) { - runOnDbThread(new Runnable() { - @Override - public void run() { - try { - Forum f = (Forum) item.getShareable(); - for (Contact c : item.getNewSharers()) { - // TODO: What happens if a contact has been removed? - forumSharingManager.respondToInvitation(f, c, accept); - } - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - } - } - }); - } - - @Override - protected int getAcceptRes() { - return R.string.forum_joined_toast; - } - - @Override - protected int getDeclineRes() { - return R.string.forum_declined_toast; - } -} diff --git a/briar-android/src/org/briarproject/android/sharing/SharingInvitationAdapter.java b/briar-android/src/org/briarproject/android/sharing/SharingInvitationAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..2964c9b930db4f376bd028d2d5d3b0db98d8796d --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/SharingInvitationAdapter.java @@ -0,0 +1,29 @@ +package org.briarproject.android.sharing; + +import android.content.Context; +import android.view.ViewGroup; + +import org.briarproject.api.sharing.SharingInvitationItem; + +class SharingInvitationAdapter extends + InvitationAdapter<SharingInvitationItem, SharingInvitationViewHolder> { + + SharingInvitationAdapter(Context ctx, InvitationClickListener listener) { + super(ctx, SharingInvitationItem.class, listener); + } + + @Override + public SharingInvitationViewHolder onCreateViewHolder( + ViewGroup parent, + int viewType) { + return new SharingInvitationViewHolder(getView(parent)); + } + + @Override + public boolean areContentsTheSame(SharingInvitationItem oldItem, + SharingInvitationItem newItem) { + return oldItem.isSubscribed() == newItem.isSubscribed() && + oldItem.getNewSharers().equals(newItem.getNewSharers()); + } + +} diff --git a/briar-android/src/org/briarproject/android/sharing/SharingInvitationViewHolder.java b/briar-android/src/org/briarproject/android/sharing/SharingInvitationViewHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..b10672468f1ada6f65c482c3c84576f4020527af --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/SharingInvitationViewHolder.java @@ -0,0 +1,35 @@ +package org.briarproject.android.sharing; + +import android.support.annotation.Nullable; +import android.view.View; + +import org.briarproject.R; +import org.briarproject.api.contact.Contact; +import org.briarproject.api.sharing.SharingInvitationItem; +import org.briarproject.util.StringUtils; + +import java.util.ArrayList; +import java.util.Collection; + +public class SharingInvitationViewHolder + extends InvitationViewHolder<SharingInvitationItem> { + + public SharingInvitationViewHolder(View v) { + super(v); + } + + @Override + public void onBind(@Nullable final SharingInvitationItem item, + final InvitationAdapter.InvitationClickListener<SharingInvitationItem> listener) { + super.onBind(item, listener); + if (item == null) return; + + Collection<String> names = new ArrayList<>(); + for (Contact c : item.getNewSharers()) + names.add(c.getAuthor().getName()); + sharedBy.setText( + sharedBy.getContext().getString(R.string.shared_by_format, + StringUtils.join(names, ", "))); + } + +} diff --git a/briar-api/src/org/briarproject/api/clients/BaseMessage.java b/briar-api/src/org/briarproject/api/clients/BaseMessage.java index 776bfcf5c93c694e2436b25f7ba5b8db43b5b986..fbfb56337789d1e2cb3dbee512fc5e170b04b84e 100644 --- a/briar-api/src/org/briarproject/api/clients/BaseMessage.java +++ b/briar-api/src/org/briarproject/api/clients/BaseMessage.java @@ -1,21 +1,26 @@ package org.briarproject.api.clients; +import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault public abstract class BaseMessage { private final Message message; + @Nullable private final MessageId parent; - public BaseMessage(@NotNull Message message, @Nullable MessageId parent) { + public BaseMessage(Message message, @Nullable MessageId parent) { this.message = message; this.parent = parent; } - @NotNull public Message getMessage() { return message; } diff --git a/briar-api/src/org/briarproject/api/event/BlogInvitationReceivedEvent.java b/briar-api/src/org/briarproject/api/event/BlogInvitationReceivedEvent.java index 26a2ae097d81b4a7068661990191ca1db4aceb0e..2089e6c5d03c388c5bf8960bd5861eceddfadd60 100644 --- a/briar-api/src/org/briarproject/api/event/BlogInvitationReceivedEvent.java +++ b/briar-api/src/org/briarproject/api/event/BlogInvitationReceivedEvent.java @@ -5,17 +5,11 @@ import org.briarproject.api.contact.ContactId; import org.briarproject.api.sharing.InvitationRequest; public class BlogInvitationReceivedEvent extends - InvitationRequestReceivedEvent { - - private final Blog blog; + InvitationRequestReceivedEvent<Blog> { public BlogInvitationReceivedEvent(Blog blog, ContactId contactId, InvitationRequest request) { - super(contactId, request); - this.blog = blog; + super(blog, contactId, request); } - public Blog getBlog() { - return blog; - } } diff --git a/briar-api/src/org/briarproject/api/event/ForumInvitationReceivedEvent.java b/briar-api/src/org/briarproject/api/event/ForumInvitationReceivedEvent.java index 3077f91605de30b7a6d8bf105f7f058e754f995a..416979d2e400017a22adc914aafee71d992fb704 100644 --- a/briar-api/src/org/briarproject/api/event/ForumInvitationReceivedEvent.java +++ b/briar-api/src/org/briarproject/api/event/ForumInvitationReceivedEvent.java @@ -5,18 +5,11 @@ import org.briarproject.api.forum.Forum; import org.briarproject.api.forum.ForumInvitationRequest; public class ForumInvitationReceivedEvent extends - InvitationRequestReceivedEvent { - - private final Forum forum; + InvitationRequestReceivedEvent<Forum> { public ForumInvitationReceivedEvent(Forum forum, ContactId contactId, ForumInvitationRequest request) { - super(contactId, request); - this.forum = forum; - } - - public Forum getForum() { - return forum; + super(forum, contactId, request); } } diff --git a/briar-api/src/org/briarproject/api/event/GroupInvitationReceivedEvent.java b/briar-api/src/org/briarproject/api/event/GroupInvitationReceivedEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..c8244a39248f621d4a829002afb232eed4f7208c --- /dev/null +++ b/briar-api/src/org/briarproject/api/event/GroupInvitationReceivedEvent.java @@ -0,0 +1,16 @@ +package org.briarproject.api.event; + +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.forum.ForumInvitationRequest; +import org.briarproject.api.privategroup.PrivateGroup; +import org.briarproject.api.privategroup.invitation.GroupInvitationRequest; + +public class GroupInvitationReceivedEvent extends + InvitationRequestReceivedEvent<PrivateGroup> { + + public GroupInvitationReceivedEvent(PrivateGroup group, ContactId contactId, + GroupInvitationRequest request) { + super(group, contactId, request); + } + +} diff --git a/briar-api/src/org/briarproject/api/event/InvitationRequestReceivedEvent.java b/briar-api/src/org/briarproject/api/event/InvitationRequestReceivedEvent.java index 184a88f5387a5e8fae71d4771c7d58e1893f1e50..f9bcfe1a5c792b2e9b6ef2490c53730224aa4e0a 100644 --- a/briar-api/src/org/briarproject/api/event/InvitationRequestReceivedEvent.java +++ b/briar-api/src/org/briarproject/api/event/InvitationRequestReceivedEvent.java @@ -2,14 +2,18 @@ package org.briarproject.api.event; import org.briarproject.api.contact.ContactId; import org.briarproject.api.sharing.InvitationRequest; +import org.briarproject.api.sharing.Shareable; -public abstract class InvitationRequestReceivedEvent extends Event { +public abstract class InvitationRequestReceivedEvent<S extends Shareable> + extends Event { + private final S shareable; private final ContactId contactId; private final InvitationRequest request; - InvitationRequestReceivedEvent(ContactId contactId, + InvitationRequestReceivedEvent(S shareable, ContactId contactId, InvitationRequest request) { + this.shareable = shareable; this.contactId = contactId; this.request = request; } @@ -21,4 +25,8 @@ public abstract class InvitationRequestReceivedEvent extends Event { public InvitationRequest getRequest() { return request; } + + public S getShareable() { + return shareable; + } } diff --git a/briar-api/src/org/briarproject/api/forum/ForumInvitationRequest.java b/briar-api/src/org/briarproject/api/forum/ForumInvitationRequest.java index 3e90116c0b63554169b2dbc382b4fb8367c13461..5d4c8d5542d29e2f2c6397fe71063f8938cee7db 100644 --- a/briar-api/src/org/briarproject/api/forum/ForumInvitationRequest.java +++ b/briar-api/src/org/briarproject/api/forum/ForumInvitationRequest.java @@ -5,16 +5,15 @@ import org.briarproject.api.contact.ContactId; import org.briarproject.api.sharing.InvitationRequest; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.MessageId; -import org.jetbrains.annotations.Nullable; public class ForumInvitationRequest extends InvitationRequest { private final String forumName; public ForumInvitationRequest(MessageId id, SessionId sessionId, - GroupId groupId, ContactId contactId, String forumName, String message, - boolean available, long time, boolean local, boolean sent, - boolean seen, boolean read) { + GroupId groupId, ContactId contactId, String forumName, + String message, boolean available, long time, boolean local, + boolean sent, boolean seen, boolean read) { super(id, sessionId, groupId, contactId, message, available, time, local, sent, seen, read); diff --git a/briar-api/src/org/briarproject/api/privategroup/GroupMessage.java b/briar-api/src/org/briarproject/api/privategroup/GroupMessage.java index 22457de2eba5053e7fc189637bf1f22360061bf5..06f277460e7828d4231bbefc58b4a127e9b7be2c 100644 --- a/briar-api/src/org/briarproject/api/privategroup/GroupMessage.java +++ b/briar-api/src/org/briarproject/api/privategroup/GroupMessage.java @@ -2,17 +2,22 @@ package org.briarproject.api.privategroup; import org.briarproject.api.clients.BaseMessage; import org.briarproject.api.identity.Author; +import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.sync.Message; import org.briarproject.api.sync.MessageId; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault public class GroupMessage extends BaseMessage { private final Author author; - public GroupMessage(@NotNull Message message, @Nullable MessageId parent, - @NotNull Author author) { + public GroupMessage(Message message, @Nullable MessageId parent, + Author author) { super(message, parent); this.author = author; } diff --git a/briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java b/briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java index 310611361b960acc44ef6b5e9f6d30207f488360..9b57d999108f822d2d777cef6ef391db9c7a6810 100644 --- a/briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java +++ b/briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java @@ -3,6 +3,7 @@ package org.briarproject.api.privategroup; import org.briarproject.api.clients.NamedGroup; import org.briarproject.api.identity.Author; import org.briarproject.api.nullsafety.NotNullByDefault; +import org.briarproject.api.sharing.Shareable; import org.briarproject.api.sync.Group; import org.jetbrains.annotations.NotNull; @@ -10,7 +11,7 @@ import javax.annotation.concurrent.Immutable; @Immutable @NotNullByDefault -public class PrivateGroup extends NamedGroup { +public class PrivateGroup extends NamedGroup implements Shareable { private final Author author; diff --git a/briar-api/src/org/briarproject/api/privategroup/invitation/GroupInvitationConstants.java b/briar-api/src/org/briarproject/api/privategroup/invitation/GroupInvitationConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..a22272b22b2f3bb4549e7abebc4e65dc2677694a --- /dev/null +++ b/briar-api/src/org/briarproject/api/privategroup/invitation/GroupInvitationConstants.java @@ -0,0 +1,8 @@ +package org.briarproject.api.privategroup.invitation; + +public interface GroupInvitationConstants { + + // Group Metadata Keys + String CONTACT_ID = "contactId"; + +} diff --git a/briar-api/src/org/briarproject/api/privategroup/invitation/GroupInvitationItem.java b/briar-api/src/org/briarproject/api/privategroup/invitation/GroupInvitationItem.java new file mode 100644 index 0000000000000000000000000000000000000000..5cb23afee77575127930ea215d866d8179995fd9 --- /dev/null +++ b/briar-api/src/org/briarproject/api/privategroup/invitation/GroupInvitationItem.java @@ -0,0 +1,27 @@ +package org.briarproject.api.privategroup.invitation; + +import org.briarproject.api.contact.Contact; +import org.briarproject.api.nullsafety.NotNullByDefault; +import org.briarproject.api.privategroup.PrivateGroup; +import org.briarproject.api.sharing.InvitationItem; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +public class GroupInvitationItem extends InvitationItem<PrivateGroup> { + + private final Contact creator; + + public GroupInvitationItem(PrivateGroup shareable, boolean subscribed, + Contact creator) { + super(shareable, subscribed); + + this.creator = creator; + } + + public Contact getCreator() { + return creator; + } + +} diff --git a/briar-api/src/org/briarproject/api/privategroup/invitation/GroupInvitationManager.java b/briar-api/src/org/briarproject/api/privategroup/invitation/GroupInvitationManager.java new file mode 100644 index 0000000000000000000000000000000000000000..a5ca33dae143336d889b38e657f3c10ca6090a3b --- /dev/null +++ b/briar-api/src/org/briarproject/api/privategroup/invitation/GroupInvitationManager.java @@ -0,0 +1,48 @@ +package org.briarproject.api.privategroup.invitation; + +import org.briarproject.api.clients.MessageTracker; +import org.briarproject.api.clients.SessionId; +import org.briarproject.api.contact.Contact; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.db.DbException; +import org.briarproject.api.privategroup.PrivateGroup; +import org.briarproject.api.sharing.InvitationMessage; +import org.briarproject.api.sync.ClientId; +import org.briarproject.api.sync.GroupId; + +import java.util.Collection; + +public interface GroupInvitationManager extends MessageTracker { + + /** Returns the unique ID of the private group invitation client. */ + ClientId getClientId(); + + /** + * Sends an invitation to share the given forum with the given contact + * and sends an optional message along with it. + */ + void sendInvitation(GroupId groupId, ContactId contactId, + String message) throws DbException; + + /** + * Responds to a pending private group invitation + */ + void respondToInvitation(PrivateGroup g, Contact c, boolean accept) + throws DbException; + + /** + * Responds to a pending private group invitation + */ + void respondToInvitation(SessionId id, boolean accept) throws DbException; + + /** + * Returns all private group invitation messages related to the contact + * identified by contactId. + */ + Collection<InvitationMessage> getInvitationMessages( + ContactId contactId) throws DbException; + + /** Returns all private groups to which the user has been invited. */ + Collection<GroupInvitationItem> getInvitations() throws DbException; + +} diff --git a/briar-api/src/org/briarproject/api/privategroup/invitation/GroupInvitationRequest.java b/briar-api/src/org/briarproject/api/privategroup/invitation/GroupInvitationRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..3115a6bc27d49a8c32e1ef28c03afc4b1cca4028 --- /dev/null +++ b/briar-api/src/org/briarproject/api/privategroup/invitation/GroupInvitationRequest.java @@ -0,0 +1,39 @@ +package org.briarproject.api.privategroup.invitation; + +import org.briarproject.api.clients.SessionId; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.identity.Author; +import org.briarproject.api.nullsafety.NotNullByDefault; +import org.briarproject.api.sharing.InvitationRequest; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.MessageId; + +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.ThreadSafe; + +@Immutable +@NotNullByDefault +public class GroupInvitationRequest extends InvitationRequest { + + private final String groupName; + private final Author creator; + + public GroupInvitationRequest(MessageId id, SessionId sessionId, + GroupId groupId, Author creator, ContactId contactId, + String groupName, String message, boolean available, long time, + boolean local, boolean sent, boolean seen, boolean read) { + super(id, sessionId, groupId, contactId, message, available, time, + local, sent, seen, read); + this.groupName = groupName; + this.creator = creator; + } + + public String getGroupName() { + return groupName; + } + + public Author getCreator() { + return creator; + } + +} diff --git a/briar-api/src/org/briarproject/api/privategroup/invitation/GroupInvitationResponse.java b/briar-api/src/org/briarproject/api/privategroup/invitation/GroupInvitationResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..dda5d554ac6ce1b8b78cc8a68f271038a695b457 --- /dev/null +++ b/briar-api/src/org/briarproject/api/privategroup/invitation/GroupInvitationResponse.java @@ -0,0 +1,40 @@ +package org.briarproject.api.privategroup.invitation; + +import org.briarproject.api.clients.SessionId; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.identity.Author; +import org.briarproject.api.nullsafety.NotNullByDefault; +import org.briarproject.api.sharing.InvitationResponse; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.MessageId; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.ThreadSafe; + +@Immutable +@NotNullByDefault +public class GroupInvitationResponse extends InvitationResponse { + + private final String groupName; + private final Author creator; + + public GroupInvitationResponse(MessageId id, SessionId sessionId, + GroupId groupId, String groupName, Author creator, + ContactId contactId, boolean accept, long time, boolean local, + boolean sent, boolean seen, boolean read) { + super(id, sessionId, groupId, contactId, accept, time, local, sent, + seen, read); + this.groupName = groupName; + this.creator = creator; + } + + public String getGroupName() { + return groupName; + } + + public Author getCreator() { + return creator; + } + +} diff --git a/briar-api/src/org/briarproject/api/sharing/InvitationItem.java b/briar-api/src/org/briarproject/api/sharing/InvitationItem.java index bc180ea13f011cd895048d46c4b54ee79030462b..c81a4730d87bd00faf9acab6e481f0fcfbc325ec 100644 --- a/briar-api/src/org/briarproject/api/sharing/InvitationItem.java +++ b/briar-api/src/org/briarproject/api/sharing/InvitationItem.java @@ -1,32 +1,36 @@ package org.briarproject.api.sharing; -import org.briarproject.api.contact.Contact; +import org.briarproject.api.nullsafety.NotNullByDefault; +import org.briarproject.api.sync.GroupId; -import java.util.Collection; +import javax.annotation.concurrent.Immutable; -public class InvitationItem { +@Immutable +@NotNullByDefault +public abstract class InvitationItem<S extends Shareable> { - private final Shareable shareable; + private final S shareable; private final boolean subscribed; - private final Collection<Contact> newSharers; - - public InvitationItem(Shareable shareable, boolean subscribed, - Collection<Contact> newSharers) { + public InvitationItem(S shareable, boolean subscribed) { this.shareable = shareable; this.subscribed = subscribed; - this.newSharers = newSharers; } - public Shareable getShareable() { + public S getShareable() { return shareable; } + public GroupId getId() { + return shareable.getId(); + } + + public String getName() { + return shareable.getName(); + } + public boolean isSubscribed() { return subscribed; } - public Collection<Contact> getNewSharers() { - return newSharers; - } } diff --git a/briar-api/src/org/briarproject/api/sharing/Shareable.java b/briar-api/src/org/briarproject/api/sharing/Shareable.java index 13c11fdeeff5b39d3f775b293281bcfabc08d097..144b06dde1db37991a3258b4970d91dfbbfa7443 100644 --- a/briar-api/src/org/briarproject/api/sharing/Shareable.java +++ b/briar-api/src/org/briarproject/api/sharing/Shareable.java @@ -8,4 +8,7 @@ public interface Shareable { GroupId getId(); Group getGroup(); + + String getName(); + } diff --git a/briar-api/src/org/briarproject/api/sharing/SharingInvitationItem.java b/briar-api/src/org/briarproject/api/sharing/SharingInvitationItem.java new file mode 100644 index 0000000000000000000000000000000000000000..04b69f49e9cd4a65be8afd3805edcd42069a03f0 --- /dev/null +++ b/briar-api/src/org/briarproject/api/sharing/SharingInvitationItem.java @@ -0,0 +1,27 @@ +package org.briarproject.api.sharing; + +import org.briarproject.api.contact.Contact; +import org.briarproject.api.nullsafety.NotNullByDefault; + +import java.util.Collection; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +public class SharingInvitationItem extends InvitationItem<Shareable> { + + private final Collection<Contact> newSharers; + + public SharingInvitationItem(Shareable shareable, boolean subscribed, + Collection<Contact> newSharers) { + super(shareable, subscribed); + + this.newSharers = newSharers; + } + + public Collection<Contact> getNewSharers() { + return newSharers; + } + +} diff --git a/briar-api/src/org/briarproject/api/sharing/SharingManager.java b/briar-api/src/org/briarproject/api/sharing/SharingManager.java index b4613cd94035ef209d7d82b24c83a6126a841453..c7b1c8004954c5af181b8a1cb092beb8f16adb84 100644 --- a/briar-api/src/org/briarproject/api/sharing/SharingManager.java +++ b/briar-api/src/org/briarproject/api/sharing/SharingManager.java @@ -1,6 +1,7 @@ package org.briarproject.api.sharing; import org.briarproject.api.clients.MessageTracker; +import org.briarproject.api.clients.SessionId; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; import org.briarproject.api.db.DbException; @@ -30,7 +31,14 @@ public interface SharingManager<S extends Shareable> extends MessageTracker { throws DbException; /** - * Returns all group sharing messages sent by the given contact. + * Responds to a pending group invitation + */ + void respondToInvitation(SessionId id, boolean accept) + throws DbException; + + /** + * Returns all group sharing messages sent by the Contact + * identified by contactId. */ Collection<InvitationMessage> getInvitationMessages( ContactId contactId) throws DbException; @@ -38,7 +46,7 @@ public interface SharingManager<S extends Shareable> extends MessageTracker { /** * Returns all invitations to groups. */ - Collection<InvitationItem> getInvitations() throws DbException; + Collection<SharingInvitationItem> getInvitations() throws DbException; /** * Returns all contacts who are sharing the given group with us. diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java index 562b95c308cf3d25ca63a982631a56bc72f14c9d..570f697a629d952be8790dd5b27e04e1a21a2a90 100644 --- a/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java +++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupModule.java @@ -1,17 +1,19 @@ package org.briarproject.privategroup; import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.contact.ContactManager; import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.data.MetadataEncoder; import org.briarproject.api.identity.AuthorFactory; +import org.briarproject.api.lifecycle.LifecycleManager; +import org.briarproject.api.messaging.ConversationManager; import org.briarproject.api.privategroup.GroupMessageFactory; import org.briarproject.api.privategroup.PrivateGroupFactory; import org.briarproject.api.privategroup.PrivateGroupManager; -import org.briarproject.api.sync.GroupFactory; +import org.briarproject.api.privategroup.invitation.GroupInvitationManager; import org.briarproject.api.sync.ValidationManager; import org.briarproject.api.system.Clock; - -import java.security.SecureRandom; +import org.briarproject.privategroup.invitation.GroupInvitationManagerImpl; import javax.inject.Inject; import javax.inject.Singleton; @@ -25,6 +27,8 @@ public class PrivateGroupModule { public static class EagerSingletons { @Inject GroupMessageValidator groupMessageValidator; + @Inject + GroupInvitationManager groupInvitationManager; } @Provides @@ -65,4 +69,22 @@ public class PrivateGroupModule { return validator; } + @Provides + @Singleton + GroupInvitationManager provideGroupInvitationManager( + LifecycleManager lifecycleManager, ContactManager contactManager, + GroupInvitationManagerImpl groupInvitationManager, + ConversationManager conversationManager, + ValidationManager validationManager) { + + validationManager.registerIncomingMessageHook( + groupInvitationManager.getClientId(), groupInvitationManager); + lifecycleManager.registerClient(groupInvitationManager); + contactManager.registerAddContactHook(groupInvitationManager); + contactManager.registerRemoveContactHook(groupInvitationManager); + conversationManager.registerConversationClient(groupInvitationManager); + + return groupInvitationManager; + } + } diff --git a/briar-core/src/org/briarproject/privategroup/invitation/GroupInvitationManagerImpl.java b/briar-core/src/org/briarproject/privategroup/invitation/GroupInvitationManagerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..12477ab20b994595192b2e53b2bef17944e0f9c2 --- /dev/null +++ b/briar-core/src/org/briarproject/privategroup/invitation/GroupInvitationManagerImpl.java @@ -0,0 +1,141 @@ +package org.briarproject.privategroup.invitation; + +import org.briarproject.api.FormatException; +import org.briarproject.api.clients.Client; +import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.clients.ContactGroupFactory; +import org.briarproject.api.clients.SessionId; +import org.briarproject.api.contact.Contact; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfList; +import org.briarproject.api.data.MetadataParser; +import org.briarproject.api.db.DatabaseComponent; +import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Transaction; +import org.briarproject.api.messaging.ConversationManager; +import org.briarproject.api.privategroup.PrivateGroup; +import org.briarproject.api.privategroup.invitation.GroupInvitationItem; +import org.briarproject.api.privategroup.invitation.GroupInvitationManager; +import org.briarproject.api.sharing.InvitationMessage; +import org.briarproject.api.sync.ClientId; +import org.briarproject.api.sync.Group; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.Message; +import org.briarproject.clients.ConversationClientImpl; +import org.briarproject.util.StringUtils; + +import java.util.ArrayList; +import java.util.Collection; + +import javax.inject.Inject; + +import static org.briarproject.api.privategroup.invitation.GroupInvitationConstants.CONTACT_ID; + +public class GroupInvitationManagerImpl extends ConversationClientImpl + implements GroupInvitationManager, Client, + ContactManager.AddContactHook, ContactManager.RemoveContactHook, + ConversationManager.ConversationClient { + + private static final ClientId CLIENT_ID = + new ClientId(StringUtils.fromHexString( + "B55231ABFC4A10666CD93D649B1D7F4F" + + "016E65B87BB4C04F4E35613713DBCD13")); + + private final ContactGroupFactory contactGroupFactory; + private final Group localGroup; + + @Inject + protected GroupInvitationManagerImpl(DatabaseComponent db, + ClientHelper clientHelper, MetadataParser metadataParser, + ContactGroupFactory contactGroupFactory) { + super(db, clientHelper, metadataParser); + this.contactGroupFactory = contactGroupFactory; + localGroup = contactGroupFactory.createLocalGroup(getClientId()); + } + + @Override + public ClientId getClientId() { + return CLIENT_ID; + } + + @Override + public void createLocalState(Transaction txn) throws DbException { + db.addGroup(txn, localGroup); + // Ensure we've set things up for any pre-existing contacts + for (Contact c : db.getContacts(txn)) addingContact(txn, c); + } + + @Override + public void addingContact(Transaction txn, Contact c) throws DbException { + try { + // Create a group to share with the contact + Group g = getContactGroup(c); + // Return if we've already set things up for this contact + if (db.containsGroup(txn, g.getId())) return; + // Store the group and share it with the contact + db.addGroup(txn, g); + db.setVisibleToContact(txn, c.getId(), g.getId(), true); + // Attach the contact ID to the group + BdfDictionary meta = new BdfDictionary(); + meta.put(CONTACT_ID, c.getId().getInt()); + clientHelper.mergeGroupMetadata(txn, g.getId(), meta); + } catch (FormatException e) { + throw new DbException(e); + } + } + + @Override + public void removingContact(Transaction txn, Contact c) throws DbException { + // remove the contact group (all messages will be removed with it) + db.removeGroup(txn, getContactGroup(c)); + } + + @Override + protected Group getContactGroup(Contact c) { + return contactGroupFactory.createContactGroup(getClientId(), c); + } + + @Override + protected boolean incomingMessage(Transaction txn, Message m, BdfList body, + BdfDictionary meta) throws DbException, FormatException { + return false; + } + + @Override + public void sendInvitation(GroupId groupId, ContactId contactId, + String message) throws DbException { + + } + + @Override + public void respondToInvitation(PrivateGroup g, Contact c, boolean accept) + throws DbException { + + } + + @Override + public void respondToInvitation(SessionId id, boolean accept) + throws DbException { + + } + + @Override + public Collection<InvitationMessage> getInvitationMessages( + ContactId contactId) throws DbException { + Collection<InvitationMessage> invitations = + new ArrayList<InvitationMessage>(); + + return invitations; + } + + @Override + public Collection<GroupInvitationItem> getInvitations() throws DbException { + Collection<GroupInvitationItem> invitations = + new ArrayList<GroupInvitationItem>(); + + return invitations; + } + +} diff --git a/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java index a7c90d0a78f45edcf201803856d1cce078572517..a355018773d32db16252c9705b01f0faff0b4431 100644 --- a/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java +++ b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java @@ -25,7 +25,7 @@ import org.briarproject.api.event.Event; import org.briarproject.api.event.InvitationRequestReceivedEvent; import org.briarproject.api.event.InvitationResponseReceivedEvent; import org.briarproject.api.identity.LocalAuthor; -import org.briarproject.api.sharing.InvitationItem; +import org.briarproject.api.sharing.SharingInvitationItem; import org.briarproject.api.sharing.InvitationMessage; import org.briarproject.api.sharing.Shareable; import org.briarproject.api.sharing.SharingManager; @@ -316,27 +316,23 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS try { // find session state based on shareable IS localState = getSessionStateForResponse(txn, f, c); + respondToInvitation(txn, localState, accept); + txn.setComplete(); + } catch (FormatException e) { + throw new DbException(e); + } finally { + db.endTransaction(txn); + } + } - // define action - InviteeSessionState.Action localAction; - if (accept) { - localAction = InviteeSessionState.Action.LOCAL_ACCEPT; - } else { - localAction = InviteeSessionState.Action.LOCAL_DECLINE; - } - - // start engine and process its state update - InviteeEngine<IS, IR> engine = - new InviteeEngine<IS, IR>(getIRFactory(), clock); - StateUpdate<IS, BaseMessage> update = - engine.onLocalAction(localState, localAction); - processInviteeStateUpdate(txn, null, update); - - // track message - // TODO handle this properly without engine hacks (#376) - long time = update.toSend.get(0).getTime(); - trackMessage(txn, localState.getGroupId(), time, true); + @Override + public void respondToInvitation(SessionId id, boolean accept) + throws DbException { + Transaction txn = db.startTransaction(false); + try { + IS localState = (IS) getSessionState(txn, id, true); + respondToInvitation(txn, localState, accept); txn.setComplete(); } catch (FormatException e) { throw new DbException(e); @@ -345,6 +341,29 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS } } + private void respondToInvitation(Transaction txn, IS localState, + boolean accept) throws DbException, FormatException { + // define action + InviteeSessionState.Action localAction; + if (accept) { + localAction = InviteeSessionState.Action.LOCAL_ACCEPT; + } else { + localAction = InviteeSessionState.Action.LOCAL_DECLINE; + } + + // start engine and process its state update + InviteeEngine<IS, IR> engine = + new InviteeEngine<IS, IR>(getIRFactory(), clock); + StateUpdate<IS, BaseMessage> update = + engine.onLocalAction(localState, localAction); + processInviteeStateUpdate(txn, null, update); + + // track message + // TODO handle this properly without engine hacks (#376) + long time = update.toSend.get(0).getTime(); + trackMessage(txn, localState.getGroupId(), time, true); + } + @Override public Collection<InvitationMessage> getInvitationMessages(ContactId contactId) throws DbException { @@ -418,8 +437,8 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS } @Override - public Collection<InvitationItem> getInvitations() throws DbException { - List<InvitationItem> invitations = new ArrayList<InvitationItem>(); + public Collection<SharingInvitationItem> getInvitations() throws DbException { + List<SharingInvitationItem> invitations = new ArrayList<SharingInvitationItem>(); Transaction txn = db.startTransaction(true); try { Set<S> shareables = new HashSet<S>(); @@ -445,8 +464,8 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IS for (S s : shareables) { Collection<Contact> newS = newSharers.get(s.getId()); boolean subscribed = db.containsGroup(txn, s.getId()); - InvitationItem invitation = - new InvitationItem(s, subscribed, newS); + SharingInvitationItem invitation = + new SharingInvitationItem(s, subscribed, newS); invitations.add(invitation); } txn.setComplete();