From b09e30a95f3dd83a76f7668bdf3b67e526ade503 Mon Sep 17 00:00:00 2001 From: Torsten Grote <t@grobox.de> Date: Thu, 29 Sep 2016 18:39:37 -0300 Subject: [PATCH] Private Group List --- briar-android/build.gradle | 4 +- briar-android/res/drawable/ic_contacts.xml | 10 + ...c_contacts_black_24dp.xml => ic_group.xml} | 0 .../res/drawable/navigation_drawer_header.png | Bin 0 -> 10893 bytes .../res/layout/activity_invitations.xml | 6 - .../res/layout/fragment_contact_list.xml | 6 - ...roduction_contact_chooser.xml => list.xml} | 6 +- briar-android/res/layout/list_item_forum.xml | 3 +- briar-android/res/layout/list_item_group.xml | 107 +++++++++++ .../res/layout/navigation_header.xml | 23 +-- briar-android/res/layout/text_avatar_view.xml | 8 +- .../res/menu/groups_list_actions.xml | 12 ++ briar-android/res/menu/navigation_drawer.xml | 9 +- briar-android/res/values/dimens.xml | 5 +- briar-android/res/values/strings.xml | 13 ++ briar-android/res/values/styles.xml | 3 - .../android/ActivityComponent.java | 2 + .../briarproject/android/ActivityModule.java | 9 + .../android/AndroidComponent.java | 3 + .../android/BriarFragmentActivity.java | 3 + .../android/NavDrawerActivity.java | 4 + .../android/contact/ContactListFragment.java | 4 +- .../introduction/ContactChooserFragment.java | 7 +- .../android/privategroup/list/GroupItem.java | 78 ++++++++ .../privategroup/list/GroupListAdapter.java | 79 ++++++++ .../list/GroupListController.java | 44 +++++ .../list/GroupListControllerImpl.java | 156 ++++++++++++++++ .../privategroup/list/GroupListFragment.java | 171 ++++++++++++++++++ .../privategroup/list/GroupViewHolder.java | 135 ++++++++++++++ .../sharing/ContactSelectorFragment.java | 5 +- .../android/sharing/InvitationsActivity.java | 4 +- .../android/view/TextAvatarView.java | 3 +- .../briarproject/api/clients/BaseGroup.java | 3 + .../api/event/GroupMessageAddedEvent.java | 36 ++++ .../api/privategroup/GroupMessageHeader.java | 18 +- .../api/privategroup/PrivateGroupManager.java | 6 + .../org/briarproject/CoreEagerSingletons.java | 5 + .../src/org/briarproject/CoreModule.java | 3 + .../privategroup/PrivateGroupManagerImpl.java | 13 ++ 39 files changed, 945 insertions(+), 61 deletions(-) create mode 100644 briar-android/res/drawable/ic_contacts.xml rename briar-android/res/drawable/{ic_contacts_black_24dp.xml => ic_group.xml} (100%) create mode 100644 briar-android/res/drawable/navigation_drawer_header.png delete mode 100644 briar-android/res/layout/activity_invitations.xml delete mode 100644 briar-android/res/layout/fragment_contact_list.xml rename briar-android/res/layout/{introduction_contact_chooser.xml => list.xml} (64%) create mode 100644 briar-android/res/layout/list_item_group.xml create mode 100644 briar-android/res/menu/groups_list_actions.xml create mode 100644 briar-android/src/org/briarproject/android/privategroup/list/GroupItem.java create mode 100644 briar-android/src/org/briarproject/android/privategroup/list/GroupListAdapter.java create mode 100644 briar-android/src/org/briarproject/android/privategroup/list/GroupListController.java create mode 100644 briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java create mode 100644 briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java create mode 100644 briar-android/src/org/briarproject/android/privategroup/list/GroupViewHolder.java create mode 100644 briar-api/src/org/briarproject/api/event/GroupMessageAddedEvent.java diff --git a/briar-android/build.gradle b/briar-android/build.gradle index 11ddb77b20..113bb63dfb 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -32,7 +32,7 @@ dependencies { } compile 'info.guardianproject.panic:panic:0.5' compile 'info.guardianproject.trustedintents:trustedintents:0.2' - compile 'de.hdodenhof:circleimageview:2.0.0' + compile 'de.hdodenhof:circleimageview:2.1.0' compile 'com.google.zxing:core:3.2.1' apt 'com.google.dagger:dagger-compiler:2.0.2' provided 'javax.annotation:jsr250-api:1.0' @@ -53,7 +53,7 @@ dependencyVerification { 'ch.acra:acra:afd5b28934d5166b55f261c85685ad59e8a4ebe9ca1960906afaa8c76d8dc9eb', 'info.guardianproject.panic:panic:a7ed9439826db2e9901649892cf9afbe76f00991b768d8f4c26332d7c9406cb2', 'info.guardianproject.trustedintents:trustedintents:6221456d8821a8d974c2acf86306900237cf6afaaa94a4c9c44e161350f80f3e', - 'de.hdodenhof:circleimageview:c76d936395b50705a3f98c9220c22d2599aeb9e609f559f6048975cfc1f686b8', + 'de.hdodenhof:circleimageview:bcbc588e19e6dcf8c120b1957776bfe229efba5d2fbe5da7156372eeacf65503', 'com.google.zxing:core:b4d82452e7a6bf6ec2698904b332431717ed8f9a850224f295aec89de80f2259', 'com.android.support:support-v4:81ce890f26d35c75ad17d0f998a7e3230330c3b41e0b629566bc744bee89e448', 'com.android.support:appcompat-v7:00f9d93acacd6731f309724054bf51492814b4b2869f16d7d5c0038dcb8c9a0d', diff --git a/briar-android/res/drawable/ic_contacts.xml b/briar-android/res/drawable/ic_contacts.xml new file mode 100644 index 0000000000..cd3e499303 --- /dev/null +++ b/briar-android/res/drawable/ic_contacts.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:alpha="0.54" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M20,0L4,0v2h16L20,0zM4,24h16v-2L4,22v2zM20,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM12,6.75c1.24,0 2.25,1.01 2.25,2.25s-1.01,2.25 -2.25,2.25S9.75,10.24 9.75,9 10.76,6.75 12,6.75zM17,17L7,17v-1.5c0,-1.67 3.33,-2.5 5,-2.5s5,0.83 5,2.5L17,17z"/> +</vector> diff --git a/briar-android/res/drawable/ic_contacts_black_24dp.xml b/briar-android/res/drawable/ic_group.xml similarity index 100% rename from briar-android/res/drawable/ic_contacts_black_24dp.xml rename to briar-android/res/drawable/ic_group.xml diff --git a/briar-android/res/drawable/navigation_drawer_header.png b/briar-android/res/drawable/navigation_drawer_header.png new file mode 100644 index 0000000000000000000000000000000000000000..d829743726116470e35367ab3cc443cd855ccefa GIT binary patch literal 10893 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 literal 0 HcmV?d00001 diff --git a/briar-android/res/layout/activity_invitations.xml b/briar-android/res/layout/activity_invitations.xml deleted file mode 100644 index 4d3d61cea8..0000000000 --- a/briar-android/res/layout/activity_invitations.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<org.briarproject.android.view.BriarRecyclerView - android:id="@+id/invitationsView" - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent"/> diff --git a/briar-android/res/layout/fragment_contact_list.xml b/briar-android/res/layout/fragment_contact_list.xml deleted file mode 100644 index de3bddebe1..0000000000 --- a/briar-android/res/layout/fragment_contact_list.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<org.briarproject.android.view.BriarRecyclerView - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/contactList" - android:layout_width="match_parent" - android:layout_height="match_parent"/> diff --git a/briar-android/res/layout/introduction_contact_chooser.xml b/briar-android/res/layout/list.xml similarity index 64% rename from briar-android/res/layout/introduction_contact_chooser.xml rename to briar-android/res/layout/list.xml index 4a99ecbed9..0fbe7cede5 100644 --- a/briar-android/res/layout/introduction_contact_chooser.xml +++ b/briar-android/res/layout/list.xml @@ -1,8 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> <org.briarproject.android.view.BriarRecyclerView + android:id="@+id/list" xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/contactList" + xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" - tools:listitem="@layout/list_item_contact"/> + app:scrollToEnd="false"/> diff --git a/briar-android/res/layout/list_item_forum.xml b/briar-android/res/layout/list_item_forum.xml index c143b6a22b..5c4df7bd17 100644 --- a/briar-android/res/layout/list_item_forum.xml +++ b/briar-android/res/layout/list_item_forum.xml @@ -15,7 +15,8 @@ android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_centerVertical="true" - android:layout_marginRight="@dimen/listitem_horizontal_margin"/> + android:layout_marginRight="@dimen/listitem_horizontal_margin" + android:layout_marginTop="@dimen/listitem_horizontal_margin"/> <org.thoughtcrime.securesms.components.emoji.EmojiTextView android:id="@+id/forumNameView" diff --git a/briar-android/res/layout/list_item_group.xml b/briar-android/res/layout/list_item_group.xml new file mode 100644 index 0000000000..afb0b642c1 --- /dev/null +++ b/briar-android/res/layout/list_item_group.xml @@ -0,0 +1,107 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout + 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:layout_marginLeft="@dimen/listitem_horizontal_margin" + android:layout_marginStart="@dimen/listitem_horizontal_margin" + android:background="?attr/selectableItemBackground"> + + <org.briarproject.android.view.TextAvatarView + android:id="@+id/avatarView" + android:layout_width="@dimen/listitem_picture_frame_size" + android:layout_height="@dimen/listitem_picture_frame_size" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_marginEnd="@dimen/listitem_horizontal_margin" + android:layout_marginRight="@dimen/listitem_horizontal_margin" + android:layout_marginTop="@dimen/listitem_horizontal_margin"/> + + <org.thoughtcrime.securesms.components.emoji.EmojiTextView + android:id="@+id/nameView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_marginTop="@dimen/listitem_horizontal_margin" + android:layout_toEndOf="@+id/avatarView" + android:layout_toRightOf="@+id/avatarView" + android:maxLines="2" + android:textColor="@color/briar_text_primary" + android:textSize="@dimen/text_size_medium" + tools:text="This is a name of a Private Group"/> + + <org.thoughtcrime.securesms.components.emoji.EmojiTextView + android:id="@+id/creatorView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/nameView" + android:layout_marginBottom="@dimen/margin_small" + android:layout_toEndOf="@+id/avatarView" + android:layout_toRightOf="@+id/avatarView" + android:paddingTop="@dimen/margin_small" + android:textColor="@color/briar_text_secondary" + android:textSize="@dimen/text_size_small" + tools:text="Created by Santa Claus"/> + + <TextView + android:id="@+id/messageCountView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/creatorView" + android:layout_marginBottom="@dimen/margin_small" + android:layout_toEndOf="@+id/avatarView" + android:layout_toRightOf="@+id/avatarView" + android:paddingTop="@dimen/margin_small" + android:textColor="@color/briar_text_secondary" + android:textSize="@dimen/text_size_small" + tools:text="1337 messages" + tools:visibility="visible"/> + + <TextView + android:id="@+id/dateView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_alignParentRight="true" + android:layout_below="@+id/creatorView" + android:layout_marginEnd="@dimen/listitem_horizontal_margin" + android:layout_marginRight="@dimen/listitem_horizontal_margin" + android:paddingTop="@dimen/margin_small" + android:textColor="@color/briar_text_secondary" + android:textSize="@dimen/text_size_small" + tools:text="3 weeks ago, 12:00" + tools:visibility="visible"/> + + <TextView + android:id="@+id/statusView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/messageCountView" + android:layout_toEndOf="@+id/avatarView" + android:layout_toRightOf="@+id/avatarView" + android:paddingTop="@dimen/margin_small" + android:textColor="@color/briar_text_tertiary" + tools:text="@string/groups_group_is_empty"/> + + <Button + android:id="@+id/removeButton" + style="@style/BriarButtonFlat.Negative" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignBottom="@+id/divider" + android:layout_alignParentRight="true" + android:layout_toRightOf="@+id/statusView" + android:text="@string/groups_remove" + tools:visibility="gone"/> + + <View + android:id="@+id/divider" + style="@style/Divider.ForumList" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_below="@+id/statusView" + android:layout_marginTop="@dimen/listitem_horizontal_margin"/> + +</RelativeLayout> + diff --git a/briar-android/res/layout/navigation_header.xml b/briar-android/res/layout/navigation_header.xml index cc7f80ca83..ff120e9dd0 100644 --- a/briar-android/res/layout/navigation_header.xml +++ b/briar-android/res/layout/navigation_header.xml @@ -1,21 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> -<FrameLayout +<ImageView 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" - tools:showIn="@layout/navigation_menu"> - - <ImageView - android:layout_width="100dp" - android:layout_height="100dp" - android:layout_gravity="top|center_horizontal" - android:layout_margin="@dimen/margin_medium" - android:contentDescription="@string/app_name" - android:src="@drawable/briar_logo_large"/> - - <View - style="@style/Divider.Horizontal" - android:layout_gravity="bottom"/> - -</FrameLayout> \ No newline at end of file + android:layout_height="100dp" + android:contentDescription="@string/app_name" + android:scaleType="fitStart" + android:src="@drawable/navigation_drawer_header" + tools:showIn="@layout/navigation_menu"/> diff --git a/briar-android/res/layout/text_avatar_view.xml b/briar-android/res/layout/text_avatar_view.xml index 211d0f000e..de7f3b868f 100644 --- a/briar-android/res/layout/text_avatar_view.xml +++ b/briar-android/res/layout/text_avatar_view.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:showIn="@layout/list_item_forum"> @@ -11,16 +10,15 @@ android:layout_width="@dimen/avatar_forum_size" android:layout_height="@dimen/avatar_forum_size" android:layout_gravity="bottom|left" - android:src="@android:color/transparent" - app:civ_fill_color="@color/briar_button_positive"/> + android:src="@color/briar_button_positive"/> <android.support.v7.widget.AppCompatTextView android:id="@+id/textAvatarView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:layout_marginRight="@dimen/listitem_picture_frame_offset" - android:layout_marginTop="@dimen/listitem_picture_frame_offset" + android:layout_marginRight="@dimen/listitem_picture_frame_offset_horizontal" + android:layout_marginTop="@dimen/listitem_picture_frame_offset_vertical" android:maxLength="1" android:shadowColor="@color/forum_avatar_shadow" android:shadowDx="0" diff --git a/briar-android/res/menu/groups_list_actions.xml b/briar-android/res/menu/groups_list_actions.xml new file mode 100644 index 0000000000..511224ff16 --- /dev/null +++ b/briar-android/res/menu/groups_list_actions.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <item + android:id="@+id/action_add_group" + android:icon="@drawable/ic_add_white" + android:title="@string/groups_add_group_title" + app:showAsAction="ifRoom"/> + +</menu> \ No newline at end of file diff --git a/briar-android/res/menu/navigation_drawer.xml b/briar-android/res/menu/navigation_drawer.xml index dfeb05e62f..9c98a51871 100644 --- a/briar-android/res/menu/navigation_drawer.xml +++ b/briar-android/res/menu/navigation_drawer.xml @@ -5,8 +5,12 @@ <group android:checkableBehavior="single"> <item android:id="@+id/nav_btn_contacts" - android:icon="@drawable/ic_contacts_black_24dp" + android:icon="@drawable/ic_contacts" android:title="@string/contact_list_button"/> + <item + android:id="@+id/nav_btn_groups" + android:icon="@drawable/ic_group" + android:title="@string/groups_button"/> <item android:id="@+id/nav_btn_forums" android:icon="@drawable/ic_forums_black_24dp" @@ -15,6 +19,9 @@ android:id="@+id/nav_btn_blogs" android:icon="@drawable/blogs" android:title="@string/blogs_button"/> + </group> + + <group android:checkableBehavior="single"> <item android:id="@+id/nav_btn_settings" android:icon="@drawable/ic_settings_black_24dp" diff --git a/briar-android/res/values/dimens.xml b/briar-android/res/values/dimens.xml index 5239b500db..cc465f1ea8 100644 --- a/briar-android/res/values/dimens.xml +++ b/briar-android/res/values/dimens.xml @@ -24,8 +24,9 @@ <dimen name="listitem_height_one_line_avatar">56dp</dimen> <dimen name="listitem_picture_size">48dp</dimen> <dimen name="listitem_picture_size_small">23dp</dimen> - <dimen name="listitem_picture_frame_size">53dp</dimen> - <dimen name="listitem_picture_frame_offset">2dp</dimen> + <dimen name="listitem_picture_frame_size">51dp</dimen> + <dimen name="listitem_picture_frame_offset_horizontal">1dp</dimen> + <dimen name="listitem_picture_frame_offset_vertical">2dp</dimen> <dimen name="listitem_selectable_picture_size">40dp</dimen> <dimen name="avatar_forum_size">48dp</dimen> <dimen name="avatar_border_width">2dp</dimen> diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml index 48e3e1f778..883dceee68 100644 --- a/briar-android/res/values/strings.xml +++ b/briar-android/res/values/strings.xml @@ -34,6 +34,7 @@ <string name="nav_drawer_open_description">Open the navigation drawer</string> <string name="nav_drawer_close_description">Close the navigation drawer</string> <string name="contact_list_button">Contacts</string> + <string name="groups_button">Private Groups</string> <string name="forums_button">Forums</string> <string name="blogs_button">Blogs</string> <string name="settings_button">Settings</string> @@ -143,6 +144,18 @@ <item quantity="other">%d new contacts added.</item> </plurals> + <!-- Private Groups --> + <string name="groups_list_empty">You are not participating in any groups.\n\nTap the + icon at the top to create a group yourself or ask your contacts to get invited into one of their groups.</string> + <string name="groups_created_by">Created by %s</string> + <plurals name="messages"> + <item quantity="one">%d message</item> + <item quantity="other">%d messages</item> + </plurals> + <string name="groups_group_is_empty">This group is empty</string> + <string name="groups_group_is_dissolved">This group is dissolved</string> + <string name="groups_remove">Remove</string> + <string name="groups_add_group_title">Add Private Group</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> diff --git a/briar-android/res/values/styles.xml b/briar-android/res/values/styles.xml index 76c9c0a1f4..2056a57c79 100644 --- a/briar-android/res/values/styles.xml +++ b/briar-android/res/values/styles.xml @@ -93,9 +93,6 @@ <style name="BriarAvatar"> <item name="civ_border_width">@dimen/avatar_border_width</item> <item name="civ_border_color">@color/briar_primary</item> - - <!-- Remove when we are using 'de.hdodenhof:circleimageview:2.1.0' --> - <item name="civ_border_overlay">true</item> </style> <style name="NavMenuButton" parent="Widget.AppCompat.Button.Borderless.Colored"> diff --git a/briar-android/src/org/briarproject/android/ActivityComponent.java b/briar-android/src/org/briarproject/android/ActivityComponent.java index 990aea7a2e..d7cab786be 100644 --- a/briar-android/src/org/briarproject/android/ActivityComponent.java +++ b/briar-android/src/org/briarproject/android/ActivityComponent.java @@ -28,6 +28,7 @@ import org.briarproject.android.keyagreement.KeyAgreementActivity; import org.briarproject.android.keyagreement.ShowQrCodeFragment; import org.briarproject.android.panic.PanicPreferencesActivity; import org.briarproject.android.panic.PanicResponderActivity; +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; @@ -114,6 +115,7 @@ public interface ActivityComponent { // Fragments void inject(ContactListFragment fragment); + void inject(GroupListFragment fragment); void inject(ForumListFragment fragment); void inject(FeedFragment fragment); void inject(IntroFragment fragment); diff --git a/briar-android/src/org/briarproject/android/ActivityModule.java b/briar-android/src/org/briarproject/android/ActivityModule.java index 21a7d35ef0..f415cc327f 100644 --- a/briar-android/src/org/briarproject/android/ActivityModule.java +++ b/briar-android/src/org/briarproject/android/ActivityModule.java @@ -22,6 +22,8 @@ import org.briarproject.android.controller.SetupController; import org.briarproject.android.controller.SetupControllerImpl; import org.briarproject.android.forum.ForumController; import org.briarproject.android.forum.ForumControllerImpl; +import org.briarproject.android.privategroup.list.GroupListController; +import org.briarproject.android.privategroup.list.GroupListControllerImpl; import dagger.Module; import dagger.Provides; @@ -90,6 +92,13 @@ public class ActivityModule { return dbController; } + @ActivityScope + @Provides + protected GroupListController provideGroupListController( + GroupListControllerImpl groupListController) { + return groupListController; + } + @ActivityScope @Provides protected ForumController provideForumController( diff --git a/briar-android/src/org/briarproject/android/AndroidComponent.java b/briar-android/src/org/briarproject/android/AndroidComponent.java index ce051f5349..9b7e6edbaf 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.MessagingManager; 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.settings.SettingsManager; import org.briarproject.plugins.AndroidPluginsModule; import org.briarproject.system.AndroidSystemModule; @@ -93,6 +94,8 @@ public interface AndroidComponent extends CoreEagerSingletons { PrivateMessageFactory privateMessageFactory(); + PrivateGroupManager privateGroupManager(); + ForumManager forumManager(); ForumSharingManager forumSharingManager(); diff --git a/briar-android/src/org/briarproject/android/BriarFragmentActivity.java b/briar-android/src/org/briarproject/android/BriarFragmentActivity.java index 946ebbd356..7044355301 100644 --- a/briar-android/src/org/briarproject/android/BriarFragmentActivity.java +++ b/briar-android/src/org/briarproject/android/BriarFragmentActivity.java @@ -10,6 +10,7 @@ import org.briarproject.android.blogs.FeedFragment; import org.briarproject.android.contact.ContactListFragment; import org.briarproject.android.forum.ForumListFragment; import org.briarproject.android.fragment.BaseFragment; +import org.briarproject.android.privategroup.list.GroupListFragment; import static android.support.v4.app.FragmentManager.POP_BACK_STACK_INCLUSIVE; @@ -26,6 +27,8 @@ public abstract class BriarFragmentActivity extends BriarActivity { if (fragmentTag.equals(ContactListFragment.TAG)) { actionBar.setTitle(R.string.contact_list_button); + } else if (fragmentTag.equals(GroupListFragment.TAG)) { + actionBar.setTitle(R.string.groups_button); } else if (fragmentTag.equals(ForumListFragment.TAG)) { actionBar.setTitle(R.string.forums_button); } else if (fragmentTag.equals(FeedFragment.TAG)) { diff --git a/briar-android/src/org/briarproject/android/NavDrawerActivity.java b/briar-android/src/org/briarproject/android/NavDrawerActivity.java index 62af704f57..751700559e 100644 --- a/briar-android/src/org/briarproject/android/NavDrawerActivity.java +++ b/briar-android/src/org/briarproject/android/NavDrawerActivity.java @@ -28,6 +28,7 @@ import org.briarproject.android.controller.TransportStateListener; import org.briarproject.android.controller.handler.UiResultHandler; import org.briarproject.android.forum.ForumListFragment; import org.briarproject.android.fragment.BaseFragment.BaseFragmentListener; +import org.briarproject.android.privategroup.list.GroupListFragment; import org.briarproject.api.TransportId; import org.briarproject.api.identity.LocalAuthor; @@ -180,6 +181,9 @@ public class NavDrawerActivity extends BriarFragmentActivity implements case R.id.nav_btn_contacts: startFragment(ContactListFragment.newInstance()); break; + case R.id.nav_btn_groups: + startFragment(GroupListFragment.newInstance()); + break; case R.id.nav_btn_forums: startFragment(ForumListFragment.newInstance()); break; diff --git a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java index c4536bd9cb..891bfcfbc2 100644 --- a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java +++ b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java @@ -109,7 +109,7 @@ public class ContactListFragment extends BaseFragment implements EventListener { setHasOptionsMenu(true); View contentView = - inflater.inflate(R.layout.fragment_contact_list, container, + inflater.inflate(R.layout.list, container, false); BaseContactListAdapter.OnItemClickListener onItemClickListener = @@ -141,7 +141,7 @@ public class ContactListFragment extends BaseFragment implements EventListener { }; adapter = new ContactListAdapter(getContext(), onItemClickListener); - list = (BriarRecyclerView) contentView.findViewById(R.id.contactList); + list = (BriarRecyclerView) contentView.findViewById(R.id.list); list.setLayoutManager(new LinearLayoutManager(getContext())); list.setAdapter(adapter); list.setEmptyText(getString(R.string.no_contacts)); diff --git a/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java b/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java index 6a01c3020a..b26d3a8dc0 100644 --- a/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java +++ b/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java @@ -77,10 +77,7 @@ public class ContactChooserFragment extends BaseFragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View contentView = - inflater.inflate(R.layout.introduction_contact_chooser, - container, false); - + View contentView = inflater.inflate(R.layout.list, container, false); if (Build.VERSION.SDK_INT >= 21) { setExitTransition(new Fade()); @@ -105,7 +102,7 @@ public class ContactChooserFragment extends BaseFragment { }; adapter = new ContactChooserAdapter(getActivity(), onItemClickListener); - list = (BriarRecyclerView) contentView.findViewById(R.id.contactList); + list = (BriarRecyclerView) contentView.findViewById(R.id.list); list.setLayoutManager(new LinearLayoutManager(getActivity())); list.setAdapter(adapter); list.setEmptyText(getString(R.string.no_contacts)); diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupItem.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupItem.java new file mode 100644 index 0000000000..89b208b00e --- /dev/null +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupItem.java @@ -0,0 +1,78 @@ +package org.briarproject.android.privategroup.list; + +import org.briarproject.api.identity.Author; +import org.briarproject.api.privategroup.GroupMessageHeader; +import org.briarproject.api.privategroup.PrivateGroup; +import org.briarproject.api.sync.GroupId; +import org.jetbrains.annotations.NotNull; + +// This class is not thread-safe +class GroupItem { + + private final PrivateGroup privateGroup; + private int messageCount; + private long lastUpdate; + private int unreadCount; + private boolean dissolved; + + GroupItem(@NotNull PrivateGroup privateGroup, int messageCount, + long lastUpdate, int unreadCount, boolean dissolved) { + + this.privateGroup = privateGroup; + this.messageCount = messageCount; + this.lastUpdate = lastUpdate; + this.unreadCount = unreadCount; + this.dissolved = dissolved; + } + + void addMessageHeader(GroupMessageHeader header) { + messageCount++; + if (header.getTimestamp() > lastUpdate) { + lastUpdate = header.getTimestamp(); + } + if (!header.isRead()) { + unreadCount++; + } + } + + @NotNull + PrivateGroup getPrivateGroup() { + return privateGroup; + } + + @NotNull + GroupId getId() { + return privateGroup.getId(); + } + + @NotNull + Author getCreator() { + return privateGroup.getAuthor(); + } + + @NotNull + String getName() { + return privateGroup.getName(); + } + + boolean isEmpty() { + return messageCount == 0; + } + + int getMessageCount() { + return messageCount; + } + + long getLastUpdate() { + return lastUpdate; + } + + int getUnreadCount() { + return unreadCount; + } + + boolean isDissolved() { + return dissolved; + } + +} diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupListAdapter.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListAdapter.java new file mode 100644 index 0000000000..20210a486c --- /dev/null +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListAdapter.java @@ -0,0 +1,79 @@ +package org.briarproject.android.privategroup.list; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.briarproject.R; +import org.briarproject.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener; +import org.briarproject.android.util.BriarAdapter; +import org.briarproject.api.sync.GroupId; +import org.jetbrains.annotations.NotNull; + +import static android.support.v7.util.SortedList.INVALID_POSITION; + +class GroupListAdapter extends BriarAdapter<GroupItem, GroupViewHolder> { + + private final OnGroupRemoveClickListener listener; + + GroupListAdapter(Context ctx, OnGroupRemoveClickListener listener) { + super(ctx, GroupItem.class); + this.listener = listener; + } + + @Override + public GroupViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(ctx).inflate( + R.layout.list_item_group, parent, false); + return new GroupViewHolder(v); + } + + @Override + public void onBindViewHolder(GroupViewHolder ui, int position) { + ui.bindView(ctx, getItemAt(position), listener); + } + + @Override + public int compare(GroupItem a, GroupItem b) { + if (a == b) return 0; + // The group with the latest message comes first + long aTime = a.getLastUpdate(), bTime = b.getLastUpdate(); + if (aTime > bTime) return -1; + if (aTime < bTime) return 1; + // Break ties by group name + String aName = a.getName(); + String bName = b.getName(); + return String.CASE_INSENSITIVE_ORDER.compare(aName, bName); + } + + @Override + public boolean areContentsTheSame(GroupItem a, GroupItem b) { + return a.getMessageCount() == b.getMessageCount() && + a.getLastUpdate() == b.getLastUpdate() && + a.getUnreadCount() == b.getUnreadCount() && + a.isDissolved() == b.isDissolved(); + } + + @Override + public boolean areItemsTheSame(GroupItem a, GroupItem b) { + return a.getId().equals(b.getId()); + } + + int findItemPosition(@NotNull GroupId g) { + for (int i = 0; i < items.size(); i++) { + GroupItem item = items.get(i); + if (item.getId().equals(g)) { + return i; + } + } + return INVALID_POSITION; + } + + void removeItem(GroupId groupId) { + int pos = findItemPosition(groupId); + if (pos != INVALID_POSITION) items.removeItemAt(pos); + } + +} diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupListController.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListController.java new file mode 100644 index 0000000000..27e8c90d4a --- /dev/null +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListController.java @@ -0,0 +1,44 @@ +package org.briarproject.android.privategroup.list; + +import android.support.annotation.UiThread; + +import org.briarproject.android.DestroyableContext; +import org.briarproject.android.controller.DbController; +import org.briarproject.android.controller.handler.ResultExceptionHandler; +import org.briarproject.api.db.DbException; +import org.briarproject.api.privategroup.GroupMessageHeader; +import org.briarproject.api.sync.GroupId; + +import java.util.Collection; + +public interface GroupListController extends DbController { + + /** + * The listener must be set right after the controller was injected + */ + void setGroupListListener(GroupListListener listener); + + @UiThread + void onStart(); + + @UiThread + void onStop(); + + void loadGroups( + ResultExceptionHandler<Collection<GroupItem>, DbException> result); + + void removeGroup(GroupId g, + ResultExceptionHandler<Void, DbException> result); + + interface GroupListListener extends DestroyableContext { + @UiThread + void onGroupMessageAdded(GroupMessageHeader header); + + @UiThread + void onGroupAdded(GroupId groupId); + + @UiThread + void onGroupRemoved(GroupId groupId); + } + +} diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java new file mode 100644 index 0000000000..6c30011dc6 --- /dev/null +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java @@ -0,0 +1,156 @@ +package org.briarproject.android.privategroup.list; + +import android.support.annotation.CallSuper; + +import org.briarproject.android.api.AndroidNotificationManager; +import org.briarproject.android.controller.DbControllerImpl; +import org.briarproject.android.controller.handler.ResultExceptionHandler; +import org.briarproject.api.clients.MessageTracker.GroupCount; +import org.briarproject.api.db.DbException; +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.GroupMessageAddedEvent; +import org.briarproject.api.event.GroupRemovedEvent; +import org.briarproject.api.identity.IdentityManager; +import org.briarproject.api.privategroup.PrivateGroup; +import org.briarproject.api.privategroup.PrivateGroupManager; +import org.briarproject.api.sync.ClientId; +import org.briarproject.api.sync.GroupId; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static java.util.logging.Level.WARNING; + +public class GroupListControllerImpl extends DbControllerImpl + implements GroupListController, EventListener { + + private static final Logger LOG = + Logger.getLogger(GroupListControllerImpl.class.getName()); + + @Inject + PrivateGroupManager groupManager; + @Inject + EventBus eventBus; + @Inject + AndroidNotificationManager notificationManager; + @Inject + IdentityManager identityManager; + + protected volatile GroupListListener listener; + + @Inject + GroupListControllerImpl() { + + } + + @Override + public void setGroupListListener(GroupListListener listener) { + this.listener = listener; + } + + @CallSuper + public void onStart() { + if (listener == null) + throw new IllegalStateException( + "GroupListListener needs to be attached"); + eventBus.addListener(this); + } + + @CallSuper + public void onStop() { + eventBus.removeListener(this); + } + + @Override + @CallSuper + public void eventOccurred(Event e) { + if (e instanceof GroupMessageAddedEvent) { + final GroupMessageAddedEvent m = (GroupMessageAddedEvent) e; + LOG.info("New group message added"); + listener.runOnUiThreadUnlessDestroyed(new Runnable() { + @Override + public void run() { + listener.onGroupMessageAdded(m.getHeader()); + } + }); + } else if (e instanceof GroupAddedEvent) { + final GroupAddedEvent gae = (GroupAddedEvent) e; + ClientId id = gae.getGroup().getClientId(); + if (id.equals(groupManager.getClientId())) { + LOG.info("Private group added"); + listener.runOnUiThreadUnlessDestroyed(new Runnable() { + @Override + public void run() { + listener.onGroupAdded(gae.getGroup().getId()); + } + }); + } + } else if (e instanceof GroupRemovedEvent) { + final GroupRemovedEvent gre = (GroupRemovedEvent) e; + ClientId id = gre.getGroup().getClientId(); + if (id.equals(groupManager.getClientId())) { + LOG.info("Private group removed"); + listener.runOnUiThreadUnlessDestroyed(new Runnable() { + @Override + public void run() { + listener.onGroupRemoved(gre.getGroup().getId()); + } + }); + } + } + } + + @Override + public void loadGroups( + final ResultExceptionHandler<Collection<GroupItem>, DbException> handler) { + runOnDbThread(new Runnable() { + @Override + public void run() { + LOG.info("Loading groups from database..."); + try { + Collection<PrivateGroup> groups = + groupManager.getPrivateGroups(); + List<GroupItem> items = new ArrayList<>(groups.size()); + for (PrivateGroup g : groups) { + GroupCount c = groupManager.getGroupCount(g.getId()); + boolean dissolved = groupManager.isDissolved(g.getId()); + items.add(new GroupItem(g, c.getMsgCount(), + c.getLatestMsgTime(), c.getUnreadCount(), + dissolved)); + } + handler.onResult(items); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + handler.onException(e); + } + } + }); + } + + @Override + public void removeGroup(final GroupId g, + final ResultExceptionHandler<Void, DbException> handler) { + runOnDbThread(new Runnable() { + @Override + public void run() { + LOG.info("Removing group from database..."); + try { + groupManager.removePrivateGroup(g); + } 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 new file mode 100644 index 0000000000..bf773c28c6 --- /dev/null +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java @@ -0,0 +1,171 @@ +package org.briarproject.android.privategroup.list; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +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.ViewGroup; + +import org.briarproject.R; +import org.briarproject.android.ActivityComponent; +import org.briarproject.android.controller.handler.UiResultExceptionHandler; +import org.briarproject.android.fragment.BaseFragment; +import org.briarproject.android.invitation.AddContactActivity; +import org.briarproject.android.privategroup.list.GroupListController.GroupListListener; +import org.briarproject.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener; +import org.briarproject.android.view.BriarRecyclerView; +import org.briarproject.api.db.DbException; +import org.briarproject.api.privategroup.GroupMessageHeader; +import org.briarproject.api.sync.GroupId; + +import java.util.Collection; + +import javax.inject.Inject; + +public class GroupListFragment extends BaseFragment implements + GroupListListener, OnGroupRemoveClickListener { + + public final static String TAG = GroupListFragment.class.getName(); + + public static GroupListFragment newInstance() { + return new GroupListFragment(); + } + + @Inject + GroupListController controller; + + private BriarRecyclerView list; + private GroupListAdapter adapter; + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + setHasOptionsMenu(true); + + View v = inflater.inflate(R.layout.list, container, false); + + adapter = new GroupListAdapter(getContext(), this); + list = (BriarRecyclerView) v.findViewById(R.id.list); + list.setEmptyText(R.string.groups_list_empty); + list.setLayoutManager(new LinearLayoutManager(getContext())); + list.setAdapter(adapter); + + return v; + } + + @Override + public void injectFragment(ActivityComponent component) { + component.inject(this); + controller.setGroupListListener(this); + } + + @Override + public void onStart() { + super.onStart(); + controller.onStart(); + list.startPeriodicUpdate(); + loadGroups(); + } + + @Override + public void onStop() { + super.onStop(); + controller.onStop(); + list.stopPeriodicUpdate(); + adapter.clear(); + list.showProgressBar(); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.groups_list_actions, menu); + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case R.id.action_add_group: + // TODO + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @UiThread + @Override + public void onGroupRemoveClick(GroupItem item) { + controller.removeGroup(item.getId(), + new UiResultExceptionHandler<Void, DbException>(listener) { + @Override + public void onResultUi(Void result) { + // handled by GroupRemovedEvent and onGroupRemoved() + } + + @Override + public void onExceptionUi(DbException exception) { + // TODO handle error + finish(); + } + }); + } + + @UiThread + @Override + public void onGroupMessageAdded(GroupMessageHeader header) { + int position = adapter.findItemPosition(header.getGroupId()); + GroupItem item = adapter.getItemAt(position); + if (item != null) { + item.addMessageHeader(header); + adapter.updateItemAt(position, item); + } + } + + @UiThread + @Override + public void onGroupAdded(GroupId groupId) { + loadGroups(); + } + + @UiThread + @Override + public void onGroupRemoved(GroupId groupId) { + adapter.removeItem(groupId); + } + + @Override + public String getUniqueTag() { + return TAG; + } + + private void loadGroups() { + controller.loadGroups( + new UiResultExceptionHandler<Collection<GroupItem>, DbException>( + listener) { + @Override + public void onResultUi(Collection<GroupItem> result) { + if (result.isEmpty()) { + list.showData(); + } else { + adapter.addAll(result); + } + } + + @Override + public void onExceptionUi(DbException exception) { + // TODO handle this error + finish(); + } + }); + } + +} diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupViewHolder.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupViewHolder.java new file mode 100644 index 0000000000..12b956df4f --- /dev/null +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupViewHolder.java @@ -0,0 +1,135 @@ +package org.briarproject.android.privategroup.list; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import org.briarproject.R; +import org.briarproject.android.util.AndroidUtils; +import org.briarproject.android.view.TextAvatarView; +import org.jetbrains.annotations.NotNull; + +import static android.support.v4.content.ContextCompat.getColor; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + +class GroupViewHolder extends RecyclerView.ViewHolder { + + private final static float ALPHA = 0.42f; + + private final ViewGroup layout; + private final TextAvatarView avatar; + private final TextView name; + private final TextView creator; + private final TextView postCount; + private final TextView date; + private final TextView status; + private final Button remove; + + GroupViewHolder(View v) { + super(v); + + layout = (ViewGroup) v; + avatar = (TextAvatarView) v.findViewById(R.id.avatarView); + name = (TextView) v.findViewById(R.id.nameView); + creator = (TextView) v.findViewById(R.id.creatorView); + postCount = (TextView) v.findViewById(R.id.messageCountView); + date = (TextView) v.findViewById(R.id.dateView); + status = (TextView) v.findViewById(R.id.statusView); + remove = (Button) v.findViewById(R.id.removeButton); + } + + void bindView(Context ctx, @Nullable final GroupItem group, + @NotNull final OnGroupRemoveClickListener listener) { + if (group == null) return; + + // Avatar + avatar.setText(group.getName().substring(0, 1)); + avatar.setBackgroundBytes(group.getId().getBytes()); + avatar.setUnreadCount(group.getUnreadCount()); + + // Group Name + name.setText(group.getName()); + + // Creator + creator.setText(ctx.getString(R.string.groups_created_by, + group.getCreator().getName())); + + if (!group.isDissolved()) { + // full visibility + avatar.setAlpha(1); + name.setAlpha(1); + creator.setAlpha(1); + + // Date and Status + if (group.isEmpty()) { + postCount.setVisibility(GONE); + date.setVisibility(GONE); + avatar.setProblem(true); + status + .setText(ctx.getString(R.string.groups_group_is_empty)); + status.setVisibility(VISIBLE); + } else { + // Message Count + int messageCount = group.getMessageCount(); + postCount.setVisibility(VISIBLE); + postCount.setText(ctx.getResources() + .getQuantityString(R.plurals.messages, messageCount, + messageCount)); + postCount.setTextColor( + getColor(ctx, R.color.briar_text_secondary)); + + long lastUpdate = group.getLastUpdate(); + date.setText(AndroidUtils.formatDate(ctx, lastUpdate)); + date.setVisibility(VISIBLE); + avatar.setProblem(false); + status.setVisibility(GONE); + } + remove.setVisibility(GONE); + } else { + // grey out + avatar.setAlpha(ALPHA); + name.setAlpha(ALPHA); + creator.setAlpha(ALPHA); + + postCount.setVisibility(GONE); + date.setVisibility(GONE); + status + .setText(ctx.getString(R.string.groups_group_is_dissolved)); + status.setVisibility(VISIBLE); + remove.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + listener.onGroupRemoveClick(group); + } + }); + remove.setVisibility(VISIBLE); + } + + // Open Group on Click + layout.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { +/* + Intent i = new Intent(ctx, GroupActivity.class); + GroupId id = item.getId(); + i.putExtra(GROUP_ID, id.getBytes()); + ActivityOptionsCompat options = ActivityOptionsCompat + .makeCustomAnimation(ctx, android.R.anim.fade_in, + android.R.anim.fade_out); + ActivityCompat.startActivity(ctx, i, options.toBundle()); +*/ + } + }); + } + + interface OnGroupRemoveClickListener { + void onGroupRemoveClick(GroupItem item); + } + +} diff --git a/briar-android/src/org/briarproject/android/sharing/ContactSelectorFragment.java b/briar-android/src/org/briarproject/android/sharing/ContactSelectorFragment.java index 286e0616dc..8e270d6407 100644 --- a/briar-android/src/org/briarproject/android/sharing/ContactSelectorFragment.java +++ b/briar-android/src/org/briarproject/android/sharing/ContactSelectorFragment.java @@ -99,8 +99,7 @@ public class ContactSelectorFragment extends BaseFragment implements public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View contentView = inflater.inflate( - R.layout.introduction_contact_chooser, container, false); + View contentView = inflater.inflate(R.layout.list, container, false); if (Build.VERSION.SDK_INT >= 21) { setExitTransition(new Fade()); @@ -108,7 +107,7 @@ public class ContactSelectorFragment extends BaseFragment implements adapter = new ContactSelectorAdapter(getActivity(), this); - list = (BriarRecyclerView) contentView.findViewById(R.id.contactList); + list = (BriarRecyclerView) contentView.findViewById(R.id.list); list.setLayoutManager(new LinearLayoutManager(getActivity())); list.setAdapter(adapter); list.setEmptyText(getString(R.string.no_contacts_selector)); diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java b/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java index 941dbb48f1..a6b33a0d8c 100644 --- a/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java +++ b/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java @@ -38,12 +38,12 @@ abstract class InvitationsActivity extends BriarActivity public void onCreate(Bundle state) { super.onCreate(state); - setContentView(R.layout.activity_invitations); + setContentView(R.layout.list); adapter = getAdapter(this, this); - list = (BriarRecyclerView) findViewById(R.id.invitationsView); + list = (BriarRecyclerView) findViewById(R.id.list); if (list != null) { list.setLayoutManager(new LinearLayoutManager(this)); list.setAdapter(adapter); diff --git a/briar-android/src/org/briarproject/android/view/TextAvatarView.java b/briar-android/src/org/briarproject/android/view/TextAvatarView.java index 8b06ecb4c1..2f5a2cfb2c 100644 --- a/briar-android/src/org/briarproject/android/view/TextAvatarView.java +++ b/briar-android/src/org/briarproject/android/view/TextAvatarView.java @@ -2,6 +2,7 @@ package org.briarproject.android.view; import android.content.Context; import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.support.annotation.Nullable; import android.support.annotation.UiThread; @@ -78,7 +79,7 @@ public class TextAvatarView extends FrameLayout { int b = getByte(bytes, 2) * 3 / 4 + 96; int color = Color.rgb(r, g, b); - background.setFillColor(color); + background.setImageDrawable(new ColorDrawable(color)); } private byte getByte(byte[] bytes, int index) { diff --git a/briar-api/src/org/briarproject/api/clients/BaseGroup.java b/briar-api/src/org/briarproject/api/clients/BaseGroup.java index 7a6d1754e8..fe8a282f5b 100644 --- a/briar-api/src/org/briarproject/api/clients/BaseGroup.java +++ b/briar-api/src/org/briarproject/api/clients/BaseGroup.java @@ -16,14 +16,17 @@ public abstract class BaseGroup { this.salt = salt; } + @NotNull public GroupId getId() { return group.getId(); } + @NotNull public Group getGroup() { return group; } + @NotNull public String getName() { return name; } diff --git a/briar-api/src/org/briarproject/api/event/GroupMessageAddedEvent.java b/briar-api/src/org/briarproject/api/event/GroupMessageAddedEvent.java new file mode 100644 index 0000000000..d0c05c105a --- /dev/null +++ b/briar-api/src/org/briarproject/api/event/GroupMessageAddedEvent.java @@ -0,0 +1,36 @@ +package org.briarproject.api.event; + +import org.briarproject.api.privategroup.GroupMessageHeader; +import org.briarproject.api.sync.GroupId; + +/** + * An event that is broadcast when a private group message was added + * to the database. + */ +public class GroupMessageAddedEvent extends Event { + + private final GroupId groupId; + private final GroupMessageHeader header; + private final boolean local; + + public GroupMessageAddedEvent(GroupId groupId, GroupMessageHeader header, + boolean local) { + + this.groupId = groupId; + this.header = header; + this.local = local; + } + + public GroupId getGroupId() { + return groupId; + } + + public GroupMessageHeader getHeader() { + return header; + } + + public boolean isLocal() { + return local; + } + +} diff --git a/briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java b/briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java index 608e86ce0a..21043297bb 100644 --- a/briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java +++ b/briar-api/src/org/briarproject/api/privategroup/GroupMessageHeader.java @@ -2,13 +2,27 @@ package org.briarproject.api.privategroup; import org.briarproject.api.clients.PostHeader; import org.briarproject.api.identity.Author; +import org.briarproject.api.identity.Author.Status; +import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.MessageId; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class GroupMessageHeader extends PostHeader { - public GroupMessageHeader(MessageId id, MessageId parentId, long timestamp, - Author author, Author.Status authorStatus, boolean read) { + private final GroupId groupId; + + public GroupMessageHeader(@NotNull GroupId groupId, @NotNull MessageId id, + @Nullable MessageId parentId, long timestamp, + @NotNull Author author, @NotNull Status authorStatus, + boolean read) { super(id, parentId, timestamp, author, authorStatus, read); + this.groupId = groupId; + } + + @NotNull + public GroupId getGroupId() { + return groupId; } } diff --git a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java index ab02e86bc0..a7fbd0cb12 100644 --- a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java +++ b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java @@ -16,6 +16,9 @@ public interface PrivateGroupManager extends MessageTracker { @NotNull ClientId getClientId(); + /** Removes a dissolved private group. */ + void removePrivateGroup(GroupId g) throws DbException; + /** Stores (and sends) a local group message. */ void addLocalMessage(GroupMessage p) throws DbException; @@ -33,6 +36,9 @@ public interface PrivateGroupManager extends MessageTracker { @NotNull Collection<PrivateGroup> getPrivateGroups() throws DbException; + /** Returns true if the private group has been dissolved. */ + boolean isDissolved(GroupId g) throws DbException; + /** Returns the body of the group message with the given ID. */ @NotNull String getMessageBody(MessageId m) throws DbException; diff --git a/briar-core/src/org/briarproject/CoreEagerSingletons.java b/briar-core/src/org/briarproject/CoreEagerSingletons.java index 4f1787c2b4..b4b7402dd9 100644 --- a/briar-core/src/org/briarproject/CoreEagerSingletons.java +++ b/briar-core/src/org/briarproject/CoreEagerSingletons.java @@ -1,5 +1,7 @@ package org.briarproject; +import org.briarproject.api.privategroup.PrivateGroup; +import org.briarproject.api.privategroup.PrivateGroupManager; import org.briarproject.blogs.BlogsModule; import org.briarproject.contact.ContactModule; import org.briarproject.crypto.CryptoModule; @@ -11,6 +13,7 @@ import org.briarproject.introduction.IntroductionModule; import org.briarproject.lifecycle.LifecycleModule; import org.briarproject.messaging.MessagingModule; import org.briarproject.plugins.PluginsModule; +import org.briarproject.privategroup.PrivateGroupModule; import org.briarproject.properties.PropertiesModule; import org.briarproject.sharing.SharingModule; import org.briarproject.sync.SyncModule; @@ -39,6 +42,8 @@ public interface CoreEagerSingletons { void inject(PluginsModule.EagerSingletons init); + void inject(PrivateGroupModule.EagerSingletons init); + void inject(PropertiesModule.EagerSingletons init); void inject(SharingModule.EagerSingletons init); diff --git a/briar-core/src/org/briarproject/CoreModule.java b/briar-core/src/org/briarproject/CoreModule.java index dbfaeb256a..82754d6688 100644 --- a/briar-core/src/org/briarproject/CoreModule.java +++ b/briar-core/src/org/briarproject/CoreModule.java @@ -17,6 +17,7 @@ import org.briarproject.keyagreement.KeyAgreementModule; import org.briarproject.lifecycle.LifecycleModule; import org.briarproject.messaging.MessagingModule; import org.briarproject.plugins.PluginsModule; +import org.briarproject.privategroup.PrivateGroupModule; import org.briarproject.properties.PropertiesModule; import org.briarproject.reliability.ReliabilityModule; import org.briarproject.reporting.ReportingModule; @@ -46,6 +47,7 @@ import dagger.Module; LifecycleModule.class, MessagingModule.class, PluginsModule.class, + PrivateGroupModule.class, PropertiesModule.class, ReliabilityModule.class, ReportingModule.class, @@ -69,6 +71,7 @@ public class CoreModule { c.inject(new LifecycleModule.EagerSingletons()); c.inject(new MessagingModule.EagerSingletons()); c.inject(new PluginsModule.EagerSingletons()); + c.inject(new PrivateGroupModule.EagerSingletons()); c.inject(new PropertiesModule.EagerSingletons()); c.inject(new SharingModule.EagerSingletons()); c.inject(new SyncModule.EagerSingletons()); diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java index 93623476d9..3c9f5814bc 100644 --- a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java @@ -25,12 +25,15 @@ import org.jetbrains.annotations.NotNull; import java.util.Collection; import java.util.Collections; +import java.util.logging.Logger; import javax.inject.Inject; public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements PrivateGroupManager { + private static final Logger LOG = + Logger.getLogger(PrivateGroupManagerImpl.class.getName()); static final ClientId CLIENT_ID = new ClientId( StringUtils.fromHexString("5072697661746547726f75704d616e61" + "67657220627920546f727374656e2047")); @@ -55,6 +58,11 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements return CLIENT_ID; } + @Override + public void removePrivateGroup(GroupId g) throws DbException { + + } + @Override public void addLocalMessage(GroupMessage m) throws DbException { Transaction txn = db.startTransaction(false); @@ -91,6 +99,11 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements return Collections.emptyList(); } + @Override + public boolean isDissolved(GroupId g) throws DbException { + return false; + } + @NotNull @Override public String getMessageBody(MessageId m) throws DbException { -- GitLab