From 6d7e0aab07014922a59ecc33bae4a3673165d9d5 Mon Sep 17 00:00:00 2001 From: Torsten Grote <t@grobox.de> Date: Tue, 16 Aug 2016 18:11:09 -0300 Subject: [PATCH] Reblogging and Comment UI --- .../org/briarproject/BlogManagerTest.java | 18 +- briar-android/AndroidManifest.xml | 11 + briar-android/res/drawable/bubble_white.xml | 17 ++ ...on.xml => activity_fragment_container.xml} | 2 +- briar-android/res/layout/author_view.xml | 22 +- .../res/layout/fragment_reblog_dialog.xml | 46 ++++ .../res/layout/list_item_blog_comment.xml | 36 +++ .../res/layout/list_item_blog_post.xml | 92 +++++-- briar-android/res/menu/blogs_blog_actions.xml | 15 +- .../res/menu/blogs_my_blog_actions.xml | 12 - briar-android/res/values/attrs.xml | 9 + briar-android/res/values/dimens.xml | 4 + briar-android/res/values/strings.xml | 5 +- briar-android/res/values/styles.xml | 1 - .../android/ActivityComponent.java | 10 +- .../android/BriarFragmentActivity.java | 4 +- .../android/NavDrawerActivity.java | 6 +- .../android/blogs/BaseController.java | 39 +++ .../android/blogs/BaseControllerImpl.java | 250 ++++++++++++++++++ .../android/blogs/BlogActivity.java | 15 +- .../android/blogs/BlogCommentItem.java | 63 +++++ .../android/blogs/BlogController.java | 12 +- .../android/blogs/BlogControllerImpl.java | 156 ++--------- .../android/blogs/BlogFragment.java | 69 +++-- .../android/blogs/BlogListAdapter.java | 5 - .../android/blogs/BlogPostAdapter.java | 52 +--- .../android/blogs/BlogPostFragment.java | 10 +- .../android/blogs/BlogPostItem.java | 30 ++- .../android/blogs/BlogPostViewHolder.java | 142 ++++++++++ .../android/blogs/BlogsFragment.java | 118 --------- .../android/blogs/CreateBlogActivity.java | 4 - .../android/blogs/FeedController.java | 17 +- .../android/blogs/FeedControllerImpl.java | 99 ++----- .../android/blogs/FeedFragment.java | 81 +++--- .../android/blogs/MyBlogsFragment.java | 169 ------------ .../android/blogs/ReblogActivity.java | 73 +++++ .../android/blogs/ReblogFragment.java | 196 ++++++++++++++ .../android/fragment/BaseFragment.java | 4 + .../introduction/IntroductionActivity.java | 6 +- .../keyagreement/ShowQrCodeFragment.java | 4 +- .../briarproject/android/util/AuthorView.java | 81 +++++- .../android/util/TrustIndicatorView.java | 3 + .../briarproject/api/blogs/BlogManager.java | 2 +- .../briarproject/blogs/BlogManagerImpl.java | 9 +- 44 files changed, 1273 insertions(+), 746 deletions(-) create mode 100644 briar-android/res/drawable/bubble_white.xml rename briar-android/res/layout/{activity_introduction.xml => activity_fragment_container.xml} (82%) create mode 100644 briar-android/res/layout/fragment_reblog_dialog.xml create mode 100644 briar-android/res/layout/list_item_blog_comment.xml delete mode 100644 briar-android/res/menu/blogs_my_blog_actions.xml create mode 100644 briar-android/src/org/briarproject/android/blogs/BaseController.java create mode 100644 briar-android/src/org/briarproject/android/blogs/BaseControllerImpl.java create mode 100644 briar-android/src/org/briarproject/android/blogs/BlogCommentItem.java create mode 100644 briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java delete mode 100644 briar-android/src/org/briarproject/android/blogs/BlogsFragment.java delete mode 100644 briar-android/src/org/briarproject/android/blogs/MyBlogsFragment.java create mode 100644 briar-android/src/org/briarproject/android/blogs/ReblogActivity.java create mode 100644 briar-android/src/org/briarproject/android/blogs/ReblogFragment.java diff --git a/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java b/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java index e2893714da..3bfaae9a2a 100644 --- a/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java @@ -32,7 +32,6 @@ import org.briarproject.lifecycle.LifecycleModule; import org.briarproject.properties.PropertiesModule; import org.briarproject.sync.SyncModule; import org.briarproject.transport.TransportModule; -import org.briarproject.util.StringUtils; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -60,7 +59,6 @@ import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.api.sync.ValidationManager.State.INVALID; import static org.briarproject.api.sync.ValidationManager.State.PENDING; import static org.briarproject.api.sync.ValidationManager.State.VALID; -import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -193,8 +191,7 @@ public class BlogManagerTest { assertEquals(1, headers0.size()); // check that body is there - assertArrayEquals(StringUtils.toUtf8(body), - blogManager0.getPostBody(p.getMessage().getId())); + assertEquals(body, blogManager0.getPostBody(p.getMessage().getId())); // make sure that blog0 at author1 doesn't have the post yet Collection<BlogPostHeader> headers1 = @@ -211,8 +208,7 @@ public class BlogManagerTest { assertEquals(POST, headers1.iterator().next().getType()); // check that body is there - assertArrayEquals(StringUtils.toUtf8(body), - blogManager1.getPostBody(p.getMessage().getId())); + assertEquals(body, blogManager1.getPostBody(p.getMessage().getId())); stopLifecycles(); } @@ -334,8 +330,7 @@ public class BlogManagerTest { assertEquals(author0, h.getParent().getAuthor()); // ensure that body can be retrieved from wrapped post - assertArrayEquals(StringUtils.toUtf8(body), - blogManager0.getPostBody(h.getParentId())); + assertEquals(body, blogManager0.getPostBody(h.getParentId())); // 1 has only their own comment in their blog headers1 = blogManager1.getPostHeaders(blog1.getId()); @@ -375,6 +370,13 @@ public class BlogManagerTest { Collection<BlogPostHeader> headers1 = blogManager1.getPostHeaders(blog0.getId()); assertEquals(2, headers1.size()); + for (BlogPostHeader h : headers1) { + if (h.getType() == POST) { + assertEquals(body, blogManager1.getPostBody(h.getId())); + } else { + assertEquals(comment, ((BlogCommentHeader)h).getComment()); + } + } stopLifecycles(); } diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml index 0ce2865ecd..5da35cdf46 100644 --- a/briar-android/AndroidManifest.xml +++ b/briar-android/AndroidManifest.xml @@ -211,6 +211,17 @@ /> </activity> + <activity + android:name=".android.blogs.ReblogActivity" + android:label="@string/blogs_reblog_button" + android:parentActivityName=".android.blogs.BlogActivity" + android:windowSoftInputMode="stateHidden"> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value=".android.blogs.BlogActivity" + /> + </activity> + <activity android:name=".android.blogs.RssFeedImportActivity" android:label="@string/blogs_rss_feeds_import" diff --git a/briar-android/res/drawable/bubble_white.xml b/briar-android/res/drawable/bubble_white.xml new file mode 100644 index 0000000000..21d0e8e15e --- /dev/null +++ b/briar-android/res/drawable/bubble_white.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape + xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + + <corners + android:radius="@dimen/unread_bubble_size"/> + + <solid + android:color="@color/briar_text_primary_inverse"/> + + <stroke + android:color="@color/briar_text_primary" + android:width="1dp"/> + +</shape> + diff --git a/briar-android/res/layout/activity_introduction.xml b/briar-android/res/layout/activity_fragment_container.xml similarity index 82% rename from briar-android/res/layout/activity_introduction.xml rename to briar-android/res/layout/activity_fragment_container.xml index f351897d0f..e6c20760fb 100644 --- a/briar-android/res/layout/activity_introduction.xml +++ b/briar-android/res/layout/activity_fragment_container.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <FrameLayout - android:id="@+id/introductionContainer" + android:id="@+id/fragmentContainer" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"/> \ No newline at end of file diff --git a/briar-android/res/layout/author_view.xml b/briar-android/res/layout/author_view.xml index 2f4a335641..405376fac9 100644 --- a/briar-android/res/layout/author_view.xml +++ b/briar-android/res/layout/author_view.xml @@ -7,11 +7,25 @@ <de.hdodenhof.circleimageview.CircleImageView android:id="@+id/avatar" style="@style/BriarAvatar" - android:layout_width="30dp" - android:layout_height="30dp" + android:layout_width="@dimen/blogs_avatar_normal_size" + android:layout_height="@dimen/blogs_avatar_normal_size" + android:layout_centerVertical="true" android:layout_marginRight="@dimen/margin_medium" tools:src="@drawable/ic_launcher"/> + <ImageView + android:id="@+id/avatarIcon" + android:layout_width="@dimen/blogs_avatar_icon_size" + android:layout_height="@dimen/blogs_avatar_icon_size" + android:layout_alignBottom="@+id/avatar" + android:layout_alignRight="@+id/avatar" + android:background="@drawable/bubble_white" + android:padding="2dp" + android:scaleType="fitCenter" + android:src="@drawable/ic_repeat" + android:visibility="invisible" + tools:ignore="ContentDescription"/> + <TextView android:id="@+id/authorName" android:layout_width="wrap_content" @@ -20,7 +34,7 @@ android:layout_toEndOf="@+id/avatar" android:layout_toRightOf="@+id/avatar" android:textColor="@color/briar_text_primary" - android:textSize="@dimen/text_size_tiny" + android:textSize="@dimen/text_size_small" tools:text="Author Name"/> <org.briarproject.android.util.TrustIndicatorView @@ -42,7 +56,7 @@ android:layout_toEndOf="@+id/avatar" android:layout_toRightOf="@+id/avatar" android:gravity="bottom" - android:textColor="@color/briar_text_primary" + android:textColor="@color/briar_text_secondary" android:textSize="@dimen/text_size_tiny" tools:text="yesterday"/> diff --git a/briar-android/res/layout/fragment_reblog_dialog.xml b/briar-android/res/layout/fragment_reblog_dialog.xml new file mode 100644 index 0000000000..28b59e7b9f --- /dev/null +++ b/briar-android/res/layout/fragment_reblog_dialog.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView + android:id="@+id/scrollView" + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@color/window_background"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="@dimen/margin_small"> + + <ProgressBar + android:id="@+id/progressBar" + style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerInParent="true"/> + + <include + android:id="@+id/postLayout" + layout="@layout/list_item_blog_post" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + + <EditText + android:id="@+id/inputText" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/postLayout" + android:layout_margin="@dimen/listitem_vertical_margin" + android:gravity="bottom" + android:hint="@string/blogs_reblog_comment_hint" + android:inputType="textShortMessage|textMultiLine|textCapSentences|textAutoCorrect"/> + + <Button + android:id="@+id/publishButton" + style="@style/BriarButton" + android:layout_below="@+id/inputText" + android:enabled="false" + android:text="@string/blogs_reblog_button"/> + + </RelativeLayout> + +</ScrollView> diff --git a/briar-android/res/layout/list_item_blog_comment.xml b/briar-android/res/layout/list_item_blog_comment.xml new file mode 100644 index 0000000000..bdbe42842d --- /dev/null +++ b/briar-android/res/layout/list_item_blog_comment.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout + 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" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:showIn="@layout/list_item_blog_post"> + + <View + android:id="@+id/inputDivider" + style="@style/Divider.Horizontal"/> + + <org.briarproject.android.util.AuthorView + android:id="@+id/authorView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_alignParentTop="true" + android:padding="@dimen/listitem_vertical_margin" + app:persona="commenter"/> + + <TextView + android:id="@+id/bodyView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/authorView" + android:paddingBottom="@dimen/listitem_vertical_margin" + android:paddingLeft="@dimen/listitem_vertical_margin" + android:paddingRight="@dimen/listitem_vertical_margin" + android:textColor="@color/briar_text_secondary" + android:textIsSelectable="true" + android:textSize="@dimen/text_size_small" + tools:text="This is a comment that appears below a blog post. Usually, it is expected to be rather short. Not much longer than this one."/> + +</RelativeLayout> diff --git a/briar-android/res/layout/list_item_blog_post.xml b/briar-android/res/layout/list_item_blog_post.xml index 2408a1923f..4697c461bf 100644 --- a/briar-android/res/layout/list_item_blog_post.xml +++ b/briar-android/res/layout/list_item_blog_post.xml @@ -2,41 +2,77 @@ <android.support.v7.widget.CardView style="@style/BriarCard" 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" android:layout_width="match_parent" android:layout_height="wrap_content"> - <RelativeLayout + <LinearLayout android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:orientation="vertical"> - <org.briarproject.android.util.AuthorView - android:id="@+id/authorView" - android:layout_width="wrap_content" + <RelativeLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_alignParentTop="true" - android:layout_marginBottom="@dimen/listitem_vertical_margin" - android:layout_toLeftOf="@+id/commentView"/> - - <ImageView - android:id="@+id/commentView" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_alignParentTop="true" - android:padding="@dimen/margin_small" - android:src="@drawable/ic_repeat"/> - - <TextView - android:id="@+id/bodyView" - android:layout_width="wrap_content" + android:padding="@dimen/listitem_vertical_margin"> + + <org.briarproject.android.util.AuthorView + android:id="@+id/rebloggerView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_alignParentTop="true" + android:layout_marginBottom="@dimen/listitem_horizontal_margin" + android:layout_toLeftOf="@+id/commentView" + app:persona="reblogger"/> + + <org.briarproject.android.util.AuthorView + android:id="@+id/authorView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_below="@+id/rebloggerView" + android:layout_marginBottom="@dimen/listitem_vertical_margin" + android:layout_toLeftOf="@+id/commentView"/> + + <ImageView + android:id="@+id/commentView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_alignParentTop="true" + android:background="?attr/selectableItemBackground" + android:clickable="true" + android:contentDescription="@string/blogs_reblog_comment_hint" + android:padding="@dimen/margin_small" + android:src="@drawable/ic_repeat"/> + + <TextView + android:id="@+id/bodyView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/authorView" + android:textColor="@color/briar_text_secondary" + android:textIsSelectable="true" + android:textSize="@dimen/text_size_medium" + tools:text="This is a body text that shows the content of a blog post.\n\nThis one is not short, but it is also not too long."/> + + </RelativeLayout> + + <LinearLayout + android:id="@+id/commentContainer" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_below="@+id/authorView" - android:textColor="@color/briar_text_secondary" - android:textSize="@dimen/text_size_medium" - tools:text="This is a body text that shows the content of a blog post.\n\nThis one is not short, but it is also not too long."/> + android:orientation="vertical"> + + <include + layout="@layout/list_item_blog_comment" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + + </LinearLayout> - </RelativeLayout> + </LinearLayout> -</android.support.v7.widget.CardView> \ No newline at end of file +</android.support.v7.widget.CardView> diff --git a/briar-android/res/menu/blogs_blog_actions.xml b/briar-android/res/menu/blogs_blog_actions.xml index 3661a49dff..64b42ac867 100644 --- a/briar-android/res/menu/blogs_blog_actions.xml +++ b/briar-android/res/menu/blogs_blog_actions.xml @@ -1,7 +1,16 @@ <?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"> + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> + + <item + android:id="@+id/action_write_blog_post" + android:icon="@drawable/forum_item_create_white" + android:title="@string/blogs_write_blog_post" + android:visible="false" + app:showAsAction="ifRoom" + tools:visible="true"/> <item android:id="@+id/action_blog_share" @@ -18,6 +27,8 @@ android:id="@+id/action_blog_delete" android:icon="@drawable/action_delete_white" android:title="@string/blogs_remove_blog" - app:showAsAction="never"/> + android:visible="false" + app:showAsAction="never" + tools:visible="true"/> </menu> \ No newline at end of file diff --git a/briar-android/res/menu/blogs_my_blog_actions.xml b/briar-android/res/menu/blogs_my_blog_actions.xml deleted file mode 100644 index 5c9052edb0..0000000000 --- a/briar-android/res/menu/blogs_my_blog_actions.xml +++ /dev/null @@ -1,12 +0,0 @@ -<?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_write_blog_post" - android:icon="@drawable/forum_item_create_white" - android:title="@string/blogs_write_blog_post" - app:showAsAction="ifRoom"/> - -</menu> \ No newline at end of file diff --git a/briar-android/res/values/attrs.xml b/briar-android/res/values/attrs.xml index 0de786b4b5..a09b80b32c 100644 --- a/briar-android/res/values/attrs.xml +++ b/briar-android/res/values/attrs.xml @@ -1,8 +1,17 @@ <?xml version="1.0" encoding="utf-8"?> <resources> + <declare-styleable name="BriarRecyclerView"> <attr name="scrollToEnd" format="boolean" /> <attr name="emptyText" format="string" /> </declare-styleable> + <declare-styleable name="AuthorView"> + <attr name="persona" format="enum"> + <enum name="normal" value="0"/> + <enum name="reblogger" value="1"/> + <enum name="commenter" value="2"/> + </attr> + </declare-styleable> + </resources> \ No newline at end of file diff --git a/briar-android/res/values/dimens.xml b/briar-android/res/values/dimens.xml index 7c676e61ae..9f62ff0944 100644 --- a/briar-android/res/values/dimens.xml +++ b/briar-android/res/values/dimens.xml @@ -43,4 +43,8 @@ <dimen name="forum_nested_indicator">24dp</dimen> <dimen name="forum_avatar_size">20dp</dimen> + <dimen name="blogs_avatar_normal_size">30dp</dimen> + <dimen name="blogs_avatar_icon_size">15dp</dimen> + <dimen name="blogs_avatar_comment_size">20dp</dimen> + </resources> diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml index dc399cd7e1..ba764c356d 100644 --- a/briar-android/res/values/strings.xml +++ b/briar-android/res/values/strings.xml @@ -35,7 +35,7 @@ <string name="nav_drawer_close_description">Close the navigation drawer</string> <string name="contact_list_button">Contacts</string> <string name="forums_button">Forums</string> - <string name="blogs_button">Micro Blogs</string> + <string name="blogs_button">Blogs</string> <string name="settings_button">Settings</string> <string name="sign_out_button">Sign Out</string> @@ -207,7 +207,6 @@ <string name="blogs_my_blogs_create_hint_desc">A short description of your new blog</string> <string name="blogs_my_blogs_create_hint_desc_explanation">Potential readers may or may not subscribe to your blog based on the content of the description.</string> <string name="blogs_my_blogs_empty_state">You don\'t have any blogs.\n\nWhy don\'t you create one now by clicking the plus in the top right screen corner?</string> - <string name="blogs_my_blogs_blog_empty_state">This is the place for content of your blog.\n\nIt seems like you haven\'t written anything yet.\n\nPlease tap the pen icon to compose a new blog post.\n\nDon\'t forget to go public and share your blog.</string> <string name="blogs_my_blogs_created">Blog created</string> <string name="blogs_blog_is_empty">This blog is empty</string> <string name="blogs_other_blog_empty_state">This blog is currently empty.\n\nEither the author hasn\'t written anything yet, or the person who shared this blog with you needs to come online, so posts can be synchronized.</string> @@ -227,6 +226,8 @@ <string name="blogs_remove_blog_dialog_message">Are you sure that you want to remove this blog and all posts?\nNote that this will not remove the blog from other people\'s devices.</string> <string name="blogs_remove_blog_ok">Remove Blog</string> <string name="blogs_blog_removed">Blog Removed</string> + <string name="blogs_reblog_comment_hint">Add an optional comment</string> + <string name="blogs_reblog_button">Reblog</string> <string name="blogs_blog_list">Blog List</string> <string name="blogs_available_blogs">Available Blogs</string> diff --git a/briar-android/res/values/styles.xml b/briar-android/res/values/styles.xml index c432dddfa8..76c9c0a1f4 100644 --- a/briar-android/res/values/styles.xml +++ b/briar-android/res/values/styles.xml @@ -126,7 +126,6 @@ <style name="BriarCard" parent="CardView"> <item name="cardUseCompatPadding">true</item> - <item name="contentPadding">@dimen/listitem_vertical_margin</item> <item name="android:layout_margin">@dimen/margin_small</item> </style> diff --git a/briar-android/src/org/briarproject/android/ActivityComponent.java b/briar-android/src/org/briarproject/android/ActivityComponent.java index 3e491d25b1..2a0d82c4b2 100644 --- a/briar-android/src/org/briarproject/android/ActivityComponent.java +++ b/briar-android/src/org/briarproject/android/ActivityComponent.java @@ -6,10 +6,10 @@ import org.briarproject.android.blogs.BlogActivity; import org.briarproject.android.blogs.BlogFragment; import org.briarproject.android.blogs.BlogListFragment; import org.briarproject.android.blogs.BlogPostFragment; -import org.briarproject.android.blogs.BlogsFragment; import org.briarproject.android.blogs.CreateBlogActivity; import org.briarproject.android.blogs.FeedFragment; -import org.briarproject.android.blogs.MyBlogsFragment; +import org.briarproject.android.blogs.ReblogActivity; +import org.briarproject.android.blogs.ReblogFragment; import org.briarproject.android.blogs.RssFeedImportActivity; import org.briarproject.android.blogs.RssFeedManageActivity; import org.briarproject.android.blogs.WriteBlogPostActivity; @@ -93,6 +93,10 @@ public interface ActivityComponent { void inject(BlogPostFragment fragment); + void inject(ReblogFragment fragment); + + void inject(ReblogActivity activity); + void inject(SettingsActivity activity); void inject(ChangePasswordActivity activity); @@ -106,10 +110,8 @@ public interface ActivityComponent { // Fragments void inject(ContactListFragment fragment); void inject(ForumListFragment fragment); - void inject(BlogsFragment fragment); void inject(BlogListFragment fragment); void inject(FeedFragment fragment); - void inject(MyBlogsFragment fragment); void inject(IntroFragment fragment); void inject(ShowQrCodeFragment fragment); void inject(ContactChooserFragment fragment); diff --git a/briar-android/src/org/briarproject/android/BriarFragmentActivity.java b/briar-android/src/org/briarproject/android/BriarFragmentActivity.java index 0a9f22a2e4..513ace7b56 100644 --- a/briar-android/src/org/briarproject/android/BriarFragmentActivity.java +++ b/briar-android/src/org/briarproject/android/BriarFragmentActivity.java @@ -7,7 +7,7 @@ import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import org.briarproject.R; -import org.briarproject.android.blogs.BlogsFragment; +import org.briarproject.android.blogs.FeedFragment; import org.briarproject.android.contact.ContactListFragment; import org.briarproject.android.forum.ForumListFragment; import org.briarproject.android.fragment.BaseFragment; @@ -27,7 +27,7 @@ public abstract class BriarFragmentActivity extends BriarActivity { actionBar.setTitle(R.string.contact_list_button); } else if (fragmentTag.equals(ForumListFragment.TAG)) { actionBar.setTitle(R.string.forums_button); - } else if (fragmentTag.equals(BlogsFragment.TAG)) { + } else if (fragmentTag.equals(FeedFragment.TAG)) { actionBar.setTitle(R.string.blogs_button); } } diff --git a/briar-android/src/org/briarproject/android/NavDrawerActivity.java b/briar-android/src/org/briarproject/android/NavDrawerActivity.java index 8de76247ad..6381bc9cd1 100644 --- a/briar-android/src/org/briarproject/android/NavDrawerActivity.java +++ b/briar-android/src/org/briarproject/android/NavDrawerActivity.java @@ -21,7 +21,7 @@ import android.widget.ImageView; import android.widget.TextView; import org.briarproject.R; -import org.briarproject.android.blogs.BlogsFragment; +import org.briarproject.android.blogs.FeedFragment; import org.briarproject.android.contact.ContactListFragment; import org.briarproject.android.controller.NavDrawerController; import org.briarproject.android.controller.TransportStateListener; @@ -82,7 +82,7 @@ public class NavDrawerActivity extends BriarFragmentActivity implements startFragment(ContactListFragment.newInstance()); } else if (intent.getBooleanExtra(INTENT_BLOGS, false)) { - startFragment(BlogsFragment.newInstance()); + startFragment(FeedFragment.newInstance()); } setIntent(null); } @@ -186,7 +186,7 @@ public class NavDrawerActivity extends BriarFragmentActivity implements startFragment(ForumListFragment.newInstance()); break; case R.id.nav_btn_blogs: - startFragment(BlogsFragment.newInstance()); + startFragment(FeedFragment.newInstance()); break; case R.id.nav_btn_settings: startActivity(new Intent(this, SettingsActivity.class)); diff --git a/briar-android/src/org/briarproject/android/blogs/BaseController.java b/briar-android/src/org/briarproject/android/blogs/BaseController.java new file mode 100644 index 0000000000..e81c44d602 --- /dev/null +++ b/briar-android/src/org/briarproject/android/blogs/BaseController.java @@ -0,0 +1,39 @@ +package org.briarproject.android.blogs; + +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; + +import org.briarproject.android.controller.handler.ResultExceptionHandler; +import org.briarproject.api.blogs.BlogPostHeader; +import org.briarproject.api.db.DbException; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.MessageId; + +import java.util.Collection; + +public interface BaseController { + + void onStart(); + + void onStop(); + + void loadBlogPosts(GroupId g, + ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler); + + void loadBlogPost(BlogPostHeader header, + ResultExceptionHandler<BlogPostItem, DbException> handler); + + void loadBlogPost(GroupId g, MessageId m, + ResultExceptionHandler<BlogPostItem, DbException> handler); + + void repeatPost(BlogPostItem item, @Nullable String comment, + ResultExceptionHandler<Void, DbException> resultHandler); + + void setOnBlogPostAddedListener(OnBlogPostAddedListener listener); + + interface OnBlogPostAddedListener { + @UiThread + void onBlogPostAdded(BlogPostHeader header, boolean local); + } + +} diff --git a/briar-android/src/org/briarproject/android/blogs/BaseControllerImpl.java b/briar-android/src/org/briarproject/android/blogs/BaseControllerImpl.java new file mode 100644 index 0000000000..ac5ce7b72d --- /dev/null +++ b/briar-android/src/org/briarproject/android/blogs/BaseControllerImpl.java @@ -0,0 +1,250 @@ +package org.briarproject.android.blogs; + +import android.app.Activity; +import android.support.annotation.CallSuper; +import android.support.annotation.Nullable; + +import org.briarproject.android.api.AndroidNotificationManager; +import org.briarproject.android.controller.DbControllerImpl; +import org.briarproject.android.controller.handler.ResultExceptionHandler; +import org.briarproject.api.blogs.Blog; +import org.briarproject.api.blogs.BlogCommentHeader; +import org.briarproject.api.blogs.BlogManager; +import org.briarproject.api.blogs.BlogPostHeader; +import org.briarproject.api.db.DbException; +import org.briarproject.api.event.BlogPostAddedEvent; +import org.briarproject.api.event.Event; +import org.briarproject.api.event.EventBus; +import org.briarproject.api.event.EventListener; +import org.briarproject.api.identity.IdentityManager; +import org.briarproject.api.identity.LocalAuthor; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.MessageId; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; + +abstract class BaseControllerImpl extends DbControllerImpl + implements BaseController, EventListener { + + private static final Logger LOG = + Logger.getLogger(BaseControllerImpl.class.getName()); + + @Inject + protected Activity activity; + @Inject + protected EventBus eventBus; + @Inject + protected AndroidNotificationManager notificationManager; + @Inject + protected IdentityManager identityManager; + + @Inject + protected volatile BlogManager blogManager; + + private final Map<MessageId, String> bodyCache = new ConcurrentHashMap<>(); + private final Map<MessageId, BlogPostHeader> headerCache = + new ConcurrentHashMap<>(); + + protected volatile OnBlogPostAddedListener listener; + + @Override + @CallSuper + public void onStart() { + eventBus.addListener(this); + } + + @Override + @CallSuper + public void onStop() { + eventBus.removeListener(this); + } + + @Override + @CallSuper + public void eventOccurred(Event e) { + if (e instanceof BlogPostAddedEvent) { + final BlogPostAddedEvent m = (BlogPostAddedEvent) e; + LOG.info("New blog post added"); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + listener.onBlogPostAdded(m.getHeader(), m.isLocal()); + } + }); + } + } + + @Override + public void setOnBlogPostAddedListener(OnBlogPostAddedListener listener) { + if (this.listener != null) + throw new IllegalStateException("Listener was already set"); + this.listener = listener; + } + + @Override + public void loadBlogPosts(final GroupId groupId, + final ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) { + if (groupId == null) throw new IllegalStateException(); + runOnDbThread(new Runnable() { + @Override + public void run() { + try { + Collection<BlogPostItem> items = loadItems(groupId); + handler.onResult(items); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + handler.onException(e); + } + } + }); + } + + protected Collection<BlogPostItem> loadItems(GroupId groupId) + throws DbException { + long now = System.currentTimeMillis(); + Collection<BlogPostHeader> headers = + blogManager.getPostHeaders(groupId); + long duration = System.currentTimeMillis() - now; + if (LOG.isLoggable(INFO)) + LOG.info("Loading headers took " + duration + " ms"); + Collection<BlogPostItem> items = new ArrayList<>(headers.size()); + now = System.currentTimeMillis(); + for (BlogPostHeader h : headers) { + headerCache.put(h.getId(), h); + BlogPostItem item = getItem(h); + items.add(item); + } + duration = System.currentTimeMillis() - now; + if (LOG.isLoggable(INFO)) + LOG.info("Loading bodies took " + duration + " ms"); + return items; + } + + @Override + public void loadBlogPost(final BlogPostHeader header, + final ResultExceptionHandler<BlogPostItem, DbException> handler) { + + String body = bodyCache.get(header.getId()); + if (body != null) { + LOG.info("Loaded body from cache"); + handler.onResult(new BlogPostItem(header, body)); + return; + } + runOnDbThread(new Runnable() { + @Override + public void run() { + try { + long now = System.currentTimeMillis(); + BlogPostItem item = getItem(header); + long duration = System.currentTimeMillis() - now; + if (LOG.isLoggable(INFO)) + LOG.info("Loading body took " + duration + " ms"); + handler.onResult(item); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + handler.onException(e); + } + } + }); + } + + @Override + public void loadBlogPost(final GroupId g, final MessageId m, + final ResultExceptionHandler<BlogPostItem, DbException> handler) { + + BlogPostHeader header = headerCache.get(m); + if (header != null) { + LOG.info("Loaded header from cache"); + loadBlogPost(header, handler); + return; + } + runOnDbThread(new Runnable() { + @Override + public void run() { + try { + long now = System.currentTimeMillis(); + BlogPostHeader header = getPostHeader(g, m); + BlogPostItem item = getItem(header); + long duration = System.currentTimeMillis() - now; + if (LOG.isLoggable(INFO)) + LOG.info("Loading post took " + duration + " ms"); + handler.onResult(item); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + handler.onException(e); + } + } + }); + } + + @Override + public void repeatPost(final BlogPostItem item, + final @Nullable String comment, + final ResultExceptionHandler<Void, DbException> handler) { + runOnDbThread(new Runnable() { + @Override + public void run() { + try { + LocalAuthor a = identityManager.getLocalAuthor(); + Blog b = blogManager.getPersonalBlog(a); + BlogPostHeader h = item.getHeader(); + blogManager.addLocalComment(a, b.getId(), comment, h); + handler.onResult(null); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + handler.onException(e); + } + } + }); + } + + private BlogPostHeader getPostHeader(GroupId g, MessageId m) + throws DbException { + + if (g == null) throw new IllegalStateException(); + BlogPostHeader header = headerCache.get(m); + if (header == null) { + header = blogManager.getPostHeader(g, m); + headerCache.put(m, header); + } + return header; + } + + private BlogPostItem getItem(BlogPostHeader h) throws DbException { + String body; + if (h instanceof BlogCommentHeader) { + BlogCommentHeader c = (BlogCommentHeader) h; + BlogCommentItem item = new BlogCommentItem(c); + body = getPostBody(item.getPostHeader().getId()); + item.setBody(body); + return item; + } else { + body = getPostBody(h.getId()); + return new BlogPostItem(h, body); + } + } + + private String getPostBody(MessageId m) throws DbException { + String body = bodyCache.get(m); + if (body == null) { + body = blogManager.getPostBody(m); + if (body != null) bodyCache.put(m, body); + } + //noinspection ConstantConditions + return body; + } + +} diff --git a/briar-android/src/org/briarproject/android/blogs/BlogActivity.java b/briar-android/src/org/briarproject/android/blogs/BlogActivity.java index 7c3309010e..7e14c9d8ec 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogActivity.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogActivity.java @@ -14,7 +14,7 @@ import android.widget.ProgressBar; import org.briarproject.R; import org.briarproject.android.ActivityComponent; import org.briarproject.android.BriarActivity; -import org.briarproject.android.blogs.BlogController.BlogPostListener; +import org.briarproject.android.blogs.BaseController.OnBlogPostAddedListener; import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener; import org.briarproject.android.controller.handler.UiResultExceptionHandler; import org.briarproject.android.fragment.BaseFragment.BaseFragmentListener; @@ -33,16 +33,16 @@ import javax.inject.Inject; import static android.view.View.GONE; import static android.view.View.VISIBLE; -public class BlogActivity extends BriarActivity implements BlogPostListener, +public class BlogActivity extends BriarActivity implements + OnBlogPostAddedListener, OnBlogPostClickListener, BaseFragmentListener { static final int REQUEST_WRITE_POST = 1; static final int REQUEST_SHARE = 2; - static final String BLOG_NAME = "briar.BLOG_NAME"; - static final String IS_MY_BLOG = "briar.IS_MY_BLOG"; + public static final String BLOG_NAME = "briar.BLOG_NAME"; static final String IS_NEW_BLOG = "briar.IS_NEW_BLOG"; - private static final String POST_ID = "briar.POST_ID"; + public static final String POST_ID = "briar.POST_ID"; private GroupId groupId; private ProgressBar progressBar; @@ -50,7 +50,7 @@ public class BlogActivity extends BriarActivity implements BlogPostListener, private BlogPagerAdapter blogPagerAdapter; private BlogPostPagerAdapter postPagerAdapter; private String blogName; - private boolean myBlog, isNew; + private boolean isNew; private MessageId savedPostId; @Inject @@ -72,7 +72,6 @@ public class BlogActivity extends BriarActivity implements BlogPostListener, if (blogName != null) setTitle(blogName); // Is this our blog and was it just created? - myBlog = i.getBooleanExtra(IS_MY_BLOG, false); isNew = i.getBooleanExtra(IS_NEW_BLOG, false); setContentView(R.layout.activity_blog); @@ -254,7 +253,7 @@ public class BlogActivity extends BriarActivity implements BlogPostListener, @Override public Fragment getItem(int position) { - return BlogFragment.newInstance(groupId, blogName, myBlog, isNew); + return BlogFragment.newInstance(groupId, blogName, isNew); } @Override diff --git a/briar-android/src/org/briarproject/android/blogs/BlogCommentItem.java b/briar-android/src/org/briarproject/android/blogs/BlogCommentItem.java new file mode 100644 index 0000000000..f499d12460 --- /dev/null +++ b/briar-android/src/org/briarproject/android/blogs/BlogCommentItem.java @@ -0,0 +1,63 @@ +package org.briarproject.android.blogs; + +import android.support.annotation.UiThread; + +import org.briarproject.api.blogs.BlogCommentHeader; +import org.briarproject.api.blogs.BlogPostHeader; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +@UiThread +class BlogCommentItem extends BlogPostItem { + + private final BlogPostHeader postHeader; + private final List<BlogCommentHeader> comments = new ArrayList<>(); + + BlogCommentItem(BlogCommentHeader header) { + super(header, null); + postHeader = collectComments(header); + Collections.sort(comments, new BlogCommentComparator()); + } + + private BlogPostHeader collectComments(BlogPostHeader header) { + if (header instanceof BlogCommentHeader) { + BlogCommentHeader comment = (BlogCommentHeader) header; + if (comment.getComment() != null) + comments.add(comment); + return collectComments(comment.getParent()); + } else { + return header; + } + } + + public void setBody(String body) { + this.body = body; + } + + @Override + public BlogCommentHeader getHeader() { + return (BlogCommentHeader) super.getHeader(); + } + + @Override + BlogPostHeader getPostHeader() { + return postHeader; + } + + List<BlogCommentHeader> getComments() { + return comments; + } + + private static class BlogCommentComparator + implements Comparator<BlogCommentHeader> { + @Override + public int compare(org.briarproject.api.blogs.BlogCommentHeader h1, + org.briarproject.api.blogs.BlogCommentHeader h2) { + // re-use same comparator used for blog posts, but reverse it + return BlogCommentItem.compare(h2, h1); + } + } +} diff --git a/briar-android/src/org/briarproject/android/blogs/BlogController.java b/briar-android/src/org/briarproject/android/blogs/BlogController.java index 90fbb61cf1..768d29c14c 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogController.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogController.java @@ -11,26 +11,20 @@ import org.briarproject.api.sync.MessageId; import java.util.Collection; -public interface BlogController extends ActivityLifecycleController { +public interface BlogController extends BaseController { void setGroupId(GroupId g); void loadBlogPosts( ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler); - void loadBlogPost(BlogPostHeader header, - ResultExceptionHandler<BlogPostItem, DbException> handler); - void loadBlogPost(MessageId m, ResultExceptionHandler<BlogPostItem, DbException> handler); + void isMyBlog(ResultExceptionHandler<Boolean, DbException> handler); + void canDeleteBlog(ResultExceptionHandler<Boolean, DbException> handler); void deleteBlog(ResultExceptionHandler<Void, DbException> handler); - interface BlogPostListener { - @UiThread - void onBlogPostAdded(BlogPostHeader header, boolean local); - } - } diff --git a/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java b/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java index 9906e511e8..a85b487345 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java @@ -1,70 +1,40 @@ package org.briarproject.android.blogs; -import android.app.Activity; - -import org.briarproject.android.api.AndroidNotificationManager; -import org.briarproject.android.controller.DbControllerImpl; +import org.briarproject.android.controller.ActivityLifecycleController; import org.briarproject.android.controller.handler.ResultExceptionHandler; import org.briarproject.api.blogs.Blog; -import org.briarproject.api.blogs.BlogManager; -import org.briarproject.api.blogs.BlogPostHeader; import org.briarproject.api.db.DbException; import org.briarproject.api.event.BlogPostAddedEvent; import org.briarproject.api.event.Event; -import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventListener; import org.briarproject.api.event.GroupRemovedEvent; +import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.MessageId; -import java.util.ArrayList; import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; import javax.inject.Inject; -import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; -public class BlogControllerImpl extends DbControllerImpl - implements BlogController, EventListener { +public class BlogControllerImpl extends BaseControllerImpl + implements ActivityLifecycleController, BlogController, EventListener { private static final Logger LOG = Logger.getLogger(BlogControllerImpl.class.getName()); - @Inject - protected Activity activity; - @Inject - protected EventBus eventBus; - @Inject - protected AndroidNotificationManager notificationManager; - - @Inject - protected volatile BlogManager blogManager; - - private final Map<MessageId, byte[]> bodyCache = new ConcurrentHashMap<>(); - private final Map<MessageId, BlogPostHeader> headerCache = - new ConcurrentHashMap<>(); - - private volatile BlogPostListener listener; private volatile GroupId groupId = null; @Inject BlogControllerImpl() { } - @Override - public void setGroupId(GroupId g) { - groupId = g; - } - @Override public void onActivityCreate() { - if (activity instanceof BlogPostListener) { - listener = (BlogPostListener) activity; + if (activity instanceof OnBlogPostAddedListener) { + listener = (OnBlogPostAddedListener) activity; } else { throw new IllegalStateException( "An activity that injects the BlogController must " + @@ -74,34 +44,33 @@ public class BlogControllerImpl extends DbControllerImpl @Override public void onActivityResume() { + super.onStart(); notificationManager.blockNotification(groupId); notificationManager.clearBlogPostNotification(groupId); - eventBus.addListener(this); } @Override public void onActivityPause() { + super.onStop(); notificationManager.unblockNotification(groupId); - eventBus.removeListener(this); } @Override public void onActivityDestroy() { } + @Override + public void setGroupId(GroupId g) { + groupId = g; + } + @Override public void eventOccurred(Event e) { if (groupId == null) throw new IllegalStateException(); if (e instanceof BlogPostAddedEvent) { - final BlogPostAddedEvent m = (BlogPostAddedEvent) e; - if (m.getGroupId().equals(groupId)) { - LOG.info("New blog post added"); - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - listener.onBlogPostAdded(m.getHeader(), m.isLocal()); - } - }); + BlogPostAddedEvent s = (BlogPostAddedEvent) e; + if (s.getGroupId().equals(groupId)) { + super.eventOccurred(e); } } else if (e instanceof GroupRemovedEvent) { GroupRemovedEvent s = (GroupRemovedEvent) e; @@ -122,86 +91,27 @@ public class BlogControllerImpl extends DbControllerImpl public void loadBlogPosts( final ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) { if (groupId == null) throw new IllegalStateException(); - runOnDbThread(new Runnable() { - @Override - public void run() { - try { - long now = System.currentTimeMillis(); - Collection<BlogPostHeader> headers = - blogManager.getPostHeaders(groupId); - long duration = System.currentTimeMillis() - now; - if (LOG.isLoggable(INFO)) - LOG.info("Loading headers took " + duration + " ms"); - List<BlogPostItem> items = new ArrayList<>(headers.size()); - now = System.currentTimeMillis(); - for (BlogPostHeader h : headers) { - headerCache.put(h.getId(), h); - byte[] body = getPostBody(h.getId()); - items.add(new BlogPostItem(groupId, h, body)); - } - duration = System.currentTimeMillis() - now; - if (LOG.isLoggable(INFO)) - LOG.info("Loading bodies took " + duration + " ms"); - handler.onResult(items); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - handler.onException(e); - } - } - }); + loadBlogPosts(groupId, handler); } @Override - public void loadBlogPost(final BlogPostHeader header, + public void loadBlogPost(final MessageId m, final ResultExceptionHandler<BlogPostItem, DbException> handler) { if (groupId == null) throw new IllegalStateException(); - byte[] body = bodyCache.get(header.getId()); - if (body != null) { - LOG.info("Loaded body from cache"); - handler.onResult(new BlogPostItem(groupId, header, body)); - return; - } - runOnDbThread(new Runnable() { - @Override - public void run() { - try { - long now = System.currentTimeMillis(); - byte[] body = getPostBody(header.getId()); - long duration = System.currentTimeMillis() - now; - if (LOG.isLoggable(INFO)) - LOG.info("Loading body took " + duration + " ms"); - handler.onResult(new BlogPostItem(groupId, header, body)); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - handler.onException(e); - } - } - }); + loadBlogPost(groupId, m, handler); } @Override - public void loadBlogPost(final MessageId m, - final ResultExceptionHandler<BlogPostItem, DbException> handler) { + public void isMyBlog( + final ResultExceptionHandler<Boolean, DbException> handler) { if (groupId == null) throw new IllegalStateException(); - BlogPostHeader header = headerCache.get(m); - if (header != null) { - LOG.info("Loaded header from cache"); - loadBlogPost(header, handler); - return; - } runOnDbThread(new Runnable() { @Override public void run() { try { - long now = System.currentTimeMillis(); - BlogPostHeader header = getPostHeader(m); - byte[] body = getPostBody(m); - long duration = System.currentTimeMillis() - now; - if (LOG.isLoggable(INFO)) - LOG.info("Loading post took " + duration + " ms"); - handler.onResult(new BlogPostItem(groupId, header, body)); + LocalAuthor a = identityManager.getLocalAuthor(); + Blog b = blogManager.getBlog(groupId); + handler.onResult(b.getAuthor().getId().equals(a.getId())); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -209,25 +119,7 @@ public class BlogControllerImpl extends DbControllerImpl } } }); - } - private BlogPostHeader getPostHeader(MessageId m) throws DbException { - if (groupId == null) throw new IllegalStateException(); - BlogPostHeader header = headerCache.get(m); - if (header == null) { - header = blogManager.getPostHeader(groupId, m); - headerCache.put(m, header); - } - return header; - } - - private byte[] getPostBody(MessageId m) throws DbException { - byte[] body = bodyCache.get(m); - if (body == null) { - body = blogManager.getPostBody(m); - if (body != null) bodyCache.put(m, body); - } - return body; } @Override diff --git a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java index 0e89542af0..b3b87a113c 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java @@ -19,7 +19,7 @@ import android.widget.Toast; import org.briarproject.R; import org.briarproject.android.ActivityComponent; -import org.briarproject.android.blogs.BlogController.BlogPostListener; +import org.briarproject.android.blogs.BaseController.OnBlogPostAddedListener; import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener; import org.briarproject.android.controller.handler.UiResultExceptionHandler; import org.briarproject.android.fragment.BaseFragment; @@ -42,12 +42,12 @@ import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation; import static android.widget.Toast.LENGTH_SHORT; import static org.briarproject.android.BriarActivity.GROUP_ID; import static org.briarproject.android.blogs.BlogActivity.BLOG_NAME; -import static org.briarproject.android.blogs.BlogActivity.IS_MY_BLOG; import static org.briarproject.android.blogs.BlogActivity.IS_NEW_BLOG; import static org.briarproject.android.blogs.BlogActivity.REQUEST_SHARE; import static org.briarproject.android.blogs.BlogActivity.REQUEST_WRITE_POST; -public class BlogFragment extends BaseFragment implements BlogPostListener { +public class BlogFragment extends BaseFragment implements + OnBlogPostAddedListener { public final static String TAG = BlogFragment.class.getName(); @@ -56,20 +56,18 @@ public class BlogFragment extends BaseFragment implements BlogPostListener { private GroupId groupId; private String blogName; - private boolean myBlog; private BlogPostAdapter adapter; private BriarRecyclerView list; - private MenuItem deleteButton; + private MenuItem writeButton, deleteButton; static BlogFragment newInstance(GroupId groupId, String name, - boolean myBlog, boolean isNew) { + boolean isNew) { BlogFragment f = new BlogFragment(); Bundle bundle = new Bundle(); bundle.putByteArray(GROUP_ID, groupId.getBytes()); bundle.putString(BLOG_NAME, name); - bundle.putBoolean(IS_MY_BLOG, myBlog); bundle.putBoolean(IS_NEW_BLOG, isNew); f.setArguments(bundle); @@ -88,7 +86,6 @@ public class BlogFragment extends BaseFragment implements BlogPostListener { if (b == null) throw new IllegalStateException("No group ID in args"); groupId = new GroupId(b); blogName = args.getString(BLOG_NAME); - myBlog = args.getBoolean(IS_MY_BLOG); boolean isNew = args.getBoolean(IS_NEW_BLOG); View v = inflater.inflate(R.layout.fragment_blog, container, false); @@ -99,12 +96,7 @@ public class BlogFragment extends BaseFragment implements BlogPostListener { list.setLayoutManager(new LinearLayoutManager(getActivity())); list.setAdapter(adapter); list.showProgressBar(); - if (myBlog) { - list.setEmptyText( - getString(R.string.blogs_my_blogs_blog_empty_state)); - } else { - list.setEmptyText(getString(R.string.blogs_other_blog_empty_state)); - } + list.setEmptyText(getString(R.string.blogs_other_blog_empty_state)); // show snackbar if this blog was just created if (isNew) { @@ -128,7 +120,8 @@ public class BlogFragment extends BaseFragment implements BlogPostListener { @Override public void onStart() { super.onStart(); - if (!myBlog) checkIfBlogCanBeDeleted(); + checkIfThisIsMyBlog(); + checkIfBlogCanBeDeleted(); } @Override @@ -146,13 +139,10 @@ public class BlogFragment extends BaseFragment implements BlogPostListener { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - if (myBlog) { - inflater.inflate(R.menu.blogs_my_blog_actions, menu); - } else { - inflater.inflate(R.menu.blogs_blog_actions, menu); - deleteButton = menu.findItem(R.id.action_blog_delete); - deleteButton.setVisible(false); - } + inflater.inflate(R.menu.blogs_blog_actions, menu); + writeButton = menu.findItem(R.id.action_write_blog_post); + deleteButton = menu.findItem(R.id.action_blog_delete); + super.onCreateOptionsMenu(menu, inflater); } @@ -199,7 +189,9 @@ public class BlogFragment extends BaseFragment implements BlogPostListener { public void onActivityResult(int request, int result, Intent data) { super.onActivityResult(request, result, data); - if (request == REQUEST_SHARE && result == RESULT_OK) { + if (request == REQUEST_WRITE_POST && result == RESULT_OK) { + displaySnackbar(R.string.blogs_blog_post_created); + } else if (request == REQUEST_SHARE && result == RESULT_OK) { displaySnackbar(R.string.blogs_sharing_snackbar); } } @@ -217,7 +209,12 @@ public class BlogFragment extends BaseFragment implements BlogPostListener { @Override public void onResultUi(BlogPostItem post) { adapter.add(post); - if (local) list.scrollToPosition(0); + if (local) { + list.scrollToPosition(0); + displaySnackbar(R.string.blogs_blog_post_created); + } else { + displaySnackbar(R.string.blogs_blog_post_received); + } } @Override @@ -251,6 +248,25 @@ public class BlogFragment extends BaseFragment implements BlogPostListener { }); } + private void checkIfThisIsMyBlog() { + blogController.canDeleteBlog( + new UiResultExceptionHandler<Boolean, DbException>( + getActivity()) { + @Override + public void onResultUi(Boolean isMyBlog) { + if (isMyBlog) { + showWriteButton(); + } + } + + @Override + public void onExceptionUi(DbException exception) { + // TODO: Decide how to handle errors in the UI + getActivity().finish(); + } + }); + } + private void checkIfBlogCanBeDeleted() { blogController.canDeleteBlog( new UiResultExceptionHandler<Boolean, DbException>( @@ -270,6 +286,11 @@ public class BlogFragment extends BaseFragment implements BlogPostListener { }); } + private void showWriteButton() { + if (writeButton != null) + writeButton.setVisible(true); + } + private void showDeleteButton() { if (deleteButton != null) deleteButton.setVisible(true); diff --git a/briar-android/src/org/briarproject/android/blogs/BlogListAdapter.java b/briar-android/src/org/briarproject/android/blogs/BlogListAdapter.java index 92f9430c42..37f7dd3016 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogListAdapter.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogListAdapter.java @@ -1,7 +1,6 @@ package org.briarproject.android.blogs; import android.app.Activity; -import android.content.Context; import android.content.Intent; import android.support.annotation.Nullable; import android.support.v4.app.ActivityCompat; @@ -9,7 +8,6 @@ import android.support.v4.app.ActivityOptionsCompat; import android.support.v4.content.ContextCompat; import android.support.v7.util.SortedList; import android.support.v7.widget.RecyclerView; -import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -23,12 +21,10 @@ import org.briarproject.api.sync.GroupId; import java.util.Collection; -import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation; import static android.view.View.GONE; import static android.view.View.VISIBLE; import static org.briarproject.android.BriarActivity.GROUP_ID; import static org.briarproject.android.blogs.BlogActivity.BLOG_NAME; -import static org.briarproject.android.blogs.BlogActivity.IS_MY_BLOG; class BlogListAdapter extends RecyclerView.Adapter<BlogListAdapter.BlogViewHolder> { @@ -136,7 +132,6 @@ class BlogListAdapter extends Blog b = item.getBlog(); i.putExtra(GROUP_ID, b.getId().getBytes()); i.putExtra(BLOG_NAME, b.getName()); - i.putExtra(IS_MY_BLOG, item.isOurs()); ActivityOptionsCompat options = ActivityOptionsCompat .makeCustomAnimation(ctx, android.R.anim.fade_in, android.R.anim.fade_out); diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostAdapter.java b/briar-android/src/org/briarproject/android/blogs/BlogPostAdapter.java index e6b2ee82ef..badf034503 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogPostAdapter.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogPostAdapter.java @@ -6,21 +6,15 @@ import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; import org.briarproject.R; -import org.briarproject.android.util.AuthorView; -import org.briarproject.util.StringUtils; import java.util.Collection; -class BlogPostAdapter extends - RecyclerView.Adapter<BlogPostAdapter.BlogPostHolder> { +class BlogPostAdapter extends RecyclerView.Adapter<BlogPostViewHolder> { private SortedList<BlogPostItem> posts = new SortedList<>( BlogPostItem.class, new SortedList.Callback<BlogPostItem>() { - @Override public int compare(BlogPostItem a, BlogPostItem b) { return a.compareTo(b); @@ -56,7 +50,6 @@ class BlogPostAdapter extends return a.getId().equals(b.getId()); } }); - private final Context ctx; private final OnBlogPostClickListener listener; @@ -66,30 +59,18 @@ class BlogPostAdapter extends } @Override - public BlogPostHolder onCreateViewHolder(ViewGroup parent, int viewType) { + public BlogPostViewHolder onCreateViewHolder(ViewGroup parent, + int viewType) { View v = LayoutInflater.from(ctx).inflate( R.layout.list_item_blog_post, parent, false); - return new BlogPostHolder(v); + BlogPostViewHolder ui = new BlogPostViewHolder(v); + ui.setOnBlogPostClickListener(listener); + return ui; } @Override - public void onBindViewHolder(final BlogPostHolder ui, int position) { - final BlogPostItem post = getItem(position); - - // author and date - ui.author.setAuthor(post.getAuthor()); - ui.author.setAuthorStatus(post.getAuthorStatus()); - ui.author.setDate(post.getTimestamp()); - - // post body - ui.body.setText(StringUtils.fromUtf8(post.getBody())); - - ui.layout.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - listener.onBlogPostClick(post); - } - }); + public void onBindViewHolder(BlogPostViewHolder ui, int position) { + ui.bindItem(getItem(position)); } @Override @@ -121,23 +102,6 @@ class BlogPostAdapter extends return posts.size() == 0; } - static class BlogPostHolder extends RecyclerView.ViewHolder { - - private final ViewGroup layout; - private final AuthorView author; - private final ImageView comment; - private final TextView body; - - BlogPostHolder(View v) { - super(v); - - layout = (ViewGroup) v; - author = (AuthorView) v.findViewById(R.id.authorView); - comment = (ImageView) v.findViewById(R.id.commentView); - body = (TextView) v.findViewById(R.id.bodyView); - } - } - interface OnBlogPostClickListener { void onBlogPostClick(BlogPostItem post); } diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java index f96deda0b2..7e0cb1c95a 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java @@ -19,7 +19,6 @@ import org.briarproject.android.util.TrustIndicatorView; import org.briarproject.api.db.DbException; import org.briarproject.api.identity.Author; import org.briarproject.api.sync.MessageId; -import org.briarproject.util.StringUtils; import java.util.logging.Logger; @@ -27,7 +26,6 @@ import javax.inject.Inject; import im.delight.android.identicons.IdenticonDrawable; -import static android.view.View.GONE; import static org.briarproject.android.util.AndroidUtils.MIN_RESOLUTION; public class BlogPostFragment extends BaseFragment { @@ -135,11 +133,7 @@ public class BlogPostFragment extends BaseFragment { if (ctx != null) { ui.date.setText(AndroidUtils.formatDate(ctx, post.getTimestamp())); } - - // TODO remove #598 - ui.title.setVisibility(GONE); - - ui.body.setText(StringUtils.fromUtf8(post.getBody())); + ui.body.setText(post.getBody()); } private static class BlogPostViewHolder { @@ -148,7 +142,6 @@ public class BlogPostFragment extends BaseFragment { private final TextView authorName; private final TrustIndicatorView trust; private final TextView date; - private final TextView title; private final TextView body; private BlogPostViewHolder(View v) { @@ -156,7 +149,6 @@ public class BlogPostFragment extends BaseFragment { authorName = (TextView) v.findViewById(R.id.authorName); trust = (TrustIndicatorView) v.findViewById(R.id.trustIndicator); date = (TextView) v.findViewById(R.id.date); - title = (TextView) v.findViewById(R.id.title); body = (TextView) v.findViewById(R.id.body); } } diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java b/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java index 55a439e2ed..5d83843f5e 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java @@ -12,16 +12,14 @@ import org.briarproject.api.sync.MessageId; // This class is not thread-safe class BlogPostItem implements Comparable<BlogPostItem> { - private final GroupId groupId; private final BlogPostHeader header; - private final byte[] body; + protected String body; private boolean read; - BlogPostItem(GroupId groupId, BlogPostHeader header, byte[] body) { - this.groupId = groupId; + BlogPostItem(BlogPostHeader header, @Nullable String body) { this.header = header; this.body = body; - read = header.isRead(); + this.read = header.isRead(); } public MessageId getId() { @@ -29,17 +27,13 @@ class BlogPostItem implements Comparable<BlogPostItem> { } public GroupId getGroupId() { - return groupId; + return header.getGroupId(); } public long getTimestamp() { return header.getTimestamp(); } - public long getTimeReceived() { - return header.getTimeReceived(); - } - public Author getAuthor() { return header.getAuthor(); } @@ -48,7 +42,7 @@ class BlogPostItem implements Comparable<BlogPostItem> { return header.getAuthorStatus(); } - public byte[] getBody() { + public String getBody() { return body; } @@ -56,15 +50,23 @@ class BlogPostItem implements Comparable<BlogPostItem> { return read; } - public void setRead(boolean read) { - this.read = read; + public BlogPostHeader getHeader() { + return header; + } + + BlogPostHeader getPostHeader() { + return getHeader(); } @Override public int compareTo(@NonNull BlogPostItem other) { if (this == other) return 0; + return compare(getHeader(), other.getHeader()); + } + + protected static int compare(BlogPostHeader h1, BlogPostHeader h2) { // The newest post comes first - long aTime = getTimeReceived(), bTime = other.getTimeReceived(); + long aTime = h1.getTimeReceived(), bTime = h2.getTimeReceived(); if (aTime > bTime) return -1; if (aTime < bTime) return 1; return 0; diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java b/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java new file mode 100644 index 0000000000..535c85e260 --- /dev/null +++ b/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java @@ -0,0 +1,142 @@ +package org.briarproject.android.blogs; + +import android.content.Context; +import android.content.Intent; +import android.support.v4.app.ActivityOptionsCompat; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.briarproject.R; +import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener; +import org.briarproject.android.util.AuthorView; +import org.briarproject.api.blogs.BlogCommentHeader; +import org.briarproject.api.blogs.BlogPostHeader; +import org.briarproject.api.identity.Author; + +import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; +import static org.briarproject.android.BriarActivity.GROUP_ID; +import static org.briarproject.android.blogs.BlogActivity.POST_ID; +import static org.briarproject.api.blogs.MessageType.POST; + +public class BlogPostViewHolder extends RecyclerView.ViewHolder { + + private final Context ctx; + private OnBlogPostClickListener listener; + + private final ViewGroup layout; + private final AuthorView reblogger; + private final AuthorView author; + private final ImageView reblogButton; + private final TextView body; + private final ViewGroup commentContainer; + + BlogPostViewHolder(View v) { + super(v); + + ctx = v.getContext(); + layout = (ViewGroup) v; + reblogger = (AuthorView) v.findViewById(R.id.rebloggerView); + author = (AuthorView) v.findViewById(R.id.authorView); + reblogButton = (ImageView) v.findViewById(R.id.commentView); + body = (TextView) v.findViewById(R.id.bodyView); + commentContainer = + (ViewGroup) v.findViewById(R.id.commentContainer); + } + + void setOnBlogPostClickListener(OnBlogPostClickListener listener) { + this.listener = listener; + } + + void setVisibility(int visibility) { + layout.setVisibility(visibility); + } + + void hideReblogButton() { + reblogButton.setVisibility(GONE); + } + + void bindItem(final BlogPostItem item) { + // author and date + BlogPostHeader post = item.getPostHeader(); + Author a = post.getAuthor(); + author.setAuthor(a); + author.setAuthorStatus(post.getAuthorStatus()); + author.setDate(post.getTimestamp()); + // TODO make author clickable more often #624 + if (item.getHeader().getType() == POST) { + author.setBlogLink(post.getGroupId()); + } else { + author.unsetBlogLink(); + } + + // post body + body.setText(item.getBody()); + + // reblog button + reblogButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent i = new Intent(ctx, ReblogActivity.class); + i.putExtra(GROUP_ID, item.getGroupId().getBytes()); + i.putExtra(POST_ID, item.getId().getBytes()); + ActivityOptionsCompat options = + makeCustomAnimation(ctx, android.R.anim.slide_in_left, + android.R.anim.slide_out_right); + Intent[] intents = { i }; + ContextCompat.startActivities(ctx, intents, options.toBundle()); + } + }); + + // comments + commentContainer.removeAllViews(); + if (item instanceof BlogCommentItem) { + onBindComment((BlogCommentItem) item); + } else { + reblogger.setVisibility(GONE); + } + + layout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (listener != null) { + listener.onBlogPostClick(item); + } + } + }); + } + + private void onBindComment(final BlogCommentItem item) { + // reblogger + reblogger.setAuthor(item.getAuthor()); + reblogger.setAuthorStatus(item.getAuthorStatus()); + reblogger.setDate(item.getTimestamp()); + reblogger.setBlogLink(item.getGroupId()); + reblogger.setVisibility(VISIBLE); + + // comments + for (BlogCommentHeader c : item.getComments()) { + View v = LayoutInflater.from(ctx) + .inflate(R.layout.list_item_blog_comment, + commentContainer, false); + + AuthorView author = (AuthorView) v.findViewById(R.id.authorView); + TextView body = (TextView) v.findViewById(R.id.bodyView); + + author.setAuthor(c.getAuthor()); + author.setAuthorStatus(c.getAuthorStatus()); + author.setDate(c.getTimestamp()); + // TODO make author clickable #624 + + body.setText(c.getComment()); + + commentContainer.addView(v); + } + } +} diff --git a/briar-android/src/org/briarproject/android/blogs/BlogsFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogsFragment.java deleted file mode 100644 index 80f2340103..0000000000 --- a/briar-android/src/org/briarproject/android/blogs/BlogsFragment.java +++ /dev/null @@ -1,118 +0,0 @@ -package org.briarproject.android.blogs; - -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.design.widget.TabLayout; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentStatePagerAdapter; -import android.support.v4.view.ViewPager; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import org.briarproject.R; -import org.briarproject.android.ActivityComponent; -import org.briarproject.android.fragment.BaseFragment; - -import static android.view.View.GONE; - -public class BlogsFragment extends BaseFragment { - - public final static String TAG = BlogsFragment.class.getName(); - - private static final String SELECTED_TAB = "selectedTab"; - private TabLayout tabLayout; - - public static BlogsFragment newInstance() { - - Bundle args = new Bundle(); - - BlogsFragment fragment = new BlogsFragment(); - fragment.setArguments(args); - return fragment; - } - - @Nullable - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - View v = inflater.inflate(R.layout.fragment_blogs, container, - false); - - tabLayout = (TabLayout) v.findViewById(R.id.tabLayout); - ViewPager viewPager = (ViewPager) v.findViewById(R.id.pager); - - String[] titles = { - getString(R.string.blogs_feed), - getString(R.string.blogs_my_blogs), - getString(R.string.blogs_blog_list), - getString(R.string.blogs_available_blogs), - getString(R.string.blogs_drafts) - }; - TabAdapter tabAdapter = - new TabAdapter(getChildFragmentManager(), titles); - viewPager.setAdapter(tabAdapter); - tabLayout.setupWithViewPager(viewPager); - - tabLayout.setVisibility(GONE); - - if (savedInstanceState != null) { - int position = savedInstanceState.getInt(SELECTED_TAB, 0); - viewPager.setCurrentItem(position); - } - return v; - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putInt(SELECTED_TAB, tabLayout.getSelectedTabPosition()); - } - - @Override - public String getUniqueTag() { - return TAG; - } - - @Override - public void injectFragment(ActivityComponent component) { - component.inject(this); - } - - - private static class TabAdapter extends FragmentStatePagerAdapter { - private String[] titles; - - TabAdapter(FragmentManager fm, String[] titles) { - super(fm); - this.titles = titles; - } - - @Override - public int getCount() { - return 1; -// return titles.length; - } - - @Override - public Fragment getItem(int position) { - return FeedFragment.newInstance(); -// switch (position) { -// case 0: -// return FeedFragment.newInstance(); -// case 1: -// return new MyBlogsFragment(); -// default: -// return BlogListFragment.newInstance(position); -// } - } - - @Override - public CharSequence getPageTitle(int position) { - return titles[position]; - } - } - -} diff --git a/briar-android/src/org/briarproject/android/blogs/CreateBlogActivity.java b/briar-android/src/org/briarproject/android/blogs/CreateBlogActivity.java index 980d67d1da..5bac574b5c 100644 --- a/briar-android/src/org/briarproject/android/blogs/CreateBlogActivity.java +++ b/briar-android/src/org/briarproject/android/blogs/CreateBlogActivity.java @@ -15,7 +15,6 @@ import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; -import android.widget.Toast; import org.briarproject.R; import org.briarproject.android.ActivityComponent; @@ -35,11 +34,9 @@ import javax.inject.Inject; import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation; import static android.view.View.GONE; import static android.view.View.VISIBLE; -import static android.widget.Toast.LENGTH_LONG; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static org.briarproject.android.blogs.BlogActivity.BLOG_NAME; -import static org.briarproject.android.blogs.BlogActivity.IS_MY_BLOG; import static org.briarproject.android.blogs.BlogActivity.IS_NEW_BLOG; import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_DESC_LENGTH; import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_TITLE_LENGTH; @@ -180,7 +177,6 @@ public class CreateBlogActivity extends BriarActivity new Intent(CreateBlogActivity.this, BlogActivity.class); i.putExtra(GROUP_ID, b.getId().getBytes()); i.putExtra(BLOG_NAME, b.getName()); - i.putExtra(IS_MY_BLOG, true); i.putExtra(IS_NEW_BLOG, true); ActivityOptionsCompat options = makeCustomAnimation(CreateBlogActivity.this, diff --git a/briar-android/src/org/briarproject/android/blogs/FeedController.java b/briar-android/src/org/briarproject/android/blogs/FeedController.java index d449eb60be..e2934f49fd 100644 --- a/briar-android/src/org/briarproject/android/blogs/FeedController.java +++ b/briar-android/src/org/briarproject/android/blogs/FeedController.java @@ -1,24 +1,17 @@ package org.briarproject.android.blogs; +import org.briarproject.android.controller.handler.ResultExceptionHandler; import org.briarproject.android.controller.handler.ResultHandler; import org.briarproject.api.blogs.Blog; +import org.briarproject.api.db.DbException; import java.util.Collection; -public interface FeedController { +public interface FeedController extends BaseController { - void onResume(); - - void onPause(); - - void loadPosts(ResultHandler<Collection<BlogPostItem>> resultHandler); + void loadBlogPosts( + ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler); void loadPersonalBlog(ResultHandler<Blog> resultHandler); - void setOnBlogPostAddedListener(OnBlogPostAddedListener listener); - - interface OnBlogPostAddedListener { - void onBlogPostAdded(final BlogPostItem post); - } - } diff --git a/briar-android/src/org/briarproject/android/blogs/FeedControllerImpl.java b/briar-android/src/org/briarproject/android/blogs/FeedControllerImpl.java index 7c093598b1..50074c0a7a 100644 --- a/briar-android/src/org/briarproject/android/blogs/FeedControllerImpl.java +++ b/briar-android/src/org/briarproject/android/blogs/FeedControllerImpl.java @@ -1,19 +1,12 @@ package org.briarproject.android.blogs; -import org.briarproject.android.api.AndroidNotificationManager; -import org.briarproject.android.controller.DbControllerImpl; +import org.briarproject.android.controller.handler.ResultExceptionHandler; import org.briarproject.android.controller.handler.ResultHandler; import org.briarproject.api.blogs.Blog; -import org.briarproject.api.blogs.BlogManager; -import org.briarproject.api.blogs.BlogPostHeader; import org.briarproject.api.db.DbException; -import org.briarproject.api.event.BlogPostAddedEvent; -import org.briarproject.api.event.Event; -import org.briarproject.api.event.EventBus; -import org.briarproject.api.event.EventListener; +import org.briarproject.api.db.NoSuchGroupException; +import org.briarproject.api.db.NoSuchMessageException; import org.briarproject.api.identity.Author; -import org.briarproject.api.identity.IdentityManager; -import org.briarproject.api.sync.GroupId; import java.util.ArrayList; import java.util.Collection; @@ -24,82 +17,56 @@ import javax.inject.Inject; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; -public class FeedControllerImpl extends DbControllerImpl - implements FeedController, EventListener { +public class FeedControllerImpl extends BaseControllerImpl + implements FeedController { private static final Logger LOG = Logger.getLogger(FeedControllerImpl.class.getName()); - @SuppressWarnings("WeakerAccess") - @Inject - AndroidNotificationManager notificationManager; - @Inject - protected EventBus eventBus; - - @Inject - protected volatile BlogManager blogManager; - @Inject - protected volatile IdentityManager identityManager; - - private volatile OnBlogPostAddedListener listener; - @Inject FeedControllerImpl() { } @Override - public void onResume() { + public void onStart() { + super.onStart(); notificationManager.blockAllBlogPostNotifications(); notificationManager.clearAllBlogPostNotifications(); - eventBus.addListener(this); } @Override - public void onPause() { + public void onStop() { + super.onStop(); notificationManager.unblockAllBlogPostNotifications(); - eventBus.removeListener(this); } @Override - public void eventOccurred(Event e) { - if (!(e instanceof BlogPostAddedEvent)) return; - - LOG.info("New blog post added"); - if (listener != null) { - BlogPostAddedEvent m = (BlogPostAddedEvent) e; - BlogPostHeader header = m.getHeader(); - addPost(m.getGroupId(), header); - } - } - - @Override - public void loadPosts( - final ResultHandler<Collection<BlogPostItem>> resultHandler) { - - LOG.info("Loading blog posts..."); + public void loadBlogPosts( + final ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) { + LOG.info("Loading all blog posts..."); runOnDbThread(new Runnable() { @Override public void run() { - Collection<BlogPostItem> posts = new ArrayList<>(); try { // load blog posts long now = System.currentTimeMillis(); + Collection<BlogPostItem> posts = new ArrayList<>(); for (Blog b : blogManager.getBlogs()) { - Collection<BlogPostHeader> header = - blogManager.getPostHeaders(b.getId()); - for (BlogPostHeader h : header) { - byte[] body = blogManager.getPostBody(h.getId()); - posts.add(new BlogPostItem(b.getId(), h, body)); + try { + posts.addAll(loadItems(b.getId())); + } catch (NoSuchGroupException | NoSuchMessageException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); } } long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) - LOG.info("Loading posts took " + duration + " ms"); - resultHandler.onResult(posts); + LOG.info("Loading all posts took " + duration + " ms"); + handler.onResult(posts); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - resultHandler.onResult(null); + handler.onException(e); } } }); @@ -114,8 +81,7 @@ public class FeedControllerImpl extends DbControllerImpl try { // load blog posts long now = System.currentTimeMillis(); - Author a = - identityManager.getLocalAuthors().iterator().next(); + Author a = identityManager.getLocalAuthor(); Blog b = blogManager.getPersonalBlog(a); long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) @@ -130,25 +96,4 @@ public class FeedControllerImpl extends DbControllerImpl }); } - @Override - public void setOnBlogPostAddedListener(OnBlogPostAddedListener listener) { - this.listener = listener; - } - - private void addPost(final GroupId groupId, final BlogPostHeader header) { - runOnDbThread(new Runnable() { - @Override - public void run() { - try { - byte[] body = blogManager.getPostBody(header.getId()); - BlogPostItem post = new BlogPostItem(groupId, header, body); - listener.onBlogPostAdded(post); - } catch (DbException ex) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, ex.toString(), ex); - } - } - }); - } - } diff --git a/briar-android/src/org/briarproject/android/blogs/FeedFragment.java b/briar-android/src/org/briarproject/android/blogs/FeedFragment.java index a83f9beb05..9e6f577487 100644 --- a/briar-android/src/org/briarproject/android/blogs/FeedFragment.java +++ b/briar-android/src/org/briarproject/android/blogs/FeedFragment.java @@ -17,11 +17,15 @@ import android.view.ViewGroup; import org.briarproject.R; import org.briarproject.android.ActivityComponent; +import org.briarproject.android.blogs.BaseController.OnBlogPostAddedListener; import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener; +import org.briarproject.android.controller.handler.UiResultExceptionHandler; import org.briarproject.android.controller.handler.UiResultHandler; import org.briarproject.android.fragment.BaseFragment; import org.briarproject.android.util.BriarRecyclerView; import org.briarproject.api.blogs.Blog; +import org.briarproject.api.blogs.BlogPostHeader; +import org.briarproject.api.db.DbException; import java.util.Collection; @@ -32,11 +36,10 @@ import static android.support.design.widget.Snackbar.LENGTH_LONG; import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation; import static org.briarproject.android.BriarActivity.GROUP_ID; import static org.briarproject.android.blogs.BlogActivity.BLOG_NAME; -import static org.briarproject.android.blogs.BlogActivity.IS_MY_BLOG; import static org.briarproject.android.blogs.BlogActivity.REQUEST_WRITE_POST; public class FeedFragment extends BaseFragment implements - OnBlogPostClickListener, FeedController.OnBlogPostAddedListener { + OnBlogPostClickListener, OnBlogPostAddedListener { public final static String TAG = FeedFragment.class.getName(); @@ -48,7 +51,7 @@ public class FeedFragment extends BaseFragment implements private BriarRecyclerView list; private Blog personalBlog = null; - static FeedFragment newInstance() { + public static FeedFragment newInstance() { FeedFragment f = new FeedFragment(); Bundle args = new Bundle(); @@ -95,6 +98,7 @@ public class FeedFragment extends BaseFragment implements @Override public void onStart() { super.onStart(); + feedController.onStart(); feedController.loadPersonalBlog( new UiResultHandler<Blog>(getActivity()) { @Override @@ -102,33 +106,30 @@ public class FeedFragment extends BaseFragment implements personalBlog = b; } }); - } - - @Override - public void onResume() { - super.onResume(); - list.startPeriodicUpdate(); - feedController.onResume(); - feedController.loadPosts( - new UiResultHandler<Collection<BlogPostItem>>(getActivity()) { + feedController.loadBlogPosts( + new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>( + getActivity()) { @Override public void onResultUi(Collection<BlogPostItem> posts) { - if (posts == null) { - // TODO show error? - } else if (posts.isEmpty()) { + if (posts.isEmpty()) { list.showData(); } else { adapter.addAll(posts); } } + @Override + public void onExceptionUi(DbException exception) { + // TODO + } }); + list.startPeriodicUpdate(); } @Override - public void onPause() { - super.onPause(); + public void onStop() { + super.onStop(); + feedController.onStop(); list.stopPeriodicUpdate(); - feedController.onPause(); // TODO save list position in database/preferences? } @@ -171,33 +172,30 @@ public class FeedFragment extends BaseFragment implements } @Override - public void onBlogPostAdded(final BlogPostItem post) { - listener.runOnUiThread(new Runnable() { - @Override - public void run() { - adapter.add(post); - showSnackBar(R.string.blogs_blog_post_received); - } - }); + public void onBlogPostAdded(BlogPostHeader header, final boolean local) { + feedController.loadBlogPost(header, + new UiResultExceptionHandler<BlogPostItem, DbException>( + getActivity()) { + @Override + public void onResultUi(BlogPostItem post) { + adapter.add(post); + if (local) { + showSnackBar(R.string.blogs_blog_post_created); + } else { + showSnackBar(R.string.blogs_blog_post_received); + } + } + @Override + public void onExceptionUi(DbException exception) { + // TODO: Decide how to handle errors in the UI + } + } + ); } @Override public void onBlogPostClick(BlogPostItem post) { - byte[] groupId = post.getGroupId().getBytes(); - String name = getString(R.string.blogs_personal_blog, - post.getAuthor().getName()); - boolean myBlog = personalBlog != null && - personalBlog.getId().equals(post.getGroupId()); - - Intent i = new Intent(getActivity(), BlogActivity.class); - i.putExtra(GROUP_ID, groupId); - i.putExtra(BLOG_NAME, name); - i.putExtra(IS_MY_BLOG, myBlog); - ActivityOptionsCompat options = - makeCustomAnimation(getActivity(), - android.R.anim.slide_in_left, - android.R.anim.slide_out_right); - startActivity(i, options.toBundle()); + // TODO Open detail view of post } @Override @@ -228,5 +226,4 @@ public class FeedFragment extends BaseFragment implements } s.show(); } - } diff --git a/briar-android/src/org/briarproject/android/blogs/MyBlogsFragment.java b/briar-android/src/org/briarproject/android/blogs/MyBlogsFragment.java deleted file mode 100644 index 97f33ebf4b..0000000000 --- a/briar-android/src/org/briarproject/android/blogs/MyBlogsFragment.java +++ /dev/null @@ -1,169 +0,0 @@ -package org.briarproject.android.blogs; - -import android.content.Intent; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.ActivityCompat; -import android.support.v4.app.ActivityOptionsCompat; -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.fragment.BaseFragment; -import org.briarproject.android.util.BriarRecyclerView; -import org.briarproject.api.blogs.Blog; -import org.briarproject.api.blogs.BlogManager; -import org.briarproject.api.blogs.BlogPostHeader; -import org.briarproject.api.db.DbException; -import org.briarproject.api.db.NoSuchGroupException; -import org.briarproject.api.identity.IdentityManager; -import org.briarproject.api.identity.LocalAuthor; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.logging.Logger; - -import javax.inject.Inject; - -import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation; -import static java.util.logging.Level.INFO; -import static java.util.logging.Level.WARNING; - -public class MyBlogsFragment extends BaseFragment { - - public final static String TAG = MyBlogsFragment.class.getName(); - - private static final Logger LOG = Logger.getLogger(TAG); - private BriarRecyclerView list; - private BlogListAdapter adapter; - - // Fields that are accessed from background threads must be volatile - @Inject - protected volatile IdentityManager identityManager; - @Inject - volatile BlogManager blogManager; - - @Inject - public MyBlogsFragment() { - } - - @Nullable - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - setHasOptionsMenu(true); - - adapter = new BlogListAdapter(getActivity()); - - list = (BriarRecyclerView) inflater - .inflate(R.layout.fragment_blogs_my, container, false); - list.setLayoutManager(new LinearLayoutManager(getActivity())); - list.setAdapter(adapter); - list.setEmptyText(getString(R.string.blogs_my_blogs_empty_state)); - - return list; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - listener.getActivityComponent().inject(this); - // Starting from here, we can use injected objects - } - - @Override - public void onResume() { - super.onResume(); - adapter.clear(); - list.showProgressBar(); - loadBlogs(); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.blogs_my_actions, menu); - super.onCreateOptionsMenu(menu, inflater); - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - // Handle presses on the action bar items - switch (item.getItemId()) { - case R.id.action_create_blog: - Intent intent = - new Intent(getContext(), CreateBlogActivity.class); - ActivityOptionsCompat options = - makeCustomAnimation(getActivity(), - android.R.anim.slide_in_left, - android.R.anim.slide_out_right); - ActivityCompat.startActivity(getActivity(), intent, - options.toBundle()); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - @Override - public String getUniqueTag() { - return TAG; - } - - @Override - public void injectFragment(ActivityComponent component) { - component.inject(this); - } - - private void loadBlogs() { - listener.runOnDbThread(new Runnable() { - @Override - public void run() { - try { - // load blogs - long now = System.currentTimeMillis(); - Collection<BlogListItem> blogs = new ArrayList<>(); - Collection<LocalAuthor> authors = - identityManager.getLocalAuthors(); - LocalAuthor a = authors.iterator().next(); - for (Blog b : blogManager.getBlogs(a)) { - try { - Collection<BlogPostHeader> headers = - blogManager.getPostHeaders(b.getId()); - blogs.add(new BlogListItem(b, headers, true)); - } catch (NoSuchGroupException e) { - // Continue - } - } - displayBlogs(blogs); - long duration = System.currentTimeMillis() - now; - if (LOG.isLoggable(INFO)) - LOG.info("Full blog load took " + duration + " ms"); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - } - } - }); - } - - private void displayBlogs(final Collection<BlogListItem> items) { - listener.runOnUiThread(new Runnable() { - @Override - public void run() { - if (items.size() == 0) { - list.showData(); - } else { - adapter.addAll(items); - } - } - }); - } - -} diff --git a/briar-android/src/org/briarproject/android/blogs/ReblogActivity.java b/briar-android/src/org/briarproject/android/blogs/ReblogActivity.java new file mode 100644 index 0000000000..9aade027de --- /dev/null +++ b/briar-android/src/org/briarproject/android/blogs/ReblogActivity.java @@ -0,0 +1,73 @@ +package org.briarproject.android.blogs; + +import android.content.Intent; +import android.os.Bundle; +import android.view.MenuItem; + +import org.briarproject.R; +import org.briarproject.android.ActivityComponent; +import org.briarproject.android.BriarActivity; +import org.briarproject.android.fragment.BaseFragment.BaseFragmentListener; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.MessageId; + +import static org.briarproject.android.blogs.BlogActivity.POST_ID; + +public class ReblogActivity extends BriarActivity implements + BaseFragmentListener { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + byte[] groupId = intent.getByteArrayExtra(GROUP_ID); + if (groupId == null) + throw new IllegalArgumentException("No group ID in intent"); + byte[] postId = intent.getByteArrayExtra(POST_ID); + if (postId == null) + throw new IllegalArgumentException("No post message ID in intent"); + + setContentView(R.layout.activity_fragment_container); + + if (savedInstanceState == null) { + ReblogFragment f = ReblogFragment + .newInstance(new GroupId(groupId), new MessageId(postId)); + getSupportFragmentManager() + .beginTransaction() + .add(R.id.fragmentContainer, f) + .commit(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + @Override + public void showLoadingScreen(boolean isBlocking, int stringId) { + // this is handled by the fragment + } + + @Override + public void hideLoadingScreen() { + // this is handled by the fragment + } + + @Override + public void onFragmentCreated(String tag) { + + } +} diff --git a/briar-android/src/org/briarproject/android/blogs/ReblogFragment.java b/briar-android/src/org/briarproject/android/blogs/ReblogFragment.java new file mode 100644 index 0000000000..367dd7041a --- /dev/null +++ b/briar-android/src/org/briarproject/android/blogs/ReblogFragment.java @@ -0,0 +1,196 @@ +package org.briarproject.android.blogs; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ProgressBar; +import android.widget.ScrollView; + +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.api.db.DbException; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.MessageId; + +import javax.inject.Inject; + +import static android.view.View.FOCUS_DOWN; +import static android.view.View.GONE; +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; +import static org.briarproject.android.BriarActivity.GROUP_ID; +import static org.briarproject.android.blogs.BlogActivity.POST_ID; + +public class ReblogFragment extends BaseFragment { + + public static final String TAG = ReblogFragment.class.getName(); + + + private BaseFragmentListener listener; + private ViewHolder ui; + private GroupId blogId; + private MessageId postId; + private BlogPostItem item; + + @Inject + FeedController feedController; + + static ReblogFragment newInstance(GroupId groupId, MessageId messageId) { + ReblogFragment f = new ReblogFragment(); + + Bundle args = new Bundle(); + args.putByteArray(GROUP_ID, groupId.getBytes()); + args.putByteArray(POST_ID, messageId.getBytes()); + f.setArguments(args); + + return f; + } + + @Override + public String getUniqueTag() { + return TAG; + } + + @Override + public void injectFragment(ActivityComponent component) { + component.inject(this); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + try { + listener = (BaseFragmentListener) context; + } catch (ClassCastException e) { + throw new ClassCastException( + "Using class must implement BaseFragmentListener"); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + setHasOptionsMenu(true); + + Bundle args = getArguments(); + blogId = new GroupId(args.getByteArray(GROUP_ID)); + postId = new MessageId(args.getByteArray(POST_ID)); + + View v = inflater.inflate(R.layout.fragment_reblog_dialog, container, + false); + ui = new ViewHolder(v); + showProgressBar(); + + return v; + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + listener.getActivityComponent().inject(this); + } + + @Override + public void onStart() { + super.onStart(); + + feedController.loadBlogPost(blogId, postId, + new UiResultExceptionHandler<BlogPostItem, DbException>( + getActivity()) { + @Override + public void onResultUi(BlogPostItem result) { + item = result; + bindViewHolder(); + } + @Override + public void onExceptionUi(DbException exception) { + // TODO + finish(); + } + }); + } + + private void bindViewHolder() { + if (item == null) return; + + ui.post.bindItem(item); + ui.post.hideReblogButton(); + + ui.publish.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + send(); + finish(); + } + }); + ui.publish.setEnabled(true); + hideProgressBar(); + ui.scrollView.post(new Runnable() { + @Override + public void run() { + //ui.scrollView.scrollTo(0, ui.scrollView.getBottom()); + ui.scrollView.fullScroll(FOCUS_DOWN); + } + }); + } + + private void send() { + String comment = getComment(); + feedController.repeatPost(item, comment, + new UiResultExceptionHandler<Void, DbException>(getActivity()) { + @Override + public void onResultUi(Void result) { + // do nothing, this fragment is gone already + } + @Override + public void onExceptionUi(DbException exception) { + // do nothing, this fragment is gone already + } + }); + } + + @Nullable + private String getComment() { + if (ui.input.getText().length() == 0) return null; + return ui.input.getText().toString(); + } + + private void showProgressBar() { + ui.progressBar.setVisibility(VISIBLE); + ui.post.setVisibility(GONE); + ui.input.setVisibility(GONE); + ui.publish.setVisibility(GONE); + } + + private void hideProgressBar() { + ui.progressBar.setVisibility(INVISIBLE); + ui.post.setVisibility(VISIBLE); + ui.input.setVisibility(VISIBLE); + ui.publish.setVisibility(VISIBLE); + } + + private static class ViewHolder { + private final ScrollView scrollView; + private final ProgressBar progressBar; + private final BlogPostViewHolder post; + private final EditText input; + private final Button publish; + + private ViewHolder(View v) { + scrollView = (ScrollView) v.findViewById(R.id.scrollView); + progressBar = (ProgressBar) v.findViewById(R.id.progressBar); + post = new BlogPostViewHolder(v.findViewById(R.id.postLayout)); + input = (EditText) v.findViewById(R.id.inputText); + publish = (Button) v.findViewById(R.id.publishButton); + } + } +} diff --git a/briar-android/src/org/briarproject/android/fragment/BaseFragment.java b/briar-android/src/org/briarproject/android/fragment/BaseFragment.java index 75d480bfbf..a2cf536461 100644 --- a/briar-android/src/org/briarproject/android/fragment/BaseFragment.java +++ b/briar-android/src/org/briarproject/android/fragment/BaseFragment.java @@ -39,6 +39,10 @@ public abstract class BaseFragment extends Fragment { listener.onFragmentCreated(getUniqueTag()); } + protected void finish() { + getActivity().supportFinishAfterTransition(); + } + public interface BaseFragmentListener { void showLoadingScreen(boolean isBlocking, int stringId); diff --git a/briar-android/src/org/briarproject/android/introduction/IntroductionActivity.java b/briar-android/src/org/briarproject/android/introduction/IntroductionActivity.java index f2227d8262..3c14628ebf 100644 --- a/briar-android/src/org/briarproject/android/introduction/IntroductionActivity.java +++ b/briar-android/src/org/briarproject/android/introduction/IntroductionActivity.java @@ -32,12 +32,12 @@ public class IntroductionActivity extends BriarActivity implements if (contactId == -1) throw new IllegalArgumentException("Wrong ContactId"); - setContentView(R.layout.activity_introduction); + setContentView(R.layout.activity_fragment_container); if (savedInstanceState == null) { getSupportFragmentManager() .beginTransaction() - .add(R.id.introductionContainer, + .add(R.id.fragmentContainer, ContactChooserFragment.newInstance()) .commit(); } @@ -109,7 +109,7 @@ public class IntroductionActivity extends BriarActivity implements android.R.anim.slide_in_left, android.R.anim.slide_out_right) .addSharedElement(view, "avatar") - .replace(R.id.introductionContainer, messageFragment, + .replace(R.id.fragmentContainer, messageFragment, ContactChooserFragment.TAG) .addToBackStack(null) .commit(); diff --git a/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java b/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java index 82fca3e36d..268b16b85e 100644 --- a/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java +++ b/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java @@ -343,12 +343,12 @@ public class ShowQrCodeFragment extends BaseEventFragment }); } - private void finish() { + @Override + protected void finish() { getActivity().getSupportFragmentManager().popBackStack(); } private class BluetoothStateReceiver extends BroadcastReceiver { - @Override public void onReceive(Context ctx, Intent intent) { int state = intent.getIntExtra(EXTRA_STATE, 0); diff --git a/briar-android/src/org/briarproject/android/util/AuthorView.java b/briar-android/src/org/briarproject/android/util/AuthorView.java index 51fb7debe2..d84fb7f1bd 100644 --- a/briar-android/src/org/briarproject/android/util/AuthorView.java +++ b/briar-android/src/org/briarproject/android/util/AuthorView.java @@ -1,29 +1,44 @@ package org.briarproject.android.util; import android.content.Context; +import android.content.Intent; +import android.content.res.TypedArray; import android.graphics.Typeface; +import android.support.annotation.Nullable; +import android.support.v4.app.ActivityOptionsCompat; +import android.support.v4.content.ContextCompat; import android.util.AttributeSet; +import android.util.TypedValue; import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import org.briarproject.R; +import org.briarproject.android.blogs.BlogActivity; import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author.Status; +import org.briarproject.api.sync.GroupId; import de.hdodenhof.circleimageview.CircleImageView; import im.delight.android.identicons.IdenticonDrawable; +import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation; +import static android.util.TypedValue.COMPLEX_UNIT_PX; +import static org.briarproject.android.BriarActivity.GROUP_ID; import static org.briarproject.api.identity.Author.Status.OURSELVES; public class AuthorView extends RelativeLayout { private final CircleImageView avatar; + private final ImageView avatarIcon; private final TextView authorName; private final TextView date; private final TrustIndicatorView trustIndicator; - public AuthorView(Context context, AttributeSet attrs) { + public AuthorView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); LayoutInflater inflater = (LayoutInflater) context @@ -32,9 +47,16 @@ public class AuthorView extends RelativeLayout { .inflate(R.layout.author_view, this, true); avatar = (CircleImageView) findViewById(R.id.avatar); + avatarIcon = (ImageView) findViewById(R.id.avatarIcon); authorName = (TextView) findViewById(R.id.authorName); date = (TextView) findViewById(R.id.dateView); trustIndicator = (TrustIndicatorView) findViewById(R.id.trustIndicator); + + TypedArray attributes = + context.obtainStyledAttributes(attrs, R.styleable.AuthorView); + int persona = attributes.getInteger(R.styleable.AuthorView_persona, 0); + setPersona(persona); + attributes.recycle(); } public AuthorView(Context context) { @@ -45,6 +67,9 @@ public class AuthorView extends RelativeLayout { authorName.setText(author.getName()); IdenticonDrawable d = new IdenticonDrawable(author.getId().getBytes()); avatar.setImageDrawable(d); + + invalidate(); + requestLayout(); } public void setAuthorStatus(Status status) { @@ -59,6 +84,60 @@ public class AuthorView extends RelativeLayout { public void setDate(long date) { this.date.setText(AndroidUtils.formatDate(getContext(), date)); + + invalidate(); + requestLayout(); + } + + public void setBlogLink(final GroupId groupId) { + setClickable(true); + TypedValue outValue = new TypedValue(); + getContext().getTheme() + .resolveAttribute(android.R.attr.selectableItemBackground, + outValue, true); + setBackgroundResource(outValue.resourceId); + setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent i = new Intent(getContext(), BlogActivity.class); + i.putExtra(GROUP_ID, groupId.getBytes()); + ActivityOptionsCompat options = + makeCustomAnimation(getContext(), + android.R.anim.slide_in_left, + android.R.anim.slide_out_right); + Intent[] intents = {i}; + ContextCompat + .startActivities(getContext(), intents, + options.toBundle()); + } + }); + } + + public void unsetBlogLink() { + setClickable(false); + setBackgroundResource(android.R.color.transparent); + setOnClickListener(null); + } + + private void setPersona(int persona) { + switch (persona) { + // reblogger + case 1: + avatarIcon.setVisibility(VISIBLE); + break; + // commenter + case 2: + ViewGroup.LayoutParams params = avatar.getLayoutParams(); + int size = getResources().getDimensionPixelSize( + R.dimen.blogs_avatar_comment_size); + params.height = size; + params.width = size; + avatar.setLayoutParams(params); + float textSize = getResources() + .getDimensionPixelSize(R.dimen.text_size_tiny); + authorName.setTextSize(COMPLEX_UNIT_PX, textSize); + break; + } } } diff --git a/briar-android/src/org/briarproject/android/util/TrustIndicatorView.java b/briar-android/src/org/briarproject/android/util/TrustIndicatorView.java index d1a663e63f..97d8e77636 100644 --- a/briar-android/src/org/briarproject/android/util/TrustIndicatorView.java +++ b/briar-android/src/org/briarproject/android/util/TrustIndicatorView.java @@ -45,6 +45,9 @@ public class TrustIndicatorView extends ImageView { } setImageDrawable(ContextCompat.getDrawable(getContext(), res)); setVisibility(VISIBLE); + + invalidate(); + requestLayout(); } } diff --git a/briar-api/src/org/briarproject/api/blogs/BlogManager.java b/briar-api/src/org/briarproject/api/blogs/BlogManager.java index ac8ea516b4..2637779d46 100644 --- a/briar-api/src/org/briarproject/api/blogs/BlogManager.java +++ b/briar-api/src/org/briarproject/api/blogs/BlogManager.java @@ -56,7 +56,7 @@ public interface BlogManager { BlogPostHeader getPostHeader(GroupId g, MessageId m) throws DbException; /** Returns the body of the blog post with the given ID. */ - byte[] getPostBody(MessageId m) throws DbException; + String getPostBody(MessageId m) throws DbException; /** Returns the headers of all posts in the given blog. */ Collection<BlogPostHeader> getPostHeaders(GroupId g) throws DbException; diff --git a/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java index 2720910e0c..92f7e9c8a0 100644 --- a/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java +++ b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java @@ -516,7 +516,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, } @Override - public byte[] getPostBody(MessageId m) throws DbException { + public String getPostBody(MessageId m) throws DbException { try { BdfList message = clientHelper.getMessageAsList(m); if (message == null) throw new DbException(); @@ -526,15 +526,14 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager, } } - // TODO directly return String (#598) - private byte[] getPostBody(BdfList message) throws FormatException { + private String getPostBody(BdfList message) throws FormatException { MessageType type = MessageType.valueOf(message.getLong(0).intValue()); if (type == POST) { // type, body, signature - return StringUtils.toUtf8(message.getString(1)); + return message.getString(1); } else if (type == WRAPPED_POST) { // type, p_group descriptor, p_timestamp, p_content, p_signature - return StringUtils.toUtf8(message.getString(3)); + return message.getString(3); } else { throw new FormatException(); } -- GitLab