diff --git a/briar-android/build.gradle b/briar-android/build.gradle index 11ddb77b20a2f7fa5797258ef242a1b80b8fb308..113bb63dfbc0564fca20eeb344c8211e9e4095d4 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 0000000000000000000000000000000000000000..cd3e499303967f8488262729e32cd1254d0b242d --- /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 Binary files /dev/null and b/briar-android/res/drawable/navigation_drawer_header.png differ diff --git a/briar-android/res/layout/activity_invitations.xml b/briar-android/res/layout/activity_invitations.xml deleted file mode 100644 index 4d3d61cea8a12c6d62b84193ee0866489f6d54e3..0000000000000000000000000000000000000000 --- 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 de3bddebe14b129d135d400ace09f2d6081a9f4e..0000000000000000000000000000000000000000 --- 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 4a99ecbed9378374dfb47cf3e3d355d52e564569..0fbe7cede515e0b4c9e85998d44d9d2de013e8ce 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 c143b6a22ba0773ccb4fd3d4aa487d696d34a8e6..5c4df7bd173ffe09c3142510844775fa50777dcb 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 0000000000000000000000000000000000000000..afb0b642c1d63557b3eb94111b25cf1921bfcfaa --- /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 cc7f80ca838b5834b310a916456e1709bad7521d..ff120e9dd000903a1860a220a6c7d5e81cc128ca 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 211d0f000ee231c52f03fd0067b57bd04ea29424..de7f3b868f670a1d08e76a6da76d11305622dbcb 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 0000000000000000000000000000000000000000..511224ff1650225e103d86b24531ea559da54bcf --- /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 dfeb05e62f5b18361d3e8dc08ea2bda45879a83b..9c98a51871f1a27c85dc638f60d476e625d99df8 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 5239b500dbe9429b3e143fe6625f01c0ba99395f..cc465f1ea840716b5d315141c85ce229e4caed28 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 48e3e1f778980382099fad974b3c204369a698e9..883dceee683a5640a16a3c49887b1589278b7aa5 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 76c9c0a1f45ee25969ab480473235f6056cbef80..2056a57c793e076b34a1fd96bda1aee2d0e7f4f4 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 990aea7a2e13af338a3bf12fa09acbcca70ce50c..d7cab786be65373e4c5efec208e210b539fdabb5 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 21a7d35ef0312f13904c567ca24c88924182231f..f415cc327f814902f5d5e150962073575f067913 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 ce051f5349f136428e6921607bca39516f24abe4..9b7e6edbaf0b64aecdf3ab00eab40732aeb3d419 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 946ebbd3569b672e1df1a331f584bbeada7f0407..70443553014378783e39d1844d63d4edd19abe9c 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 62af704f57d51f06484c9b640b46b63b089e87ea..751700559ebc95d966a558eb0e4ad39458d51fea 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 c4536bd9cbaebc97418c69c6cbab1bcf99cec082..891bfcfbc254b38e4b3242b1e092ae8d9d39f211 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 6a01c3020af3c6c0d1defea18b7138995729659b..b26d3a8dc05c9dbdf8911d96d13d81d116437846 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 0000000000000000000000000000000000000000..89b208b00e2638488760fc8a2fd6259099e72933 --- /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 0000000000000000000000000000000000000000..20210a486c9a903cd63d1f686d92a390a0d89716 --- /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 0000000000000000000000000000000000000000..27e8c90d4a04104df35a4e26a4bff4b1c3773a97 --- /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 0000000000000000000000000000000000000000..6c30011dc60ac6a23e8c9a43f111a9d3dbea7dc6 --- /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 0000000000000000000000000000000000000000..bf773c28c654d0be47ad835c51de88d40b1e1947 --- /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 0000000000000000000000000000000000000000..12b956df4f688597947569e31fb25df1b5f0302a --- /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 286e0616dc6192934de34a3bf678bf808d236362..8e270d6407ca27f62a3b4f2d411efa49023230a5 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 941dbb48f16d102557c16a22ed722211e9dce4de..a6b33a0d8c601d32f06ddb3b895df849a7e6a556 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 8b06ecb4c1cd49ffc4acd83b72f20ff08123ebb8..2f5a2cfb2c6d486e8a19b40306837bafffc12327 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 7a6d1754e805edffe0671958c7bb46716c51e64a..fe8a282f5b7fb50770be89e170dd3bf889d6171d 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 0000000000000000000000000000000000000000..d0c05c105a315a1b2d3e9d1a0ef420f948de010c --- /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 608e86ce0aae58fa1f78eaa230e30bca8e177b3c..21043297bb338e51de4b76a73800dd970207280a 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 ab02e86bc04a6e500b470505a5741754d6464544..a7fbd0cb12a5421e04d3c1c37eacebb09bf43c19 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 4f1787c2b4317402540ec97ace9df11f83425a30..b4b7402dd9b7fb265ab9aa30c887f8f4089f3da4 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 dbfaeb256a9c4d7f3670afbccf8181e61066212e..82754d66880e509cd1d3dbdafd78a4a6579b8a4f 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 93623476d9b273c78f93a924db973dc8a56b47e7..3c9f5814bc580c50a3bb1822855000993d613e82 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 {