Merge branch '496-implement-ui-for-reblogging-and-blog-comments' into 'master'

UI for reblogging and blog comments

This MR wraps all blog posts inside a `CardView` and adds a reblog button to blog posts. When this button is pressed, a new activity opens that shows the blog post again and allows the user to reblog it into their own blog. An optional comment can be added.

Also a new compound view `AuthorView` is introduced to display authors and their trust level in a consistent way.

The `BlogController` has been refactored, so the `FeedController` can share most code through an abstract base class.

![reblog2](/uploads/ce4764851c4cbe39684c95a3b427e3a2/reblog2.gif)

Closes #496, #437 

See merge request !300
This commit is contained in:
akwizgran
2016-09-02 11:16:28 +00:00
47 changed files with 1501 additions and 861 deletions

View File

@@ -37,6 +37,34 @@
<JavaCodeStyleSettings>
<option name="ANNOTATION_PARAMETER_WRAP" value="1" />
</JavaCodeStyleSettings>
<Objective-C-extensions>
<option name="GENERATE_INSTANCE_VARIABLES_FOR_PROPERTIES" value="ASK" />
<option name="RELEASE_STYLE" value="IVAR" />
<option name="TYPE_QUALIFIERS_PLACEMENT" value="BEFORE" />
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" />
<pair source="c" header="h" />
</extensions>
</Objective-C-extensions>
<XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>

View File

@@ -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();
}

View File

@@ -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"

View File

@@ -27,6 +27,7 @@ dependencies {
exclude module: 'support-v4'
exclude module: 'recyclerview-v7'
}
compile "com.android.support:cardview-v7:$supportVersion"
compile('ch.acra:acra:4.8.5') {
exclude module: 'support-v4'
exclude module: 'support-annotations'
@@ -61,6 +62,7 @@ dependencyVerification {
'com.android.support:animated-vector-drawable:06d1963b85aa917099d7757e6a7b3e4dc06889413dc747f625ae8683606db3a1',
'com.android.support:support-vector-drawable:799bafe4c3de812386f0b291f744d5d6876452722dd40189b9ab87dbbf594ea1',
'com.android.support:recyclerview-v7:44040a888e23e0c93162a3377cfe06751080e3c22d369ab0d4301ef60d63b0fe',
'com.android.support:cardview-v7:4595f1c4a28cfa083b6c0920ad4d49e1c2ca4b8302a955e548f68eb63b74931b',
]
}

View File

@@ -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>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:alpha="0.54"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,5.9c1.16,0 2.1,0.94 2.1,2.1s-0.94,2.1 -2.1,2.1S9.9,9.16 9.9,8s0.94,-2.1 2.1,-2.1m0,9c2.97,0 6.1,1.46 6.1,2.1v1.1L5.9,18.1L5.9,17c0,-0.64 3.13,-2.1 6.1,-2.1M12,4C9.79,4 8,5.79 8,8s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,13c-2.67,0 -8,1.34 -8,4v3h16v-3c0,-2.66 -5.33,-4 -8,-4z"/>
</vector>

View File

@@ -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"/>

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="@layout/list_item_blog_post">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatar"
style="@style/BriarAvatar"
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"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/avatar"
android:layout_toEndOf="@+id/avatar"
android:layout_toRightOf="@+id/avatar"
android:textColor="@color/briar_text_primary"
android:textSize="@dimen/text_size_small"
tools:text="Author Name"/>
<org.briarproject.android.util.TrustIndicatorView
android:id="@+id/trustIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/authorName"
android:layout_alignTop="@+id/authorName"
android:layout_marginLeft="@dimen/margin_small"
android:layout_toRightOf="@id/authorName"
android:scaleType="center"
tools:src="@drawable/trust_indicator_verified"/>
<TextView
android:id="@+id/dateView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/authorName"
android:layout_toEndOf="@+id/avatar"
android:layout_toRightOf="@+id/avatar"
android:gravity="bottom"
android:textColor="@color/briar_text_secondary"
android:textSize="@dimen/text_size_tiny"
tools:text="yesterday"/>
</merge>

View File

@@ -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">
<include
android:id="@+id/postLayout"
layout="@layout/list_item_blog_post"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"/>
<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>

View File

@@ -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>

View File

@@ -1,118 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
<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"
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin"
android:layout_marginTop="@dimen/listitem_vertical_margin"
android:background="?attr/selectableItemBackground">
android:layout_height="wrap_content">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatar"
style="@style/BriarAvatar"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginBottom="@dimen/margin_medium"
android:layout_marginRight="@dimen/margin_medium"
tools:src="@drawable/ic_launcher"/>
<TextView
android:id="@+id/authorName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/avatar"
android:layout_toEndOf="@+id/avatar"
android:layout_toRightOf="@+id/avatar"
android:textColor="@color/briar_text_primary"
android:textSize="@dimen/text_size_tiny"
tools:text="Author Name"/>
<TextView
android:id="@+id/dateView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/avatar"
android:layout_below="@+id/authorName"
android:layout_toEndOf="@+id/avatar"
android:layout_toRightOf="@+id/avatar"
android:gravity="bottom"
android:textColor="@color/briar_text_primary"
android:textSize="@dimen/text_size_tiny"
tools:text="yesterday"/>
<TextView
android:id="@+id/newView"
style="@style/BriarTag"
android:layout_alignBottom="@+id/dateView"
android:layout_marginLeft="@dimen/margin_small"
android:layout_toRightOf="@+id/dateView"
android:text="@string/tag_new"
android:visibility="gone"/>
<org.briarproject.android.util.TrustIndicatorView
android:id="@+id/trustIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/authorName"
android:layout_alignTop="@+id/authorName"
android:layout_marginLeft="@dimen/margin_small"
android:layout_toRightOf="@+id/authorName"
android:scaleType="center"
tools:src="@drawable/trust_indicator_verified"/>
<ImageView
android:id="@+id/chatView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@+id/commentView"
android:padding="@dimen/margin_small"
android:src="@drawable/ic_chat"
android:visibility="gone"/>
<ImageView
android:id="@+id/commentView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="@dimen/listitem_horizontal_margin"
android:padding="@dimen/margin_small"
android:src="@drawable/ic_repeat"
android:visibility="gone"/>
<TextView
android:id="@+id/titleView"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/avatar"
android:layout_marginBottom="@dimen/margin_medium"
android:layout_marginEnd="@dimen/listitem_horizontal_margin"
android:layout_marginRight="@dimen/listitem_horizontal_margin"
android:ellipsize="end"
android:maxLines="3"
android:textColor="@color/briar_text_primary"
android:textSize="@dimen/text_size_large"
android:visibility="gone"
tools:text="This is a blog post title which can also be longer"/>
android:orientation="vertical">
<TextView
android:id="@+id/bodyView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/titleView"
android:layout_marginEnd="@dimen/margin_medium"
android:layout_marginRight="@dimen/listitem_horizontal_margin"
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. This one is not short, but it is also not too long."/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/listitem_vertical_margin">
<View
style="@style/Divider.ForumList"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/bodyView"
android:layout_marginTop="@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"/>
</RelativeLayout>
<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:orientation="vertical">
<include
layout="@layout/list_item_blog_comment"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -124,6 +124,11 @@
<item name="tabTextColor">@color/briar_text_primary_inverse</item>
</style>
<style name="BriarCard" parent="CardView">
<item name="cardUseCompatPadding">true</item>
<item name="android:layout_margin">@dimen/margin_small</item>
</style>
<!-- This fixes the missing TextAppearance.Design.Counter.Overflow style -->
<style name="BriarTextCounter.Overflow" parent="TextAppearance.Design.Counter">
<item name="android:textColor">@color/briar_button_negative</item>

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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));

View File

@@ -0,0 +1,41 @@
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 {
@UiThread
void onStart();
@UiThread
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);
}
}

View File

@@ -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;
}
}

View File

@@ -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
@@ -67,12 +67,11 @@ public class BlogActivity extends BriarActivity implements BlogPostListener,
groupId = new GroupId(b);
blogController.setGroupId(groupId);
// Name of the Blog from Intent
// Name of the blog
blogName = i.getStringExtra(BLOG_NAME);
if (blogName != null) setTitle(blogName);
// Is this our blog and was it just created?
myBlog = i.getBooleanExtra(IS_MY_BLOG, false);
// Was this blog just created?
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

View File

@@ -0,0 +1,65 @@
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 static final BlogCommentComparator COMPARATOR =
new BlogCommentComparator();
private final BlogPostHeader postHeader;
private final List<BlogCommentHeader> comments = new ArrayList<>();
BlogCommentItem(BlogCommentHeader header) {
super(header, null);
postHeader = collectComments(header);
Collections.sort(comments, COMPARATOR);
}
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(BlogCommentHeader h1, BlogCommentHeader h2) {
// re-use same comparator used for blog posts, but reverse it
return BlogPostItem.compare(h2, h1);
}
}
}

View File

@@ -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);
}
}

View File

@@ -1,107 +1,76 @@
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 onActivityCreate() {
if (activity instanceof OnBlogPostAddedListener) {
listener = (OnBlogPostAddedListener) activity;
} else {
throw new IllegalStateException(
"An activity that injects the BlogController must " +
"implement the OnBlogPostAddedListener");
}
}
@Override
public void onActivityResume() {
super.onStart(); // TODO: Should be called when activity starts. #609
notificationManager.blockNotification(groupId);
notificationManager.clearBlogPostNotification(groupId);
}
@Override
public void onActivityPause() {
super.onStop(); // TODO: Should be called when activity stops. #609
notificationManager.unblockNotification(groupId);
}
@Override
public void onActivityDestroy() {
}
@Override
public void setGroupId(GroupId g) {
groupId = g;
}
@Override
public void onActivityCreate() {
if (activity instanceof BlogPostListener) {
listener = (BlogPostListener) activity;
} else {
throw new IllegalStateException(
"An activity that injects the BlogController must " +
"implement the BlogPostListener");
}
}
@Override
public void onActivityResume() {
notificationManager.blockNotification(groupId);
notificationManager.clearBlogPostNotification(groupId);
eventBus.addListener(this);
}
@Override
public void onActivityPause() {
notificationManager.unblockNotification(groupId);
eventBus.removeListener(this);
}
@Override
public void onActivityDestroy() {
}
@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);
}
}
});
}
@Override
public void loadBlogPost(final BlogPostHeader header,
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);
}
}
});
loadBlogPosts(groupId, handler);
}
@Override
public void loadBlogPost(final MessageId m,
final ResultExceptionHandler<BlogPostItem, 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;
}
loadBlogPost(groupId, m, handler);
}
@Override
public void isMyBlog(
final ResultExceptionHandler<Boolean, DbException> handler) {
if (groupId == null) throw new IllegalStateException();
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

View File

@@ -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);

View File

@@ -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);

View File

@@ -6,25 +6,15 @@ import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.android.util.AndroidUtils;
import org.briarproject.android.util.TrustIndicatorView;
import org.briarproject.api.identity.Author;
import org.briarproject.util.StringUtils;
import java.util.Collection;
import de.hdodenhof.circleimageview.CircleImageView;
import im.delight.android.identicons.IdenticonDrawable;
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);
@@ -60,7 +50,6 @@ class BlogPostAdapter extends
return a.getId().equals(b.getId());
}
});
private final Context ctx;
private final OnBlogPostClickListener listener;
@@ -70,34 +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 author = post.getAuthor();
IdenticonDrawable d = new IdenticonDrawable(author.getId().getBytes());
ui.avatar.setImageDrawable(d);
ui.author.setText(author.getName());
ui.trust.setTrustLevel(post.getAuthorStatus());
// date
ui.date.setText(AndroidUtils.formatDate(ctx, 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
@@ -129,27 +102,6 @@ class BlogPostAdapter extends
return posts.size() == 0;
}
static class BlogPostHolder extends RecyclerView.ViewHolder {
private final ViewGroup layout;
private final CircleImageView avatar;
private final TextView author;
private final TrustIndicatorView trust;
private final TextView date;
private final TextView body;
BlogPostHolder(View v) {
super(v);
layout = (ViewGroup) v;
avatar = (CircleImageView) v.findViewById(R.id.avatar);
author = (TextView) v.findViewById(R.id.authorName);
trust = (TrustIndicatorView) v.findViewById(R.id.trustIndicator);
date = (TextView) v.findViewById(R.id.dateView);
body = (TextView) v.findViewById(R.id.bodyView);
}
}
interface OnBlogPostClickListener {
void onBlogPostClick(BlogPostItem post);
}

View File

@@ -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);
}
}

View File

@@ -2,6 +2,7 @@ package org.briarproject.android.blogs;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import org.briarproject.api.blogs.BlogPostHeader;
import org.briarproject.api.identity.Author;
@@ -9,19 +10,17 @@ import org.briarproject.api.identity.Author.Status;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
// This class is not thread-safe
@UiThread
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 +28,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 +43,7 @@ class BlogPostItem implements Comparable<BlogPostItem> {
return header.getAuthorStatus();
}
public byte[] getBody() {
public String getBody() {
return body;
}
@@ -56,15 +51,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;

View File

@@ -0,0 +1,157 @@
package org.briarproject.android.blogs;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.UiThread;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.view.ViewCompat;
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 org.briarproject.api.sync.MessageId;
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
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;
@UiThread
class BlogPostViewHolder extends RecyclerView.ViewHolder {
private final Context ctx;
private final ViewGroup layout;
private final AuthorView reblogger;
private final AuthorView author;
private final ImageView reblogButton;
private final TextView body;
private final ViewGroup commentContainer;
private OnBlogPostClickListener listener;
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 setTransitionName(MessageId id) {
ViewCompat.setTransitionName(layout, getTransitionName(id));
}
private String getTransitionName(MessageId id) {
return "blogPost" + id.hashCode();
}
void bindItem(final BlogPostItem item) {
setTransitionName(item.getId());
layout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onBlogPostClick(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 =
makeSceneTransitionAnimation((Activity) ctx, layout,
getTransitionName(item.getId()));
ActivityCompat
.startActivity((Activity) ctx, i, options.toBundle());
}
});
// comments
commentContainer.removeAllViews();
if (item instanceof BlogCommentItem) {
onBindComment((BlogCommentItem) item);
} else {
reblogger.setVisibility(GONE);
}
}
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);
}
}
}

View File

@@ -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];
}
}
}

View File

@@ -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,

View File

@@ -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);
}
}

View File

@@ -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);
}
}
});
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
});
}
}

View File

@@ -0,0 +1,91 @@
package org.briarproject.android.blogs;
import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.transition.Fade;
import android.transition.Transition;
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);
if (Build.VERSION.SDK_INT >= 21) {
setTransition();
}
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) {
}
@TargetApi(21)
private void setTransition() {
Transition fade = new Fade();
fade.excludeTarget(android.R.id.statusBarBackground, true);
fade.excludeTarget(R.id.action_bar_container, true);
fade.excludeTarget(android.R.id.navigationBarBackground, true);
getWindow().setExitTransition(fade);
getWindow().setEnterTransition(fade);
}
}

View File

@@ -0,0 +1,198 @@
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, container,
false);
ui = new ViewHolder(v);
ui.post.setTransitionName(postId);
showProgressBar();
return v;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
listener.getActivityComponent().inject(this);
}
@Override
public void onStart() {
super.onStart();
// TODO: Load blog post when fragment is created. #631
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;
hideProgressBar();
ui.post.bindItem(item);
ui.post.hideReblogButton();
ui.publish.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
send();
finish();
}
});
ui.publish.setEnabled(true);
ui.scrollView.post(new Runnable() {
@Override
public void run() {
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.input.setVisibility(GONE);
ui.publish.setVisibility(GONE);
}
private void hideProgressBar() {
ui.progressBar.setVisibility(INVISIBLE);
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);
}
}
}

View File

@@ -3,6 +3,7 @@ package org.briarproject.android.fragment;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.v4.app.Fragment;
import org.briarproject.android.ActivityComponent;
@@ -39,18 +40,27 @@ public abstract class BaseFragment extends Fragment {
listener.onFragmentCreated(getUniqueTag());
}
@UiThread
protected void finish() {
getActivity().supportFinishAfterTransition();
}
public interface BaseFragmentListener {
@UiThread
void showLoadingScreen(boolean isBlocking, int stringId);
@UiThread
void hideLoadingScreen();
void runOnUiThread(Runnable runnable);
void runOnDbThread(Runnable runnable);
@UiThread
ActivityComponent getActivityComponent();
@UiThread
void onFragmentCreated(String tag);
}
}

View File

@@ -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();

View File

@@ -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);

View File

@@ -0,0 +1,141 @@
package org.briarproject.android.util;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
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.content.Context.LAYOUT_INFLATER_SERVICE;
import static android.graphics.Typeface.BOLD;
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, @Nullable AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(LAYOUT_INFLATER_SERVICE);
inflater.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) {
this(context, null);
}
public void setAuthor(Author author) {
authorName.setText(author.getName());
IdenticonDrawable d = new IdenticonDrawable(author.getId().getBytes());
avatar.setImageDrawable(d);
invalidate();
requestLayout();
}
public void setAuthorStatus(Status status) {
trustIndicator.setTrustLevel(status);
if (status == OURSELVES) {
authorName.setTypeface(authorName.getTypeface(), BOLD);
}
invalidate();
requestLayout();
}
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;
}
}
}

View File

@@ -26,11 +26,6 @@ public class TrustIndicatorView extends ImageView {
}
public void setTrustLevel(Status status) {
if (status == OURSELVES) {
setVisibility(GONE);
return;
}
int res;
switch (status) {
case ANONYMOUS:
@@ -42,11 +37,17 @@ public class TrustIndicatorView extends ImageView {
case VERIFIED:
res = R.drawable.trust_indicator_verified;
break;
case OURSELVES:
res = R.drawable.ic_our_identity_black;
break;
default:
res = R.drawable.trust_indicator_unknown;
}
setImageDrawable(ContextCompat.getDrawable(getContext(), res));
setVisibility(VISIBLE);
invalidate();
requestLayout();
}
}

View File

@@ -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;

View File

@@ -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();
}