diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml
index 0677e45dbda840b74b6441c24eba9f9e52693215..2feaa4e95fbea9bbb14af009cc80761f3beb8141 100644
--- a/.idea/codeStyleSettings.xml
+++ b/.idea/codeStyleSettings.xml
@@ -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>
diff --git a/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java b/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java
index e2893714dacc1bce8ab50b76684cfd9f2fd1f49d..3bfaae9a2a9ef99ad10490611f0a87dd0e7d81f4 100644
--- a/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java
+++ b/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java
@@ -32,7 +32,6 @@ import org.briarproject.lifecycle.LifecycleModule;
 import org.briarproject.properties.PropertiesModule;
 import org.briarproject.sync.SyncModule;
 import org.briarproject.transport.TransportModule;
-import org.briarproject.util.StringUtils;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -60,7 +59,6 @@ import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
 import static org.briarproject.api.sync.ValidationManager.State.INVALID;
 import static org.briarproject.api.sync.ValidationManager.State.PENDING;
 import static org.briarproject.api.sync.ValidationManager.State.VALID;
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -193,8 +191,7 @@ public class BlogManagerTest {
 		assertEquals(1, headers0.size());
 
 		// check that body is there
-		assertArrayEquals(StringUtils.toUtf8(body),
-				blogManager0.getPostBody(p.getMessage().getId()));
+		assertEquals(body, blogManager0.getPostBody(p.getMessage().getId()));
 
 		// make sure that blog0 at author1 doesn't have the post yet
 		Collection<BlogPostHeader> headers1 =
@@ -211,8 +208,7 @@ public class BlogManagerTest {
 		assertEquals(POST, headers1.iterator().next().getType());
 
 		// check that body is there
-		assertArrayEquals(StringUtils.toUtf8(body),
-				blogManager1.getPostBody(p.getMessage().getId()));
+		assertEquals(body, blogManager1.getPostBody(p.getMessage().getId()));
 
 		stopLifecycles();
 	}
@@ -334,8 +330,7 @@ public class BlogManagerTest {
 		assertEquals(author0, h.getParent().getAuthor());
 
 		// ensure that body can be retrieved from wrapped post
-		assertArrayEquals(StringUtils.toUtf8(body),
-				blogManager0.getPostBody(h.getParentId()));
+		assertEquals(body, blogManager0.getPostBody(h.getParentId()));
 
 		// 1 has only their own comment in their blog
 		headers1 = blogManager1.getPostHeaders(blog1.getId());
@@ -375,6 +370,13 @@ public class BlogManagerTest {
 		Collection<BlogPostHeader> headers1 =
 				blogManager1.getPostHeaders(blog0.getId());
 		assertEquals(2, headers1.size());
+		for (BlogPostHeader h : headers1) {
+			if (h.getType() == POST) {
+				assertEquals(body, blogManager1.getPostBody(h.getId()));
+			} else {
+				assertEquals(comment, ((BlogCommentHeader)h).getComment());
+			}
+		}
 
 		stopLifecycles();
 	}
diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml
index 0ce2865ecdb281bc5989c247b7befa515c45e40b..5da35cdf462510dd5206de26c2692a3ed516497d 100644
--- a/briar-android/AndroidManifest.xml
+++ b/briar-android/AndroidManifest.xml
@@ -211,6 +211,17 @@
 				/>
 		</activity>
 
+		<activity
+			android:name=".android.blogs.ReblogActivity"
+			android:label="@string/blogs_reblog_button"
+			android:parentActivityName=".android.blogs.BlogActivity"
+			android:windowSoftInputMode="stateHidden">
+			<meta-data
+				android:name="android.support.PARENT_ACTIVITY"
+				android:value=".android.blogs.BlogActivity"
+				/>
+		</activity>
+
 		<activity
 			android:name=".android.blogs.RssFeedImportActivity"
 			android:label="@string/blogs_rss_feeds_import"
diff --git a/briar-android/build.gradle b/briar-android/build.gradle
index 8e75abfe40a67660b43a696458d6bf3081be7bbd..adea13909d84b5fb0cb8f69d979fdd6e4315149c 100644
--- a/briar-android/build.gradle
+++ b/briar-android/build.gradle
@@ -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',
 	]
 }
 
diff --git a/briar-android/res/drawable/bubble_white.xml b/briar-android/res/drawable/bubble_white.xml
new file mode 100644
index 0000000000000000000000000000000000000000..21d0e8e15eb5e066cf275cc8ee087e98a5f34e74
--- /dev/null
+++ b/briar-android/res/drawable/bubble_white.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape
+	xmlns:android="http://schemas.android.com/apk/res/android"
+	android:shape="rectangle">
+
+	<corners
+		android:radius="@dimen/unread_bubble_size"/>
+
+	<solid
+		android:color="@color/briar_text_primary_inverse"/>
+
+	<stroke
+		android:color="@color/briar_text_primary"
+	    android:width="1dp"/>
+
+</shape>
+
diff --git a/briar-android/res/drawable/ic_our_identity_black.xml b/briar-android/res/drawable/ic_our_identity_black.xml
new file mode 100644
index 0000000000000000000000000000000000000000..af997ec0c067ed1f944219241cf447136b0ce5e9
--- /dev/null
+++ b/briar-android/res/drawable/ic_our_identity_black.xml
@@ -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>
diff --git a/briar-android/res/layout/activity_introduction.xml b/briar-android/res/layout/activity_fragment_container.xml
similarity index 82%
rename from briar-android/res/layout/activity_introduction.xml
rename to briar-android/res/layout/activity_fragment_container.xml
index f351897d0f7b281b42f63847603b676d7f5bf7d2..e6c20760fb5078de5def86eec38402fc2db9042a 100644
--- a/briar-android/res/layout/activity_introduction.xml
+++ b/briar-android/res/layout/activity_fragment_container.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <FrameLayout
-	android:id="@+id/introductionContainer"
+	android:id="@+id/fragmentContainer"
 	xmlns:android="http://schemas.android.com/apk/res/android"
 	android:layout_width="match_parent"
 	android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/briar-android/res/layout/author_view.xml b/briar-android/res/layout/author_view.xml
new file mode 100644
index 0000000000000000000000000000000000000000..405376fac9b46eeec2780eddbf3e44d9a356dc9c
--- /dev/null
+++ b/briar-android/res/layout/author_view.xml
@@ -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>
diff --git a/briar-android/res/layout/fragment_reblog.xml b/briar-android/res/layout/fragment_reblog.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b17143b35193564ad3b2c503e15f83f0d6bb0a9e
--- /dev/null
+++ b/briar-android/res/layout/fragment_reblog.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView
+	android:id="@+id/scrollView"
+	xmlns:android="http://schemas.android.com/apk/res/android"
+	android:layout_width="match_parent"
+	android:layout_height="wrap_content"
+	android:background="@color/window_background">
+
+	<RelativeLayout
+		android:layout_width="match_parent"
+		android:layout_height="wrap_content"
+		android:padding="@dimen/margin_small">
+
+		<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>
diff --git a/briar-android/res/layout/list_item_blog_comment.xml b/briar-android/res/layout/list_item_blog_comment.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bdbe42842d003db9c92fc6e1a8515366da1957ff
--- /dev/null
+++ b/briar-android/res/layout/list_item_blog_comment.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+	xmlns:android="http://schemas.android.com/apk/res/android"
+	xmlns:app="http://schemas.android.com/apk/res-auto"
+	xmlns:tools="http://schemas.android.com/tools"
+	android:layout_width="match_parent"
+	android:layout_height="wrap_content"
+	tools:showIn="@layout/list_item_blog_post">
+
+	<View
+		android:id="@+id/inputDivider"
+		style="@style/Divider.Horizontal"/>
+
+	<org.briarproject.android.util.AuthorView
+		android:id="@+id/authorView"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_alignParentLeft="true"
+		android:layout_alignParentTop="true"
+		android:padding="@dimen/listitem_vertical_margin"
+		app:persona="commenter"/>
+
+	<TextView
+		android:id="@+id/bodyView"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_below="@+id/authorView"
+		android:paddingBottom="@dimen/listitem_vertical_margin"
+		android:paddingLeft="@dimen/listitem_vertical_margin"
+		android:paddingRight="@dimen/listitem_vertical_margin"
+		android:textColor="@color/briar_text_secondary"
+		android:textIsSelectable="true"
+		android:textSize="@dimen/text_size_small"
+		tools:text="This is a comment that appears below a blog post. Usually, it is expected to be rather short. Not much longer than this one."/>
+
+</RelativeLayout>
diff --git a/briar-android/res/layout/list_item_blog_post.xml b/briar-android/res/layout/list_item_blog_post.xml
index 0f7e1d53294356d48b0c7b3fd96164fee6e64b82..4697c461bf8e22f8fdb1832662d565ab7294309f 100644
--- a/briar-android/res/layout/list_item_blog_post.xml
+++ b/briar-android/res/layout/list_item_blog_post.xml
@@ -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"
+	<LinearLayout
+		android:layout_width="match_parent"
 		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"/>
+		android:orientation="vertical">
 
-	<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"/>
+		<RelativeLayout
+			android:layout_width="match_parent"
+			android:layout_height="wrap_content"
+			android:padding="@dimen/listitem_vertical_margin">
 
-	<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.AuthorView
+				android:id="@+id/rebloggerView"
+				android:layout_width="wrap_content"
+				android:layout_height="wrap_content"
+				android:layout_alignParentLeft="true"
+				android:layout_alignParentTop="true"
+				android:layout_marginBottom="@dimen/listitem_horizontal_margin"
+				android:layout_toLeftOf="@+id/commentView"
+				app:persona="reblogger"/>
 
-	<org.briarproject.android.util.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"/>
+			<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/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_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"/>
 
-	<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/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."/>
 
-	<TextView
-		android:id="@+id/titleView"
-		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"/>
+		</RelativeLayout>
 
-	<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."/>
+		<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"/>
 
-	<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"/>
+		</LinearLayout>
 
-</RelativeLayout>
+	</LinearLayout>
 
+</android.support.v7.widget.CardView>
diff --git a/briar-android/res/menu/blogs_blog_actions.xml b/briar-android/res/menu/blogs_blog_actions.xml
index 3661a49dffb91a655b61845b9deec7daf8afe860..64b42ac867a44016de994bb5508ab065f4b33de2 100644
--- a/briar-android/res/menu/blogs_blog_actions.xml
+++ b/briar-android/res/menu/blogs_blog_actions.xml
@@ -1,7 +1,16 @@
 <?xml version="1.0" encoding="utf-8"?>
 <menu
 	xmlns:android="http://schemas.android.com/apk/res/android"
-	xmlns:app="http://schemas.android.com/apk/res-auto">
+	xmlns:app="http://schemas.android.com/apk/res-auto"
+	xmlns:tools="http://schemas.android.com/tools">
+
+	<item
+		android:id="@+id/action_write_blog_post"
+		android:icon="@drawable/forum_item_create_white"
+		android:title="@string/blogs_write_blog_post"
+		android:visible="false"
+		app:showAsAction="ifRoom"
+		tools:visible="true"/>
 
 	<item
 		android:id="@+id/action_blog_share"
@@ -18,6 +27,8 @@
 		android:id="@+id/action_blog_delete"
 		android:icon="@drawable/action_delete_white"
 		android:title="@string/blogs_remove_blog"
-		app:showAsAction="never"/>
+		android:visible="false"
+		app:showAsAction="never"
+		tools:visible="true"/>
 
 </menu>
\ No newline at end of file
diff --git a/briar-android/res/menu/blogs_my_blog_actions.xml b/briar-android/res/menu/blogs_my_blog_actions.xml
deleted file mode 100644
index 5c9052edb01da64626f8a04b6e59f8728a37bf1f..0000000000000000000000000000000000000000
--- a/briar-android/res/menu/blogs_my_blog_actions.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<menu
-	xmlns:android="http://schemas.android.com/apk/res/android"
-	xmlns:app="http://schemas.android.com/apk/res-auto">
-
-	<item
-		android:id="@+id/action_write_blog_post"
-		android:icon="@drawable/forum_item_create_white"
-		android:title="@string/blogs_write_blog_post"
-		app:showAsAction="ifRoom"/>
-
-</menu>
\ No newline at end of file
diff --git a/briar-android/res/values/attrs.xml b/briar-android/res/values/attrs.xml
index 0de786b4b557f43344bfbd48cb5a30f3180aaded..a09b80b32c17ba83878d2ae6e79a7ad6e33cb695 100644
--- a/briar-android/res/values/attrs.xml
+++ b/briar-android/res/values/attrs.xml
@@ -1,8 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
+
 	<declare-styleable name="BriarRecyclerView">
 		<attr name="scrollToEnd" format="boolean" />
 		<attr name="emptyText" format="string" />
 	</declare-styleable>
 
+	<declare-styleable name="AuthorView">
+		<attr name="persona" format="enum">
+			<enum name="normal" value="0"/>
+			<enum name="reblogger" value="1"/>
+			<enum name="commenter" value="2"/>
+		</attr>
+	</declare-styleable>
+
 </resources>
\ No newline at end of file
diff --git a/briar-android/res/values/dimens.xml b/briar-android/res/values/dimens.xml
index 7c676e61aee591c01dc4a36dee1c189dd59247b1..9f62ff0944d8491f42245eeecb7b4a858524a423 100644
--- a/briar-android/res/values/dimens.xml
+++ b/briar-android/res/values/dimens.xml
@@ -43,4 +43,8 @@
 	<dimen name="forum_nested_indicator">24dp</dimen>
 	<dimen name="forum_avatar_size">20dp</dimen>
 
+	<dimen name="blogs_avatar_normal_size">30dp</dimen>
+	<dimen name="blogs_avatar_icon_size">15dp</dimen>
+	<dimen name="blogs_avatar_comment_size">20dp</dimen>
+
 </resources>
diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index dc399cd7e12823ff4435c1278e3e4ac4b10bede0..ba764c356d3880c5a16081cb1d98e02dd48466dd 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -35,7 +35,7 @@
 	<string name="nav_drawer_close_description">Close the navigation drawer</string>
 	<string name="contact_list_button">Contacts</string>
 	<string name="forums_button">Forums</string>
-	<string name="blogs_button">Micro Blogs</string>
+	<string name="blogs_button">Blogs</string>
 	<string name="settings_button">Settings</string>
 	<string name="sign_out_button">Sign Out</string>
 
@@ -207,7 +207,6 @@
 	<string name="blogs_my_blogs_create_hint_desc">A short description of your new blog</string>
 	<string name="blogs_my_blogs_create_hint_desc_explanation">Potential readers may or may not subscribe to your blog based on the content of the description.</string>
 	<string name="blogs_my_blogs_empty_state">You don\'t have any blogs.\n\nWhy don\'t you create one now by clicking the plus in the top right screen corner?</string>
-	<string name="blogs_my_blogs_blog_empty_state">This is the place for content of your blog.\n\nIt seems like you haven\'t written anything yet.\n\nPlease tap the pen icon to compose a new blog post.\n\nDon\'t forget to go public and share your blog.</string>
 	<string name="blogs_my_blogs_created">Blog created</string>
 	<string name="blogs_blog_is_empty">This blog is empty</string>
 	<string name="blogs_other_blog_empty_state">This blog is currently empty.\n\nEither the author hasn\'t written anything yet, or the person who shared this blog with you needs to come online, so posts can be synchronized.</string>
@@ -227,6 +226,8 @@
 	<string name="blogs_remove_blog_dialog_message">Are you sure that you want to remove this blog and all posts?\nNote that this will not remove the blog from other people\'s devices.</string>
 	<string name="blogs_remove_blog_ok">Remove Blog</string>
 	<string name="blogs_blog_removed">Blog Removed</string>
+	<string name="blogs_reblog_comment_hint">Add an optional comment</string>
+	<string name="blogs_reblog_button">Reblog</string>
 
 	<string name="blogs_blog_list">Blog List</string>
 	<string name="blogs_available_blogs">Available Blogs</string>
diff --git a/briar-android/res/values/styles.xml b/briar-android/res/values/styles.xml
index 98751d04c188c20f0686342a83c3a594bbdd8bc0..76c9c0a1f45ee25969ab480473235f6056cbef80 100644
--- a/briar-android/res/values/styles.xml
+++ b/briar-android/res/values/styles.xml
@@ -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>
diff --git a/briar-android/src/org/briarproject/android/ActivityComponent.java b/briar-android/src/org/briarproject/android/ActivityComponent.java
index 3e491d25b1682cab98db282b947efc34925183c6..2a0d82c4b26811aec01e7b8656e1aa0e169e6dd3 100644
--- a/briar-android/src/org/briarproject/android/ActivityComponent.java
+++ b/briar-android/src/org/briarproject/android/ActivityComponent.java
@@ -6,10 +6,10 @@ import org.briarproject.android.blogs.BlogActivity;
 import org.briarproject.android.blogs.BlogFragment;
 import org.briarproject.android.blogs.BlogListFragment;
 import org.briarproject.android.blogs.BlogPostFragment;
-import org.briarproject.android.blogs.BlogsFragment;
 import org.briarproject.android.blogs.CreateBlogActivity;
 import org.briarproject.android.blogs.FeedFragment;
-import org.briarproject.android.blogs.MyBlogsFragment;
+import org.briarproject.android.blogs.ReblogActivity;
+import org.briarproject.android.blogs.ReblogFragment;
 import org.briarproject.android.blogs.RssFeedImportActivity;
 import org.briarproject.android.blogs.RssFeedManageActivity;
 import org.briarproject.android.blogs.WriteBlogPostActivity;
@@ -93,6 +93,10 @@ public interface ActivityComponent {
 
 	void inject(BlogPostFragment fragment);
 
+	void inject(ReblogFragment fragment);
+
+	void inject(ReblogActivity activity);
+
 	void inject(SettingsActivity activity);
 
 	void inject(ChangePasswordActivity activity);
@@ -106,10 +110,8 @@ public interface ActivityComponent {
 	// Fragments
 	void inject(ContactListFragment fragment);
 	void inject(ForumListFragment fragment);
-	void inject(BlogsFragment fragment);
 	void inject(BlogListFragment fragment);
 	void inject(FeedFragment fragment);
-	void inject(MyBlogsFragment fragment);
 	void inject(IntroFragment fragment);
 	void inject(ShowQrCodeFragment fragment);
 	void inject(ContactChooserFragment fragment);
diff --git a/briar-android/src/org/briarproject/android/BriarFragmentActivity.java b/briar-android/src/org/briarproject/android/BriarFragmentActivity.java
index 0a9f22a2e4b93da6f1816da5e332b40690fbf15e..513ace7b560c5b038f7839e1240fcf2ed690a3ca 100644
--- a/briar-android/src/org/briarproject/android/BriarFragmentActivity.java
+++ b/briar-android/src/org/briarproject/android/BriarFragmentActivity.java
@@ -7,7 +7,7 @@ import android.support.v7.app.ActionBar;
 import android.support.v7.app.AlertDialog;
 
 import org.briarproject.R;
-import org.briarproject.android.blogs.BlogsFragment;
+import org.briarproject.android.blogs.FeedFragment;
 import org.briarproject.android.contact.ContactListFragment;
 import org.briarproject.android.forum.ForumListFragment;
 import org.briarproject.android.fragment.BaseFragment;
@@ -27,7 +27,7 @@ public abstract class BriarFragmentActivity extends BriarActivity {
 			actionBar.setTitle(R.string.contact_list_button);
 		} else if (fragmentTag.equals(ForumListFragment.TAG)) {
 			actionBar.setTitle(R.string.forums_button);
-		} else if (fragmentTag.equals(BlogsFragment.TAG)) {
+		} else if (fragmentTag.equals(FeedFragment.TAG)) {
 			actionBar.setTitle(R.string.blogs_button);
 		}
 	}
diff --git a/briar-android/src/org/briarproject/android/NavDrawerActivity.java b/briar-android/src/org/briarproject/android/NavDrawerActivity.java
index 8de76247adbcc85f138f1f2a37dbb544a1efb75c..6381bc9cd1491eefb9b94cde41b0bfca989e12b4 100644
--- a/briar-android/src/org/briarproject/android/NavDrawerActivity.java
+++ b/briar-android/src/org/briarproject/android/NavDrawerActivity.java
@@ -21,7 +21,7 @@ import android.widget.ImageView;
 import android.widget.TextView;
 
 import org.briarproject.R;
-import org.briarproject.android.blogs.BlogsFragment;
+import org.briarproject.android.blogs.FeedFragment;
 import org.briarproject.android.contact.ContactListFragment;
 import org.briarproject.android.controller.NavDrawerController;
 import org.briarproject.android.controller.TransportStateListener;
@@ -82,7 +82,7 @@ public class NavDrawerActivity extends BriarFragmentActivity implements
 			startFragment(ContactListFragment.newInstance());
 		}
 		else if (intent.getBooleanExtra(INTENT_BLOGS, false)) {
-			startFragment(BlogsFragment.newInstance());
+			startFragment(FeedFragment.newInstance());
 		}
 		setIntent(null);
 	}
@@ -186,7 +186,7 @@ public class NavDrawerActivity extends BriarFragmentActivity implements
 				startFragment(ForumListFragment.newInstance());
 				break;
 			case R.id.nav_btn_blogs:
-				startFragment(BlogsFragment.newInstance());
+				startFragment(FeedFragment.newInstance());
 				break;
 			case R.id.nav_btn_settings:
 				startActivity(new Intent(this, SettingsActivity.class));
diff --git a/briar-android/src/org/briarproject/android/blogs/BaseController.java b/briar-android/src/org/briarproject/android/blogs/BaseController.java
new file mode 100644
index 0000000000000000000000000000000000000000..a7cf1e4243eebaa076e59f7ecedc373b5cf2251c
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/blogs/BaseController.java
@@ -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);
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/blogs/BaseControllerImpl.java b/briar-android/src/org/briarproject/android/blogs/BaseControllerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..ac5ce7b72ddecac43b34c57e988a966ee5a1ed3c
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/blogs/BaseControllerImpl.java
@@ -0,0 +1,250 @@
+package org.briarproject.android.blogs;
+
+import android.app.Activity;
+import android.support.annotation.CallSuper;
+import android.support.annotation.Nullable;
+
+import org.briarproject.android.api.AndroidNotificationManager;
+import org.briarproject.android.controller.DbControllerImpl;
+import org.briarproject.android.controller.handler.ResultExceptionHandler;
+import org.briarproject.api.blogs.Blog;
+import org.briarproject.api.blogs.BlogCommentHeader;
+import org.briarproject.api.blogs.BlogManager;
+import org.briarproject.api.blogs.BlogPostHeader;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.event.BlogPostAddedEvent;
+import org.briarproject.api.event.Event;
+import org.briarproject.api.event.EventBus;
+import org.briarproject.api.event.EventListener;
+import org.briarproject.api.identity.IdentityManager;
+import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+import static java.util.logging.Level.INFO;
+import static java.util.logging.Level.WARNING;
+
+abstract class BaseControllerImpl extends DbControllerImpl
+		implements BaseController, EventListener {
+
+	private static final Logger LOG =
+			Logger.getLogger(BaseControllerImpl.class.getName());
+
+	@Inject
+	protected Activity activity;
+	@Inject
+	protected EventBus eventBus;
+	@Inject
+	protected AndroidNotificationManager notificationManager;
+	@Inject
+	protected IdentityManager identityManager;
+
+	@Inject
+	protected volatile BlogManager blogManager;
+
+	private final Map<MessageId, String> bodyCache = new ConcurrentHashMap<>();
+	private final Map<MessageId, BlogPostHeader> headerCache =
+			new ConcurrentHashMap<>();
+
+	protected volatile OnBlogPostAddedListener listener;
+
+	@Override
+	@CallSuper
+	public void onStart() {
+		eventBus.addListener(this);
+	}
+
+	@Override
+	@CallSuper
+	public void onStop() {
+		eventBus.removeListener(this);
+	}
+
+	@Override
+	@CallSuper
+	public void eventOccurred(Event e) {
+		if (e instanceof BlogPostAddedEvent) {
+			final BlogPostAddedEvent m = (BlogPostAddedEvent) e;
+			LOG.info("New blog post added");
+			activity.runOnUiThread(new Runnable() {
+				@Override
+				public void run() {
+					listener.onBlogPostAdded(m.getHeader(), m.isLocal());
+				}
+			});
+		}
+	}
+
+	@Override
+	public void setOnBlogPostAddedListener(OnBlogPostAddedListener listener) {
+		if (this.listener != null)
+			throw new IllegalStateException("Listener was already set");
+		this.listener = listener;
+	}
+
+	@Override
+	public void loadBlogPosts(final GroupId groupId,
+			final ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) {
+		if (groupId == null) throw new IllegalStateException();
+		runOnDbThread(new Runnable() {
+			@Override
+			public void run() {
+				try {
+					Collection<BlogPostItem> items = loadItems(groupId);
+					handler.onResult(items);
+				} catch (DbException e) {
+					if (LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+					handler.onException(e);
+				}
+			}
+		});
+	}
+
+	protected Collection<BlogPostItem> loadItems(GroupId groupId)
+			throws DbException {
+		long now = System.currentTimeMillis();
+		Collection<BlogPostHeader> headers =
+				blogManager.getPostHeaders(groupId);
+		long duration = System.currentTimeMillis() - now;
+		if (LOG.isLoggable(INFO))
+			LOG.info("Loading headers took " + duration + " ms");
+		Collection<BlogPostItem> items = new ArrayList<>(headers.size());
+		now = System.currentTimeMillis();
+		for (BlogPostHeader h : headers) {
+			headerCache.put(h.getId(), h);
+			BlogPostItem item = getItem(h);
+			items.add(item);
+		}
+		duration = System.currentTimeMillis() - now;
+		if (LOG.isLoggable(INFO))
+			LOG.info("Loading bodies took " + duration + " ms");
+		return items;
+	}
+
+	@Override
+	public void loadBlogPost(final BlogPostHeader header,
+			final ResultExceptionHandler<BlogPostItem, DbException> handler) {
+
+		String body = bodyCache.get(header.getId());
+		if (body != null) {
+			LOG.info("Loaded body from cache");
+			handler.onResult(new BlogPostItem(header, body));
+			return;
+		}
+		runOnDbThread(new Runnable() {
+			@Override
+			public void run() {
+				try {
+					long now = System.currentTimeMillis();
+					BlogPostItem item = getItem(header);
+					long duration = System.currentTimeMillis() - now;
+					if (LOG.isLoggable(INFO))
+						LOG.info("Loading body took " + duration + " ms");
+					handler.onResult(item);
+				} catch (DbException e) {
+					if (LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+					handler.onException(e);
+				}
+			}
+		});
+	}
+
+	@Override
+	public void loadBlogPost(final GroupId g, final MessageId m,
+			final ResultExceptionHandler<BlogPostItem, DbException> handler) {
+
+		BlogPostHeader header = headerCache.get(m);
+		if (header != null) {
+			LOG.info("Loaded header from cache");
+			loadBlogPost(header, handler);
+			return;
+		}
+		runOnDbThread(new Runnable() {
+			@Override
+			public void run() {
+				try {
+					long now = System.currentTimeMillis();
+					BlogPostHeader header = getPostHeader(g, m);
+					BlogPostItem item = getItem(header);
+					long duration = System.currentTimeMillis() - now;
+					if (LOG.isLoggable(INFO))
+						LOG.info("Loading post took " + duration + " ms");
+					handler.onResult(item);
+				} catch (DbException e) {
+					if (LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+					handler.onException(e);
+				}
+			}
+		});
+	}
+
+	@Override
+	public void repeatPost(final BlogPostItem item,
+			final @Nullable String comment,
+			final ResultExceptionHandler<Void, DbException> handler) {
+		runOnDbThread(new Runnable() {
+			@Override
+			public void run() {
+				try {
+					LocalAuthor a = identityManager.getLocalAuthor();
+					Blog b = blogManager.getPersonalBlog(a);
+					BlogPostHeader h = item.getHeader();
+					blogManager.addLocalComment(a, b.getId(), comment, h);
+					handler.onResult(null);
+				} catch (DbException e) {
+					if (LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+					handler.onException(e);
+				}
+			}
+		});
+	}
+
+	private BlogPostHeader getPostHeader(GroupId g, MessageId m)
+			throws DbException {
+
+		if (g == null) throw new IllegalStateException();
+		BlogPostHeader header = headerCache.get(m);
+		if (header == null) {
+			header = blogManager.getPostHeader(g, m);
+			headerCache.put(m, header);
+		}
+		return header;
+	}
+
+	private BlogPostItem getItem(BlogPostHeader h) throws DbException {
+		String body;
+		if (h instanceof BlogCommentHeader) {
+			BlogCommentHeader c = (BlogCommentHeader) h;
+			BlogCommentItem item = new BlogCommentItem(c);
+			body = getPostBody(item.getPostHeader().getId());
+			item.setBody(body);
+			return item;
+		} else {
+			body = getPostBody(h.getId());
+			return new BlogPostItem(h, body);
+		}
+	}
+
+	private String getPostBody(MessageId m) throws DbException {
+		String body = bodyCache.get(m);
+		if (body == null) {
+			body = blogManager.getPostBody(m);
+			if (body != null) bodyCache.put(m, body);
+		}
+		//noinspection ConstantConditions
+		return body;
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogActivity.java b/briar-android/src/org/briarproject/android/blogs/BlogActivity.java
index 7c3309010e1c85ccfabdcedab718e80637d61816..249d0ff7cf0fc741b7fad3bff73c02c6d908c7d0 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogActivity.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogActivity.java
@@ -14,7 +14,7 @@ import android.widget.ProgressBar;
 import org.briarproject.R;
 import org.briarproject.android.ActivityComponent;
 import org.briarproject.android.BriarActivity;
-import org.briarproject.android.blogs.BlogController.BlogPostListener;
+import org.briarproject.android.blogs.BaseController.OnBlogPostAddedListener;
 import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener;
 import org.briarproject.android.controller.handler.UiResultExceptionHandler;
 import org.briarproject.android.fragment.BaseFragment.BaseFragmentListener;
@@ -33,16 +33,16 @@ import javax.inject.Inject;
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 
-public class BlogActivity extends BriarActivity implements BlogPostListener,
+public class BlogActivity extends BriarActivity implements
+		OnBlogPostAddedListener,
 		OnBlogPostClickListener, BaseFragmentListener {
 
 	static final int REQUEST_WRITE_POST = 1;
 	static final int REQUEST_SHARE = 2;
-	static final String BLOG_NAME = "briar.BLOG_NAME";
-	static final String IS_MY_BLOG = "briar.IS_MY_BLOG";
+	public static final String BLOG_NAME = "briar.BLOG_NAME";
 	static final String IS_NEW_BLOG = "briar.IS_NEW_BLOG";
 
-	private static final String POST_ID = "briar.POST_ID";
+	public static final String POST_ID = "briar.POST_ID";
 
 	private GroupId groupId;
 	private ProgressBar progressBar;
@@ -50,7 +50,7 @@ public class BlogActivity extends BriarActivity implements BlogPostListener,
 	private BlogPagerAdapter blogPagerAdapter;
 	private BlogPostPagerAdapter postPagerAdapter;
 	private String blogName;
-	private boolean myBlog, isNew;
+	private boolean isNew;
 	private MessageId savedPostId;
 
 	@Inject
@@ -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
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogCommentItem.java b/briar-android/src/org/briarproject/android/blogs/BlogCommentItem.java
new file mode 100644
index 0000000000000000000000000000000000000000..b3dea0b8c96a5faf7753d96ceec98efb47c69b25
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/blogs/BlogCommentItem.java
@@ -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);
+		}
+	}
+}
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogController.java b/briar-android/src/org/briarproject/android/blogs/BlogController.java
index 90fbb61cf1ae4ab2428674fadca842608a92da97..768d29c14cb059bebe4618c93fa7c1c6156ae03e 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogController.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogController.java
@@ -11,26 +11,20 @@ import org.briarproject.api.sync.MessageId;
 
 import java.util.Collection;
 
-public interface BlogController extends ActivityLifecycleController {
+public interface BlogController extends BaseController {
 
 	void setGroupId(GroupId g);
 
 	void loadBlogPosts(
 			ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler);
 
-	void loadBlogPost(BlogPostHeader header,
-			ResultExceptionHandler<BlogPostItem, DbException> handler);
-
 	void loadBlogPost(MessageId m,
 			ResultExceptionHandler<BlogPostItem, DbException> handler);
 
+	void isMyBlog(ResultExceptionHandler<Boolean, DbException> handler);
+
 	void canDeleteBlog(ResultExceptionHandler<Boolean, DbException> handler);
 
 	void deleteBlog(ResultExceptionHandler<Void, DbException> handler);
 
-	interface BlogPostListener {
-		@UiThread
-		void onBlogPostAdded(BlogPostHeader header, boolean local);
-	}
-
 }
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java b/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java
index 9906e511e80952f4a9c1352b405d38e7613e3b76..d70779e34ea17b04f149c318601f736fe6a3597e 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java
@@ -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 setGroupId(GroupId g) {
-		groupId = g;
-	}
-
 	@Override
 	public void onActivityCreate() {
-		if (activity instanceof BlogPostListener) {
-			listener = (BlogPostListener) activity;
+		if (activity instanceof OnBlogPostAddedListener) {
+			listener = (OnBlogPostAddedListener) activity;
 		} else {
 			throw new IllegalStateException(
 					"An activity that injects the BlogController must " +
-							"implement the BlogPostListener");
+							"implement the OnBlogPostAddedListener");
 		}
 	}
 
 	@Override
 	public void onActivityResume() {
+		super.onStart(); // TODO: Should be called when activity starts. #609
 		notificationManager.blockNotification(groupId);
 		notificationManager.clearBlogPostNotification(groupId);
-		eventBus.addListener(this);
 	}
 
 	@Override
 	public void onActivityPause() {
+		super.onStop(); // TODO: Should be called when activity stops. #609
 		notificationManager.unblockNotification(groupId);
-		eventBus.removeListener(this);
 	}
 
 	@Override
 	public void onActivityDestroy() {
 	}
 
+	@Override
+	public void setGroupId(GroupId g) {
+		groupId = g;
+	}
+
 	@Override
 	public void eventOccurred(Event e) {
 		if (groupId == null) throw new IllegalStateException();
 		if (e instanceof BlogPostAddedEvent) {
-			final BlogPostAddedEvent m = (BlogPostAddedEvent) e;
-			if (m.getGroupId().equals(groupId)) {
-				LOG.info("New blog post added");
-				activity.runOnUiThread(new Runnable() {
-					@Override
-					public void run() {
-						listener.onBlogPostAdded(m.getHeader(), m.isLocal());
-					}
-				});
+			BlogPostAddedEvent s = (BlogPostAddedEvent) e;
+			if (s.getGroupId().equals(groupId)) {
+				super.eventOccurred(e);
 			}
 		} else if (e instanceof GroupRemovedEvent) {
 			GroupRemovedEvent s = (GroupRemovedEvent) e;
@@ -122,86 +91,27 @@ public class BlogControllerImpl extends DbControllerImpl
 	public void loadBlogPosts(
 			final ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) {
 		if (groupId == null) throw new IllegalStateException();
-		runOnDbThread(new Runnable() {
-			@Override
-			public void run() {
-				try {
-					long now = System.currentTimeMillis();
-					Collection<BlogPostHeader> headers =
-							blogManager.getPostHeaders(groupId);
-					long duration = System.currentTimeMillis() - now;
-					if (LOG.isLoggable(INFO))
-						LOG.info("Loading headers took " + duration + " ms");
-					List<BlogPostItem> items = new ArrayList<>(headers.size());
-					now = System.currentTimeMillis();
-					for (BlogPostHeader h : headers) {
-						headerCache.put(h.getId(), h);
-						byte[] body = getPostBody(h.getId());
-						items.add(new BlogPostItem(groupId, h, body));
-					}
-					duration = System.currentTimeMillis() - now;
-					if (LOG.isLoggable(INFO))
-						LOG.info("Loading bodies took " + duration + " ms");
-					handler.onResult(items);
-				} catch (DbException e) {
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, e.toString(), e);
-					handler.onException(e);
-				}
-			}
-		});
+		loadBlogPosts(groupId, handler);
 	}
 
 	@Override
-	public void loadBlogPost(final BlogPostHeader header,
+	public void loadBlogPost(final MessageId m,
 			final ResultExceptionHandler<BlogPostItem, DbException> handler) {
 		if (groupId == null) throw new IllegalStateException();
-		byte[] body = bodyCache.get(header.getId());
-		if (body != null) {
-			LOG.info("Loaded body from cache");
-			handler.onResult(new BlogPostItem(groupId, header, body));
-			return;
-		}
-		runOnDbThread(new Runnable() {
-			@Override
-			public void run() {
-				try {
-					long now = System.currentTimeMillis();
-					byte[] body = getPostBody(header.getId());
-					long duration = System.currentTimeMillis() - now;
-					if (LOG.isLoggable(INFO))
-						LOG.info("Loading body took " + duration + " ms");
-					handler.onResult(new BlogPostItem(groupId, header, body));
-				} catch (DbException e) {
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, e.toString(), e);
-					handler.onException(e);
-				}
-			}
-		});
+		loadBlogPost(groupId, m, handler);
 	}
 
 	@Override
-	public void loadBlogPost(final MessageId m,
-			final ResultExceptionHandler<BlogPostItem, DbException> handler) {
+	public void isMyBlog(
+			final ResultExceptionHandler<Boolean, DbException> handler) {
 		if (groupId == null) throw new IllegalStateException();
-		BlogPostHeader header = headerCache.get(m);
-		if (header != null) {
-			LOG.info("Loaded header from cache");
-			loadBlogPost(header, handler);
-			return;
-		}
 		runOnDbThread(new Runnable() {
 			@Override
 			public void run() {
 				try {
-					long now = System.currentTimeMillis();
-					BlogPostHeader header = getPostHeader(m);
-					byte[] body = getPostBody(m);
-					long duration = System.currentTimeMillis() - now;
-					if (LOG.isLoggable(INFO))
-						LOG.info("Loading post took " + duration + " ms");
-					handler.onResult(new BlogPostItem(groupId, header, body));
+					LocalAuthor a = identityManager.getLocalAuthor();
+					Blog b = blogManager.getBlog(groupId);
+					handler.onResult(b.getAuthor().getId().equals(a.getId()));
 				} catch (DbException e) {
 					if (LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
@@ -209,25 +119,7 @@ public class BlogControllerImpl extends DbControllerImpl
 				}
 			}
 		});
-	}
 
-	private BlogPostHeader getPostHeader(MessageId m) throws DbException {
-		if (groupId == null) throw new IllegalStateException();
-		BlogPostHeader header = headerCache.get(m);
-		if (header == null) {
-			header = blogManager.getPostHeader(groupId, m);
-			headerCache.put(m, header);
-		}
-		return header;
-	}
-
-	private byte[] getPostBody(MessageId m) throws DbException {
-		byte[] body = bodyCache.get(m);
-		if (body == null) {
-			body = blogManager.getPostBody(m);
-			if (body != null) bodyCache.put(m, body);
-		}
-		return body;
 	}
 
 	@Override
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java
index 0e89542af0459ebd9e35411353adf62a39edde18..b3b87a113c3481a7e7eae63457ad6d3bf0ed5e17 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java
@@ -19,7 +19,7 @@ import android.widget.Toast;
 
 import org.briarproject.R;
 import org.briarproject.android.ActivityComponent;
-import org.briarproject.android.blogs.BlogController.BlogPostListener;
+import org.briarproject.android.blogs.BaseController.OnBlogPostAddedListener;
 import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener;
 import org.briarproject.android.controller.handler.UiResultExceptionHandler;
 import org.briarproject.android.fragment.BaseFragment;
@@ -42,12 +42,12 @@ import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
 import static android.widget.Toast.LENGTH_SHORT;
 import static org.briarproject.android.BriarActivity.GROUP_ID;
 import static org.briarproject.android.blogs.BlogActivity.BLOG_NAME;
-import static org.briarproject.android.blogs.BlogActivity.IS_MY_BLOG;
 import static org.briarproject.android.blogs.BlogActivity.IS_NEW_BLOG;
 import static org.briarproject.android.blogs.BlogActivity.REQUEST_SHARE;
 import static org.briarproject.android.blogs.BlogActivity.REQUEST_WRITE_POST;
 
-public class BlogFragment extends BaseFragment implements BlogPostListener {
+public class BlogFragment extends BaseFragment implements
+		OnBlogPostAddedListener {
 
 	public final static String TAG = BlogFragment.class.getName();
 
@@ -56,20 +56,18 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
 
 	private GroupId groupId;
 	private String blogName;
-	private boolean myBlog;
 	private BlogPostAdapter adapter;
 	private BriarRecyclerView list;
-	private MenuItem deleteButton;
+	private MenuItem writeButton, deleteButton;
 
 	static BlogFragment newInstance(GroupId groupId, String name,
-			boolean myBlog, boolean isNew) {
+			boolean isNew) {
 
 		BlogFragment f = new BlogFragment();
 
 		Bundle bundle = new Bundle();
 		bundle.putByteArray(GROUP_ID, groupId.getBytes());
 		bundle.putString(BLOG_NAME, name);
-		bundle.putBoolean(IS_MY_BLOG, myBlog);
 		bundle.putBoolean(IS_NEW_BLOG, isNew);
 
 		f.setArguments(bundle);
@@ -88,7 +86,6 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
 		if (b == null) throw new IllegalStateException("No group ID in args");
 		groupId = new GroupId(b);
 		blogName = args.getString(BLOG_NAME);
-		myBlog = args.getBoolean(IS_MY_BLOG);
 		boolean isNew = args.getBoolean(IS_NEW_BLOG);
 
 		View v = inflater.inflate(R.layout.fragment_blog, container, false);
@@ -99,12 +96,7 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
 		list.setLayoutManager(new LinearLayoutManager(getActivity()));
 		list.setAdapter(adapter);
 		list.showProgressBar();
-		if (myBlog) {
-			list.setEmptyText(
-					getString(R.string.blogs_my_blogs_blog_empty_state));
-		} else {
-			list.setEmptyText(getString(R.string.blogs_other_blog_empty_state));
-		}
+		list.setEmptyText(getString(R.string.blogs_other_blog_empty_state));
 
 		// show snackbar if this blog was just created
 		if (isNew) {
@@ -128,7 +120,8 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
 	@Override
 	public void onStart() {
 		super.onStart();
-		if (!myBlog) checkIfBlogCanBeDeleted();
+		checkIfThisIsMyBlog();
+		checkIfBlogCanBeDeleted();
 	}
 
 	@Override
@@ -146,13 +139,10 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
 
 	@Override
 	public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-		if (myBlog) {
-			inflater.inflate(R.menu.blogs_my_blog_actions, menu);
-		} else {
-			inflater.inflate(R.menu.blogs_blog_actions, menu);
-			deleteButton = menu.findItem(R.id.action_blog_delete);
-			deleteButton.setVisible(false);
-		}
+		inflater.inflate(R.menu.blogs_blog_actions, menu);
+		writeButton = menu.findItem(R.id.action_write_blog_post);
+		deleteButton = menu.findItem(R.id.action_blog_delete);
+
 		super.onCreateOptionsMenu(menu, inflater);
 	}
 
@@ -199,7 +189,9 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
 	public void onActivityResult(int request, int result, Intent data) {
 		super.onActivityResult(request, result, data);
 
-		if (request == REQUEST_SHARE && result == RESULT_OK) {
+		if (request == REQUEST_WRITE_POST && result == RESULT_OK) {
+			displaySnackbar(R.string.blogs_blog_post_created);
+		} else if (request == REQUEST_SHARE && result == RESULT_OK) {
 			displaySnackbar(R.string.blogs_sharing_snackbar);
 		}
 	}
@@ -217,7 +209,12 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
 					@Override
 					public void onResultUi(BlogPostItem post) {
 						adapter.add(post);
-						if (local) list.scrollToPosition(0);
+						if (local) {
+							list.scrollToPosition(0);
+							displaySnackbar(R.string.blogs_blog_post_created);
+						} else {
+							displaySnackbar(R.string.blogs_blog_post_received);
+						}
 					}
 
 					@Override
@@ -251,6 +248,25 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
 				});
 	}
 
+	private void checkIfThisIsMyBlog() {
+		blogController.canDeleteBlog(
+				new UiResultExceptionHandler<Boolean, DbException>(
+						getActivity()) {
+					@Override
+					public void onResultUi(Boolean isMyBlog) {
+						if (isMyBlog) {
+							showWriteButton();
+						}
+					}
+
+					@Override
+					public void onExceptionUi(DbException exception) {
+						// TODO: Decide how to handle errors in the UI
+						getActivity().finish();
+					}
+				});
+	}
+
 	private void checkIfBlogCanBeDeleted() {
 		blogController.canDeleteBlog(
 				new UiResultExceptionHandler<Boolean, DbException>(
@@ -270,6 +286,11 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
 				});
 	}
 
+	private void showWriteButton() {
+		if (writeButton != null)
+			writeButton.setVisible(true);
+	}
+
 	private void showDeleteButton() {
 		if (deleteButton != null)
 			deleteButton.setVisible(true);
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogListAdapter.java b/briar-android/src/org/briarproject/android/blogs/BlogListAdapter.java
index 92f9430c42a1c7365f0add736b811114891c43ca..37f7dd30167d58160b40b35aa53922ea4543b219 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogListAdapter.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogListAdapter.java
@@ -1,7 +1,6 @@
 package org.briarproject.android.blogs;
 
 import android.app.Activity;
-import android.content.Context;
 import android.content.Intent;
 import android.support.annotation.Nullable;
 import android.support.v4.app.ActivityCompat;
@@ -9,7 +8,6 @@ import android.support.v4.app.ActivityOptionsCompat;
 import android.support.v4.content.ContextCompat;
 import android.support.v7.util.SortedList;
 import android.support.v7.widget.RecyclerView;
-import android.text.format.DateUtils;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -23,12 +21,10 @@ import org.briarproject.api.sync.GroupId;
 
 import java.util.Collection;
 
-import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 import static org.briarproject.android.BriarActivity.GROUP_ID;
 import static org.briarproject.android.blogs.BlogActivity.BLOG_NAME;
-import static org.briarproject.android.blogs.BlogActivity.IS_MY_BLOG;
 
 class BlogListAdapter extends
 		RecyclerView.Adapter<BlogListAdapter.BlogViewHolder> {
@@ -136,7 +132,6 @@ class BlogListAdapter extends
 				Blog b = item.getBlog();
 				i.putExtra(GROUP_ID, b.getId().getBytes());
 				i.putExtra(BLOG_NAME, b.getName());
-				i.putExtra(IS_MY_BLOG, item.isOurs());
 				ActivityOptionsCompat options = ActivityOptionsCompat
 						.makeCustomAnimation(ctx, android.R.anim.fade_in,
 								android.R.anim.fade_out);
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostAdapter.java b/briar-android/src/org/briarproject/android/blogs/BlogPostAdapter.java
index 44d971c83f0d23d5dcdaacb22835fba44cc89b02..badf034503d5d99fc57e0a89860d8abe99b9a5f5 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogPostAdapter.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogPostAdapter.java
@@ -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);
 	}
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java
index f96deda0b2e9ecaeed0d79d3b9cea3adc0d2f3a8..7e0cb1c95a29fd18aba6f72c76dc9b5a0cf82f3b 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java
@@ -19,7 +19,6 @@ import org.briarproject.android.util.TrustIndicatorView;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.sync.MessageId;
-import org.briarproject.util.StringUtils;
 
 import java.util.logging.Logger;
 
@@ -27,7 +26,6 @@ import javax.inject.Inject;
 
 import im.delight.android.identicons.IdenticonDrawable;
 
-import static android.view.View.GONE;
 import static org.briarproject.android.util.AndroidUtils.MIN_RESOLUTION;
 
 public class BlogPostFragment extends BaseFragment {
@@ -135,11 +133,7 @@ public class BlogPostFragment extends BaseFragment {
 		if (ctx != null) {
 			ui.date.setText(AndroidUtils.formatDate(ctx, post.getTimestamp()));
 		}
-
-		// TODO remove #598
-		ui.title.setVisibility(GONE);
-
-		ui.body.setText(StringUtils.fromUtf8(post.getBody()));
+		ui.body.setText(post.getBody());
 	}
 
 	private static class BlogPostViewHolder {
@@ -148,7 +142,6 @@ public class BlogPostFragment extends BaseFragment {
 		private final TextView authorName;
 		private final TrustIndicatorView trust;
 		private final TextView date;
-		private final TextView title;
 		private final TextView body;
 
 		private BlogPostViewHolder(View v) {
@@ -156,7 +149,6 @@ public class BlogPostFragment extends BaseFragment {
 			authorName = (TextView) v.findViewById(R.id.authorName);
 			trust = (TrustIndicatorView) v.findViewById(R.id.trustIndicator);
 			date = (TextView) v.findViewById(R.id.date);
-			title = (TextView) v.findViewById(R.id.title);
 			body = (TextView) v.findViewById(R.id.body);
 		}
 	}
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java b/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java
index 55a439e2edfb148f28f8d44556f4f27c5188fea3..747b661593e1d0cb12f6efd2dc73a247c08dcff1 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java
@@ -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;
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java b/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java
new file mode 100644
index 0000000000000000000000000000000000000000..378b3993b7afe3545e6e5312e7b9527aef6b24b6
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java
@@ -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);
+		}
+	}
+}
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogsFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogsFragment.java
deleted file mode 100644
index 80f234010360227c985d37cfe9df029d80ef5e68..0000000000000000000000000000000000000000
--- a/briar-android/src/org/briarproject/android/blogs/BlogsFragment.java
+++ /dev/null
@@ -1,118 +0,0 @@
-package org.briarproject.android.blogs;
-
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.design.widget.TabLayout;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentStatePagerAdapter;
-import android.support.v4.view.ViewPager;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import org.briarproject.R;
-import org.briarproject.android.ActivityComponent;
-import org.briarproject.android.fragment.BaseFragment;
-
-import static android.view.View.GONE;
-
-public class BlogsFragment extends BaseFragment {
-
-	public final static String TAG = BlogsFragment.class.getName();
-
-	private static final String SELECTED_TAB = "selectedTab";
-	private TabLayout tabLayout;
-
-	public static BlogsFragment newInstance() {
-		
-		Bundle args = new Bundle();
-		
-		BlogsFragment fragment = new BlogsFragment();
-		fragment.setArguments(args);
-		return fragment;
-	}
-	
-	@Nullable
-	@Override
-	public View onCreateView(LayoutInflater inflater, ViewGroup container,
-			Bundle savedInstanceState) {
-
-		View v = inflater.inflate(R.layout.fragment_blogs, container,
-				false);
-
-		tabLayout = (TabLayout) v.findViewById(R.id.tabLayout);
-		ViewPager viewPager = (ViewPager) v.findViewById(R.id.pager);
-
-		String[] titles = {
-				getString(R.string.blogs_feed),
-				getString(R.string.blogs_my_blogs),
-				getString(R.string.blogs_blog_list),
-				getString(R.string.blogs_available_blogs),
-				getString(R.string.blogs_drafts)
-		};
-		TabAdapter tabAdapter =
-				new TabAdapter(getChildFragmentManager(), titles);
-		viewPager.setAdapter(tabAdapter);
-		tabLayout.setupWithViewPager(viewPager);
-
-		tabLayout.setVisibility(GONE);
-
-		if (savedInstanceState != null) {
-			int position = savedInstanceState.getInt(SELECTED_TAB, 0);
-			viewPager.setCurrentItem(position);
-		}
-		return v;
-	}
-
-	@Override
-	public void onSaveInstanceState(Bundle outState) {
-		super.onSaveInstanceState(outState);
-		outState.putInt(SELECTED_TAB, tabLayout.getSelectedTabPosition());
-	}
-
-	@Override
-	public String getUniqueTag() {
-		return TAG;
-	}
-
-	@Override
-	public void injectFragment(ActivityComponent component) {
-		component.inject(this);
-	}
-
-
-	private static class TabAdapter extends FragmentStatePagerAdapter {
-		private String[] titles;
-
-		TabAdapter(FragmentManager fm, String[] titles) {
-			super(fm);
-			this.titles = titles;
-		}
-
-		@Override
-		public int getCount() {
-			return 1;
-//			return titles.length;
-		}
-
-		@Override
-		public Fragment getItem(int position) {
-			return FeedFragment.newInstance();
-//			switch (position) {
-//				case 0:
-//					return FeedFragment.newInstance();
-//				case 1:
-//					return new MyBlogsFragment();
-//				default:
-//					return BlogListFragment.newInstance(position);
-//			}
-		}
-
-		@Override
-		public CharSequence getPageTitle(int position) {
-			return titles[position];
-		}
-	}
-
-}
diff --git a/briar-android/src/org/briarproject/android/blogs/CreateBlogActivity.java b/briar-android/src/org/briarproject/android/blogs/CreateBlogActivity.java
index 980d67d1da8c96f84882d3b94b6e1dda6a82d34b..5bac574b5cf4dbd64ba64ffbb90f8baa91bb57e7 100644
--- a/briar-android/src/org/briarproject/android/blogs/CreateBlogActivity.java
+++ b/briar-android/src/org/briarproject/android/blogs/CreateBlogActivity.java
@@ -15,7 +15,6 @@ import android.widget.Button;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
-import android.widget.Toast;
 
 import org.briarproject.R;
 import org.briarproject.android.ActivityComponent;
@@ -35,11 +34,9 @@ import javax.inject.Inject;
 import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
-import static android.widget.Toast.LENGTH_LONG;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.android.blogs.BlogActivity.BLOG_NAME;
-import static org.briarproject.android.blogs.BlogActivity.IS_MY_BLOG;
 import static org.briarproject.android.blogs.BlogActivity.IS_NEW_BLOG;
 import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_DESC_LENGTH;
 import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_TITLE_LENGTH;
@@ -180,7 +177,6 @@ public class CreateBlogActivity extends BriarActivity
 						new Intent(CreateBlogActivity.this, BlogActivity.class);
 				i.putExtra(GROUP_ID, b.getId().getBytes());
 				i.putExtra(BLOG_NAME, b.getName());
-				i.putExtra(IS_MY_BLOG, true);
 				i.putExtra(IS_NEW_BLOG, true);
 				ActivityOptionsCompat options =
 						makeCustomAnimation(CreateBlogActivity.this,
diff --git a/briar-android/src/org/briarproject/android/blogs/FeedController.java b/briar-android/src/org/briarproject/android/blogs/FeedController.java
index d449eb60be5f8f53c53549650fbfa11e3fa51f8b..e2934f49fd67757128d0dc521f020d86953fcb4a 100644
--- a/briar-android/src/org/briarproject/android/blogs/FeedController.java
+++ b/briar-android/src/org/briarproject/android/blogs/FeedController.java
@@ -1,24 +1,17 @@
 package org.briarproject.android.blogs;
 
+import org.briarproject.android.controller.handler.ResultExceptionHandler;
 import org.briarproject.android.controller.handler.ResultHandler;
 import org.briarproject.api.blogs.Blog;
+import org.briarproject.api.db.DbException;
 
 import java.util.Collection;
 
-public interface FeedController {
+public interface FeedController extends BaseController {
 
-	void onResume();
-
-	void onPause();
-
-	void loadPosts(ResultHandler<Collection<BlogPostItem>> resultHandler);
+	void loadBlogPosts(
+			ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler);
 
 	void loadPersonalBlog(ResultHandler<Blog> resultHandler);
 
-	void setOnBlogPostAddedListener(OnBlogPostAddedListener listener);
-
-	interface OnBlogPostAddedListener {
-		void onBlogPostAdded(final BlogPostItem post);
-	}
-
 }
diff --git a/briar-android/src/org/briarproject/android/blogs/FeedControllerImpl.java b/briar-android/src/org/briarproject/android/blogs/FeedControllerImpl.java
index 7c093598b156da61c6d04d6bca7758f7c1b88161..50074c0a7adf24bd36053d1eb7bbeeef3002b828 100644
--- a/briar-android/src/org/briarproject/android/blogs/FeedControllerImpl.java
+++ b/briar-android/src/org/briarproject/android/blogs/FeedControllerImpl.java
@@ -1,19 +1,12 @@
 package org.briarproject.android.blogs;
 
-import org.briarproject.android.api.AndroidNotificationManager;
-import org.briarproject.android.controller.DbControllerImpl;
+import org.briarproject.android.controller.handler.ResultExceptionHandler;
 import org.briarproject.android.controller.handler.ResultHandler;
 import org.briarproject.api.blogs.Blog;
-import org.briarproject.api.blogs.BlogManager;
-import org.briarproject.api.blogs.BlogPostHeader;
 import org.briarproject.api.db.DbException;
-import org.briarproject.api.event.BlogPostAddedEvent;
-import org.briarproject.api.event.Event;
-import org.briarproject.api.event.EventBus;
-import org.briarproject.api.event.EventListener;
+import org.briarproject.api.db.NoSuchGroupException;
+import org.briarproject.api.db.NoSuchMessageException;
 import org.briarproject.api.identity.Author;
-import org.briarproject.api.identity.IdentityManager;
-import org.briarproject.api.sync.GroupId;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -24,82 +17,56 @@ import javax.inject.Inject;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
 
-public class FeedControllerImpl extends DbControllerImpl
-		implements FeedController, EventListener {
+public class FeedControllerImpl extends BaseControllerImpl
+		implements FeedController {
 
 	private static final Logger LOG =
 			Logger.getLogger(FeedControllerImpl.class.getName());
 
-	@SuppressWarnings("WeakerAccess")
-	@Inject
-	AndroidNotificationManager notificationManager;
-	@Inject
-	protected EventBus eventBus;
-
-	@Inject
-	protected volatile BlogManager blogManager;
-	@Inject
-	protected volatile IdentityManager identityManager;
-
-	private volatile OnBlogPostAddedListener listener;
-
 	@Inject
 	FeedControllerImpl() {
 	}
 
 	@Override
-	public void onResume() {
+	public void onStart() {
+		super.onStart();
 		notificationManager.blockAllBlogPostNotifications();
 		notificationManager.clearAllBlogPostNotifications();
-		eventBus.addListener(this);
 	}
 
 	@Override
-	public void onPause() {
+	public void onStop() {
+		super.onStop();
 		notificationManager.unblockAllBlogPostNotifications();
-		eventBus.removeListener(this);
 	}
 
 	@Override
-	public void eventOccurred(Event e) {
-		if (!(e instanceof BlogPostAddedEvent)) return;
-
-		LOG.info("New blog post added");
-		if (listener != null) {
-			BlogPostAddedEvent m = (BlogPostAddedEvent) e;
-			BlogPostHeader header = m.getHeader();
-			addPost(m.getGroupId(), header);
-		}
-	}
-
-	@Override
-	public void loadPosts(
-			final ResultHandler<Collection<BlogPostItem>> resultHandler) {
-
-		LOG.info("Loading blog posts...");
+	public void loadBlogPosts(
+			final ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) {
+		LOG.info("Loading all blog posts...");
 		runOnDbThread(new Runnable() {
 			@Override
 			public void run() {
-				Collection<BlogPostItem> posts = new ArrayList<>();
 				try {
 					// load blog posts
 					long now = System.currentTimeMillis();
+					Collection<BlogPostItem> posts = new ArrayList<>();
 					for (Blog b : blogManager.getBlogs()) {
-						Collection<BlogPostHeader> header =
-								blogManager.getPostHeaders(b.getId());
-						for (BlogPostHeader h : header) {
-							byte[] body = blogManager.getPostBody(h.getId());
-							posts.add(new BlogPostItem(b.getId(), h, body));
+						try {
+							posts.addAll(loadItems(b.getId()));
+						} catch (NoSuchGroupException | NoSuchMessageException e) {
+							if (LOG.isLoggable(WARNING))
+								LOG.log(WARNING, e.toString(), e);
 						}
 					}
 					long duration = System.currentTimeMillis() - now;
 					if (LOG.isLoggable(INFO))
-						LOG.info("Loading posts took " + duration + " ms");
-					resultHandler.onResult(posts);
+						LOG.info("Loading all posts took " + duration + " ms");
+					handler.onResult(posts);
 				} catch (DbException e) {
 					if (LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
-					resultHandler.onResult(null);
+					handler.onException(e);
 				}
 			}
 		});
@@ -114,8 +81,7 @@ public class FeedControllerImpl extends DbControllerImpl
 				try {
 					// load blog posts
 					long now = System.currentTimeMillis();
-					Author a =
-							identityManager.getLocalAuthors().iterator().next();
+					Author a = identityManager.getLocalAuthor();
 					Blog b = blogManager.getPersonalBlog(a);
 					long duration = System.currentTimeMillis() - now;
 					if (LOG.isLoggable(INFO))
@@ -130,25 +96,4 @@ public class FeedControllerImpl extends DbControllerImpl
 		});
 	}
 
-	@Override
-	public void setOnBlogPostAddedListener(OnBlogPostAddedListener listener) {
-		this.listener = listener;
-	}
-
-	private void addPost(final GroupId groupId, final BlogPostHeader header) {
-		runOnDbThread(new Runnable() {
-			@Override
-			public void run() {
-				try {
-					byte[] body = blogManager.getPostBody(header.getId());
-					BlogPostItem post = new BlogPostItem(groupId, header, body);
-					listener.onBlogPostAdded(post);
-				} catch (DbException ex) {
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, ex.toString(), ex);
-				}
-			}
-		});
-	}
-
 }
diff --git a/briar-android/src/org/briarproject/android/blogs/FeedFragment.java b/briar-android/src/org/briarproject/android/blogs/FeedFragment.java
index a83f9beb056b87d5d41ba441c63f08e3c5d0e2c7..9e6f5774877c5a43f99aef2f21780f02dba2d446 100644
--- a/briar-android/src/org/briarproject/android/blogs/FeedFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/FeedFragment.java
@@ -17,11 +17,15 @@ import android.view.ViewGroup;
 
 import org.briarproject.R;
 import org.briarproject.android.ActivityComponent;
+import org.briarproject.android.blogs.BaseController.OnBlogPostAddedListener;
 import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener;
+import org.briarproject.android.controller.handler.UiResultExceptionHandler;
 import org.briarproject.android.controller.handler.UiResultHandler;
 import org.briarproject.android.fragment.BaseFragment;
 import org.briarproject.android.util.BriarRecyclerView;
 import org.briarproject.api.blogs.Blog;
+import org.briarproject.api.blogs.BlogPostHeader;
+import org.briarproject.api.db.DbException;
 
 import java.util.Collection;
 
@@ -32,11 +36,10 @@ import static android.support.design.widget.Snackbar.LENGTH_LONG;
 import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
 import static org.briarproject.android.BriarActivity.GROUP_ID;
 import static org.briarproject.android.blogs.BlogActivity.BLOG_NAME;
-import static org.briarproject.android.blogs.BlogActivity.IS_MY_BLOG;
 import static org.briarproject.android.blogs.BlogActivity.REQUEST_WRITE_POST;
 
 public class FeedFragment extends BaseFragment implements
-		OnBlogPostClickListener, FeedController.OnBlogPostAddedListener {
+		OnBlogPostClickListener, OnBlogPostAddedListener {
 
 	public final static String TAG = FeedFragment.class.getName();
 
@@ -48,7 +51,7 @@ public class FeedFragment extends BaseFragment implements
 	private BriarRecyclerView list;
 	private Blog personalBlog = null;
 
-	static FeedFragment newInstance() {
+	public static FeedFragment newInstance() {
 		FeedFragment f = new FeedFragment();
 
 		Bundle args = new Bundle();
@@ -95,6 +98,7 @@ public class FeedFragment extends BaseFragment implements
 	@Override
 	public void onStart() {
 		super.onStart();
+		feedController.onStart();
 		feedController.loadPersonalBlog(
 				new UiResultHandler<Blog>(getActivity()) {
 					@Override
@@ -102,33 +106,30 @@ public class FeedFragment extends BaseFragment implements
 						personalBlog = b;
 					}
 				});
-	}
-
-	@Override
-	public void onResume() {
-		super.onResume();
-		list.startPeriodicUpdate();
-		feedController.onResume();
-		feedController.loadPosts(
-				new UiResultHandler<Collection<BlogPostItem>>(getActivity()) {
+		feedController.loadBlogPosts(
+				new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>(
+						getActivity()) {
 					@Override
 					public void onResultUi(Collection<BlogPostItem> posts) {
-						if (posts == null) {
-							// TODO show error?
-						} else if (posts.isEmpty()) {
+						if (posts.isEmpty()) {
 							list.showData();
 						} else {
 							adapter.addAll(posts);
 						}
 					}
+					@Override
+					public void onExceptionUi(DbException exception) {
+						// TODO
+					}
 				});
+		list.startPeriodicUpdate();
 	}
 
 	@Override
-	public void onPause() {
-		super.onPause();
+	public void onStop() {
+		super.onStop();
+		feedController.onStop();
 		list.stopPeriodicUpdate();
-		feedController.onPause();
 		// TODO save list position in database/preferences?
 	}
 
@@ -171,33 +172,30 @@ public class FeedFragment extends BaseFragment implements
 	}
 
 	@Override
-	public void onBlogPostAdded(final BlogPostItem post) {
-		listener.runOnUiThread(new Runnable() {
-			@Override
-			public void run() {
-				adapter.add(post);
-				showSnackBar(R.string.blogs_blog_post_received);
-			}
-		});
+	public void onBlogPostAdded(BlogPostHeader header, final boolean local) {
+		feedController.loadBlogPost(header,
+				new UiResultExceptionHandler<BlogPostItem, DbException>(
+						getActivity()) {
+					@Override
+					public void onResultUi(BlogPostItem post) {
+						adapter.add(post);
+						if (local) {
+							showSnackBar(R.string.blogs_blog_post_created);
+						} else {
+							showSnackBar(R.string.blogs_blog_post_received);
+						}
+					}
+					@Override
+					public void onExceptionUi(DbException exception) {
+						// TODO: Decide how to handle errors in the UI
+					}
+				}
+		);
 	}
 
 	@Override
 	public void onBlogPostClick(BlogPostItem post) {
-		byte[] groupId = post.getGroupId().getBytes();
-		String name = getString(R.string.blogs_personal_blog,
-				post.getAuthor().getName());
-		boolean myBlog = personalBlog != null &&
-				personalBlog.getId().equals(post.getGroupId());
-
-		Intent i = new Intent(getActivity(), BlogActivity.class);
-		i.putExtra(GROUP_ID, groupId);
-		i.putExtra(BLOG_NAME, name);
-		i.putExtra(IS_MY_BLOG, myBlog);
-		ActivityOptionsCompat options =
-				makeCustomAnimation(getActivity(),
-						android.R.anim.slide_in_left,
-						android.R.anim.slide_out_right);
-		startActivity(i, options.toBundle());
+		// TODO Open detail view of post
 	}
 
 	@Override
@@ -228,5 +226,4 @@ public class FeedFragment extends BaseFragment implements
 		}
 		s.show();
 	}
-
 }
diff --git a/briar-android/src/org/briarproject/android/blogs/MyBlogsFragment.java b/briar-android/src/org/briarproject/android/blogs/MyBlogsFragment.java
deleted file mode 100644
index 97f33ebf4bf78112f21dd0b00c9ed98a34a7aa03..0000000000000000000000000000000000000000
--- a/briar-android/src/org/briarproject/android/blogs/MyBlogsFragment.java
+++ /dev/null
@@ -1,169 +0,0 @@
-package org.briarproject.android.blogs;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v4.app.ActivityCompat;
-import android.support.v4.app.ActivityOptionsCompat;
-import android.support.v7.widget.LinearLayoutManager;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-
-import org.briarproject.R;
-import org.briarproject.android.ActivityComponent;
-import org.briarproject.android.fragment.BaseFragment;
-import org.briarproject.android.util.BriarRecyclerView;
-import org.briarproject.api.blogs.Blog;
-import org.briarproject.api.blogs.BlogManager;
-import org.briarproject.api.blogs.BlogPostHeader;
-import org.briarproject.api.db.DbException;
-import org.briarproject.api.db.NoSuchGroupException;
-import org.briarproject.api.identity.IdentityManager;
-import org.briarproject.api.identity.LocalAuthor;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.logging.Logger;
-
-import javax.inject.Inject;
-
-import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
-
-public class MyBlogsFragment extends BaseFragment {
-
-	public final static String TAG = MyBlogsFragment.class.getName();
-
-	private static final Logger LOG = Logger.getLogger(TAG);
-	private BriarRecyclerView list;
-	private BlogListAdapter adapter;
-
-	// Fields that are accessed from background threads must be volatile
-	@Inject
-	protected volatile IdentityManager identityManager;
-	@Inject
-	volatile BlogManager blogManager;
-
-	@Inject
-	public MyBlogsFragment() {
-	}
-
-	@Nullable
-	@Override
-	public View onCreateView(LayoutInflater inflater, ViewGroup container,
-			Bundle savedInstanceState) {
-
-		setHasOptionsMenu(true);
-
-		adapter = new BlogListAdapter(getActivity());
-
-		list = (BriarRecyclerView) inflater
-				.inflate(R.layout.fragment_blogs_my, container, false);
-		list.setLayoutManager(new LinearLayoutManager(getActivity()));
-		list.setAdapter(adapter);
-		list.setEmptyText(getString(R.string.blogs_my_blogs_empty_state));
-
-		return list;
-	}
-
-	@Override
-	public void onActivityCreated(Bundle savedInstanceState) {
-		super.onActivityCreated(savedInstanceState);
-		listener.getActivityComponent().inject(this);
-		// Starting from here, we can use injected objects
-	}
-
-	@Override
-	public void onResume() {
-		super.onResume();
-		adapter.clear();
-		list.showProgressBar();
-		loadBlogs();
-	}
-
-	@Override
-	public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-		inflater.inflate(R.menu.blogs_my_actions, menu);
-		super.onCreateOptionsMenu(menu, inflater);
-	}
-
-	@Override
-	public boolean onOptionsItemSelected(final MenuItem item) {
-		// Handle presses on the action bar items
-		switch (item.getItemId()) {
-			case R.id.action_create_blog:
-				Intent intent =
-						new Intent(getContext(), CreateBlogActivity.class);
-				ActivityOptionsCompat options =
-						makeCustomAnimation(getActivity(),
-								android.R.anim.slide_in_left,
-								android.R.anim.slide_out_right);
-				ActivityCompat.startActivity(getActivity(), intent,
-						options.toBundle());
-				return true;
-			default:
-				return super.onOptionsItemSelected(item);
-		}
-	}
-
-	@Override
-	public String getUniqueTag() {
-		return TAG;
-	}
-
-	@Override
-	public void injectFragment(ActivityComponent component) {
-		component.inject(this);
-	}
-
-	private void loadBlogs() {
-		listener.runOnDbThread(new Runnable() {
-			@Override
-			public void run() {
-				try {
-					// load blogs
-					long now = System.currentTimeMillis();
-					Collection<BlogListItem> blogs = new ArrayList<>();
-					Collection<LocalAuthor> authors =
-							identityManager.getLocalAuthors();
-					LocalAuthor a = authors.iterator().next();
-					for (Blog b : blogManager.getBlogs(a)) {
-						try {
-							Collection<BlogPostHeader> headers =
-									blogManager.getPostHeaders(b.getId());
-							blogs.add(new BlogListItem(b, headers, true));
-						} catch (NoSuchGroupException e) {
-							// Continue
-						}
-					}
-					displayBlogs(blogs);
-					long duration = System.currentTimeMillis() - now;
-					if (LOG.isLoggable(INFO))
-						LOG.info("Full blog load took " + duration + " ms");
-				} catch (DbException e) {
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, e.toString(), e);
-				}
-			}
-		});
-	}
-
-	private void displayBlogs(final Collection<BlogListItem> items) {
-		listener.runOnUiThread(new Runnable() {
-			@Override
-			public void run() {
-				if (items.size() == 0) {
-					list.showData();
-				} else {
-					adapter.addAll(items);
-				}
-			}
-		});
-	}
-
-}
diff --git a/briar-android/src/org/briarproject/android/blogs/ReblogActivity.java b/briar-android/src/org/briarproject/android/blogs/ReblogActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..923d520a65053f6d7adfc3c2e7ab03c977acf2f8
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/blogs/ReblogActivity.java
@@ -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);
+	}
+}
diff --git a/briar-android/src/org/briarproject/android/blogs/ReblogFragment.java b/briar-android/src/org/briarproject/android/blogs/ReblogFragment.java
new file mode 100644
index 0000000000000000000000000000000000000000..1fee6c6473124c6d2893cbcb5bd01d3e33fe6274
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/blogs/ReblogFragment.java
@@ -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);
+		}
+	}
+}
diff --git a/briar-android/src/org/briarproject/android/fragment/BaseFragment.java b/briar-android/src/org/briarproject/android/fragment/BaseFragment.java
index 75d480bfbfe78a18600f4338f3c57e4892dfb9e7..c19bcf1eed31cafa16714666da3283ab0a1901cb 100644
--- a/briar-android/src/org/briarproject/android/fragment/BaseFragment.java
+++ b/briar-android/src/org/briarproject/android/fragment/BaseFragment.java
@@ -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);
 	}
 }
diff --git a/briar-android/src/org/briarproject/android/introduction/IntroductionActivity.java b/briar-android/src/org/briarproject/android/introduction/IntroductionActivity.java
index f2227d826257c7a2feb11d744642846c75db6435..3c14628ebf55a535190f26736029abf327573676 100644
--- a/briar-android/src/org/briarproject/android/introduction/IntroductionActivity.java
+++ b/briar-android/src/org/briarproject/android/introduction/IntroductionActivity.java
@@ -32,12 +32,12 @@ public class IntroductionActivity extends BriarActivity implements
 		if (contactId == -1)
 			throw new IllegalArgumentException("Wrong ContactId");
 
-		setContentView(R.layout.activity_introduction);
+		setContentView(R.layout.activity_fragment_container);
 
 		if (savedInstanceState == null) {
 			getSupportFragmentManager()
 					.beginTransaction()
-					.add(R.id.introductionContainer,
+					.add(R.id.fragmentContainer,
 							ContactChooserFragment.newInstance())
 					.commit();
 		}
@@ -109,7 +109,7 @@ public class IntroductionActivity extends BriarActivity implements
 						android.R.anim.slide_in_left,
 						android.R.anim.slide_out_right)
 				.addSharedElement(view, "avatar")
-				.replace(R.id.introductionContainer, messageFragment,
+				.replace(R.id.fragmentContainer, messageFragment,
 						ContactChooserFragment.TAG)
 				.addToBackStack(null)
 				.commit();
diff --git a/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java b/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java
index 82fca3e36d63c587eac49ee6e7edabe278b00437..268b16b85ecc8822b0ae808fdc742bfa58540cd7 100644
--- a/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java
+++ b/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java
@@ -343,12 +343,12 @@ public class ShowQrCodeFragment extends BaseEventFragment
 		});
 	}
 
-	private void finish() {
+	@Override
+	protected void finish() {
 		getActivity().getSupportFragmentManager().popBackStack();
 	}
 
 	private class BluetoothStateReceiver extends BroadcastReceiver {
-
 		@Override
 		public void onReceive(Context ctx, Intent intent) {
 			int state = intent.getIntExtra(EXTRA_STATE, 0);
diff --git a/briar-android/src/org/briarproject/android/util/AuthorView.java b/briar-android/src/org/briarproject/android/util/AuthorView.java
new file mode 100644
index 0000000000000000000000000000000000000000..c98c889a305b28ec67ca4486c35375fa3e8609a7
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/util/AuthorView.java
@@ -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;
+		}
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/util/TrustIndicatorView.java b/briar-android/src/org/briarproject/android/util/TrustIndicatorView.java
index a9ea3c8fb24653f07cbfc09252813550abad1253..97d8e7763609a175d86a9e18199098239720bfbf 100644
--- a/briar-android/src/org/briarproject/android/util/TrustIndicatorView.java
+++ b/briar-android/src/org/briarproject/android/util/TrustIndicatorView.java
@@ -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();
 	}
 
 }
diff --git a/briar-api/src/org/briarproject/api/blogs/BlogManager.java b/briar-api/src/org/briarproject/api/blogs/BlogManager.java
index ac8ea516b47f4269a51f8fca2c0d68a4d0fc1437..2637779d46e248687824ca3518d9906cd0368b6a 100644
--- a/briar-api/src/org/briarproject/api/blogs/BlogManager.java
+++ b/briar-api/src/org/briarproject/api/blogs/BlogManager.java
@@ -56,7 +56,7 @@ public interface BlogManager {
 	BlogPostHeader getPostHeader(GroupId g, MessageId m) throws DbException;
 
 	/** Returns the body of the blog post with the given ID. */
-	byte[] getPostBody(MessageId m) throws DbException;
+	String getPostBody(MessageId m) throws DbException;
 
 	/** Returns the headers of all posts in the given blog. */
 	Collection<BlogPostHeader> getPostHeaders(GroupId g) throws DbException;
diff --git a/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java
index 2720910e0c1f1ca77a7f3e93fc2950f582215fc3..92f7e9c8a0d6ae289267ef5d105438fb841e16eb 100644
--- a/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java
+++ b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java
@@ -516,7 +516,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
 	}
 
 	@Override
-	public byte[] getPostBody(MessageId m) throws DbException {
+	public String getPostBody(MessageId m) throws DbException {
 		try {
 			BdfList message = clientHelper.getMessageAsList(m);
 			if (message == null) throw new DbException();
@@ -526,15 +526,14 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
 		}
 	}
 
-	// TODO directly return String (#598)
-	private byte[] getPostBody(BdfList message) throws FormatException {
+	private String getPostBody(BdfList message) throws FormatException {
 		MessageType type = MessageType.valueOf(message.getLong(0).intValue());
 		if (type == POST) {
 			// type, body, signature
-			return StringUtils.toUtf8(message.getString(1));
+			return message.getString(1);
 		} else if (type == WRAPPED_POST) {
 			// type, p_group descriptor, p_timestamp, p_content, p_signature
-			return StringUtils.toUtf8(message.getString(3));
+			return message.getString(3);
 		} else {
 			throw new FormatException();
 		}