From 307e124ee84a3ec74c33d4c7ad7aef7ae4088e49 Mon Sep 17 00:00:00 2001
From: Torsten Grote <t@grobox.de>
Date: Mon, 5 Sep 2016 20:21:11 -0300
Subject: [PATCH] Make the blog post pager usable for the feed and individual
 blogs

---
 .../layout/activity_fragment_container.xml    |  12 +-
 .../res/layout/fragment_blog_post.xml         |   7 +-
 .../res/layout/fragment_blog_post_pager.xml   |   6 +
 .../android/ActivityComponent.java            |   7 +
 .../android/NavDrawerActivity.java            |   5 +-
 .../android/blogs/BasePostFragment.java       |  99 +++++++
 .../android/blogs/BasePostPagerFragment.java  | 187 +++++++++++++
 .../android/blogs/BlogActivity.java           | 258 ++----------------
 .../android/blogs/BlogFragment.java           |  48 +++-
 .../android/blogs/BlogPostFragment.java       |  90 +-----
 .../android/blogs/BlogPostPagerFragment.java  |  66 +++++
 .../android/blogs/FeedFragment.java           |   7 +-
 .../android/blogs/FeedPostFragment.java       |  85 ++++++
 .../android/blogs/FeedPostPagerFragment.java  |  65 +++++
 14 files changed, 614 insertions(+), 328 deletions(-)
 create mode 100644 briar-android/res/layout/fragment_blog_post_pager.xml
 create mode 100644 briar-android/src/org/briarproject/android/blogs/BasePostFragment.java
 create mode 100644 briar-android/src/org/briarproject/android/blogs/BasePostPagerFragment.java
 create mode 100644 briar-android/src/org/briarproject/android/blogs/BlogPostPagerFragment.java
 create mode 100644 briar-android/src/org/briarproject/android/blogs/FeedPostFragment.java
 create mode 100644 briar-android/src/org/briarproject/android/blogs/FeedPostPagerFragment.java

diff --git a/briar-android/res/layout/activity_fragment_container.xml b/briar-android/res/layout/activity_fragment_container.xml
index e6c20760fb..8bf59ee5ac 100644
--- a/briar-android/res/layout/activity_fragment_container.xml
+++ b/briar-android/res/layout/activity_fragment_container.xml
@@ -3,4 +3,14 @@
 	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
+	android:layout_height="match_parent">
+
+	<ProgressBar
+		android:id="@+id/progressBar"
+		style="?android:attr/progressBarStyleLarge"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_gravity="center"
+		android:visibility="invisible"/>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/briar-android/res/layout/fragment_blog_post.xml b/briar-android/res/layout/fragment_blog_post.xml
index 7a0df0d0b5..bc1b87c2ab 100644
--- a/briar-android/res/layout/fragment_blog_post.xml
+++ b/briar-android/res/layout/fragment_blog_post.xml
@@ -6,7 +6,12 @@
 
 	<FrameLayout
 		android:layout_width="wrap_content"
-		android:layout_height="wrap_content">
+		android:layout_height="wrap_content"
+		android:descendantFocusability="beforeDescendants"
+		android:focusable="true"
+		android:focusableInTouchMode="true">
+		<!-- Above Focusability attributes prevent automatic scroll-down,
+		     because body text is selectable -->
 
 		<include
 			android:id="@+id/postLayout"
diff --git a/briar-android/res/layout/fragment_blog_post_pager.xml b/briar-android/res/layout/fragment_blog_post_pager.xml
new file mode 100644
index 0000000000..fe1c2568a1
--- /dev/null
+++ b/briar-android/res/layout/fragment_blog_post_pager.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v4.view.ViewPager
+	android:id="@+id/pager"
+	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/src/org/briarproject/android/ActivityComponent.java b/briar-android/src/org/briarproject/android/ActivityComponent.java
index 2a0d82c4b2..baee29d41e 100644
--- a/briar-android/src/org/briarproject/android/ActivityComponent.java
+++ b/briar-android/src/org/briarproject/android/ActivityComponent.java
@@ -6,8 +6,11 @@ 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.BlogPostPagerFragment;
 import org.briarproject.android.blogs.CreateBlogActivity;
+import org.briarproject.android.blogs.FeedPostFragment;
 import org.briarproject.android.blogs.FeedFragment;
+import org.briarproject.android.blogs.FeedPostPagerFragment;
 import org.briarproject.android.blogs.ReblogActivity;
 import org.briarproject.android.blogs.ReblogFragment;
 import org.briarproject.android.blogs.RssFeedImportActivity;
@@ -92,6 +95,10 @@ public interface ActivityComponent {
 	void inject(BlogFragment fragment);
 
 	void inject(BlogPostFragment fragment);
+	void inject(FeedPostFragment fragment);
+
+	void inject(BlogPostPagerFragment fragment);
+	void inject(FeedPostPagerFragment fragment);
 
 	void inject(ReblogFragment fragment);
 
diff --git a/briar-android/src/org/briarproject/android/NavDrawerActivity.java b/briar-android/src/org/briarproject/android/NavDrawerActivity.java
index 6381bc9cd1..d4bacf55a9 100644
--- a/briar-android/src/org/briarproject/android/NavDrawerActivity.java
+++ b/briar-android/src/org/briarproject/android/NavDrawerActivity.java
@@ -74,7 +74,9 @@ public class NavDrawerActivity extends BriarFragmentActivity implements
 		super.onNewIntent(intent);
 		exitIfStartupFailed(intent);
 		checkAuthorHandle(intent);
-		clearBackStack();
+		// FIXME why was the stack cleared here?
+		// This prevents state from being restored properly
+//		clearBackStack();
 		if (intent.getBooleanExtra(INTENT_FORUMS, false)) {
 			startFragment(ForumListFragment.newInstance());
 		}
@@ -248,7 +250,6 @@ public class NavDrawerActivity extends BriarFragmentActivity implements
 	@Override
 	public void hideLoadingScreen() {
 		drawerLayout.setDrawerLockMode(LOCK_MODE_UNLOCKED);
-		CustomAnimations.animateHeight(toolbar, true, 250);
 		progressViewGroup.setVisibility(INVISIBLE);
 	}
 
diff --git a/briar-android/src/org/briarproject/android/blogs/BasePostFragment.java b/briar-android/src/org/briarproject/android/blogs/BasePostFragment.java
new file mode 100644
index 0000000000..e340a3fcba
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/blogs/BasePostFragment.java
@@ -0,0 +1,99 @@
+package org.briarproject.android.blogs;
+
+import android.os.Bundle;
+import android.support.annotation.CallSuper;
+import android.support.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.briarproject.R;
+import org.briarproject.android.fragment.BaseFragment;
+import org.briarproject.api.db.DbException;
+
+import java.util.logging.Logger;
+
+import static org.briarproject.android.util.AndroidUtils.MIN_RESOLUTION;
+
+public abstract class BasePostFragment extends BaseFragment {
+
+	private final Logger LOG =
+			Logger.getLogger(BasePostFragment.class.getName());
+
+	private View view;
+	private BlogPostViewHolder ui;
+	private BlogPostItem post;
+	private Runnable refresher;
+
+	@CallSuper
+	@Nullable
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,
+			Bundle savedInstanceState) {
+		setHasOptionsMenu(true);
+
+		view = inflater.inflate(R.layout.fragment_blog_post, container,
+				false);
+		ui = new BlogPostViewHolder(view);
+		return view;
+	}
+
+	@CallSuper
+	@Override
+	public void onStart() {
+		super.onStart();
+		startPeriodicUpdate();
+	}
+
+	@Override
+	public void onStop() {
+		super.onStop();
+		stopPeriodicUpdate();
+	}
+
+	@Override
+	public boolean onOptionsItemSelected(final MenuItem item) {
+		switch (item.getItemId()) {
+			case android.R.id.home:
+				getActivity().onBackPressed();
+				return true;
+			default:
+				return super.onOptionsItemSelected(item);
+		}
+	}
+
+	protected void onBlogPostLoaded(BlogPostItem post) {
+		listener.hideLoadingScreen();
+		this.post = post;
+		ui.bindItem(post);
+	}
+
+	protected void onBlogPostLoadException(DbException exception) {
+		// TODO: Decide how to handle errors in the UI
+		finish();
+	}
+
+	private void startPeriodicUpdate() {
+		refresher = new Runnable() {
+			@Override
+			public void run() {
+				if (ui == null) return;
+				LOG.info("Updating Content...");
+
+				ui.updateDate(post.getTimestamp());
+				view.postDelayed(refresher, MIN_RESOLUTION);
+			}
+		};
+		LOG.info("Adding Handler Callback");
+		view.postDelayed(refresher, MIN_RESOLUTION);
+	}
+
+	private void stopPeriodicUpdate() {
+		if (refresher != null && ui != null) {
+			LOG.info("Removing Handler Callback");
+			view.removeCallbacks(refresher);
+		}
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/blogs/BasePostPagerFragment.java b/briar-android/src/org/briarproject/android/blogs/BasePostPagerFragment.java
new file mode 100644
index 0000000000..b24f6be3a9
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/blogs/BasePostPagerFragment.java
@@ -0,0 +1,187 @@
+package org.briarproject.android.blogs;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.annotation.UiThread;
+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.blogs.BaseController.OnBlogPostAddedListener;
+import org.briarproject.android.controller.handler.UiResultExceptionHandler;
+import org.briarproject.android.fragment.BaseFragment;
+import org.briarproject.api.blogs.BlogPostHeader;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.sync.MessageId;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import static org.briarproject.android.blogs.BasePostPagerFragment.BlogPostPagerAdapter.INVALID_POSITION;
+import static org.briarproject.android.blogs.BlogActivity.POST_ID;
+
+abstract class BasePostPagerFragment extends BaseFragment
+		implements OnBlogPostAddedListener {
+
+	private ViewPager pager;
+	private BlogPostPagerAdapter postPagerAdapter;
+	private MessageId postId;
+
+	@Nullable
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,
+			Bundle state) {
+
+		Bundle args;
+		if (state == null) args = getArguments();
+		else args = state;
+		byte[] p = args.getByteArray(POST_ID);
+		if (p == null)
+			throw new IllegalStateException("No post ID in args");
+		postId = new MessageId(p);
+
+		View v = inflater.inflate(R.layout.fragment_blog_post_pager, container,
+				false);
+		pager = (ViewPager) v.findViewById(R.id.pager);
+		postPagerAdapter = new BlogPostPagerAdapter(getChildFragmentManager());
+		listener.showLoadingScreen(false, R.string.progress_title_please_wait);
+
+		return v;
+	}
+
+	@Override
+	public void onStart() {
+		super.onStart();
+		if (postId == null) {
+			MessageId selected = getSelectedPost();
+			if (selected != null) loadBlogPosts(selected);
+		} else {
+			loadBlogPosts(postId);
+		}
+	}
+
+	@Override
+	public void onSaveInstanceState(Bundle outState) {
+		super.onSaveInstanceState(outState);
+		MessageId selected = getSelectedPost();
+		if (selected != null)
+			outState.putByteArray(POST_ID, selected.getBytes());
+	}
+
+	@Override
+	public void onBlogPostAdded(BlogPostHeader header, boolean local) {
+		loadBlogPost(header);
+	}
+
+	abstract void loadBlogPosts(final MessageId select);
+
+	abstract BaseController getController();
+
+	protected void onBlogPostsLoaded(MessageId select,
+			Collection<BlogPostItem> posts) {
+
+		postId = null;
+		postPagerAdapter.setPosts(posts);
+		selectPost(select);
+	}
+
+	protected void onBlogPostsLoadedException(DbException exception) {
+		// TODO: Decide how to handle errors in the UI
+		finish();
+	}
+
+	private void loadBlogPost(BlogPostHeader header) {
+		getController().loadBlogPost(header,
+				new UiResultExceptionHandler<BlogPostItem, DbException>(
+						getActivity()) {
+					@Override
+					public void onResultUi(BlogPostItem post) {
+						addPost(post);
+					}
+
+					@Override
+					public void onExceptionUi(DbException exception) {
+						// TODO: Decide how to handle errors in the UI
+						finish();
+					}
+				});
+	}
+
+	@Nullable
+	private MessageId getSelectedPost() {
+		if (postPagerAdapter.getCount() == 0) return null;
+		int position = pager.getCurrentItem();
+		return postPagerAdapter.getPost(position).getId();
+	}
+
+	private void selectPost(MessageId m) {
+		int pos = postPagerAdapter.getPostPosition(m);
+		if (pos != INVALID_POSITION) {
+			pager.setAdapter(postPagerAdapter);
+			pager.setCurrentItem(pos);
+		}
+	}
+
+	protected void addPost(BlogPostItem post) {
+		MessageId selected = getSelectedPost();
+		postPagerAdapter.addPost(post);
+		if (selected != null) selectPost(selected);
+	}
+
+	@UiThread
+	static class BlogPostPagerAdapter extends FragmentStatePagerAdapter {
+
+		static final int INVALID_POSITION = -1;
+		private final List<BlogPostItem> posts = new ArrayList<>();
+
+		private BlogPostPagerAdapter(FragmentManager fm) {
+			super(fm);
+		}
+
+		@Override
+		public int getCount() {
+			return posts.size();
+		}
+
+		@Override
+		public Fragment getItem(int position) {
+			BlogPostItem post = posts.get(position);
+			return FeedPostFragment.newInstance(post.getGroupId(), post.getId());
+		}
+
+		private BlogPostItem getPost(int position) {
+			return posts.get(position);
+		}
+
+		private void setPosts(Collection<BlogPostItem> posts) {
+			this.posts.clear();
+			this.posts.addAll(posts);
+			Collections.sort(this.posts);
+			notifyDataSetChanged();
+		}
+
+		private void addPost(BlogPostItem post) {
+			posts.add(post);
+			Collections.sort(posts);
+			notifyDataSetChanged();
+		}
+
+		private int getPostPosition(MessageId m) {
+			int count = getCount();
+			for (int i = 0; i < count; i++) {
+				if (getPost(i).getId().equals(m)) {
+					return i;
+				}
+			}
+			return INVALID_POSITION;
+		}
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogActivity.java b/briar-android/src/org/briarproject/android/blogs/BlogActivity.java
index 249d0ff7cf..34ba66f982 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogActivity.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogActivity.java
@@ -2,13 +2,6 @@ package org.briarproject.android.blogs;
 
 import android.content.Intent;
 import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.annotation.UiThread;
-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.ViewGroup;
 import android.widget.ProgressBar;
 
 import org.briarproject.R;
@@ -16,42 +9,25 @@ import org.briarproject.android.ActivityComponent;
 import org.briarproject.android.BriarActivity;
 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;
 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.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
 
 import javax.inject.Inject;
 
-import static android.view.View.GONE;
+import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
 public class BlogActivity extends BriarActivity implements
-		OnBlogPostAddedListener,
-		OnBlogPostClickListener, BaseFragmentListener {
+		OnBlogPostAddedListener, OnBlogPostClickListener, BaseFragmentListener {
 
 	static final int REQUEST_WRITE_POST = 1;
 	static final int REQUEST_SHARE = 2;
-	public static final String BLOG_NAME = "briar.BLOG_NAME";
+	static final String BLOG_NAME = "briar.BLOG_NAME";
 	static final String IS_NEW_BLOG = "briar.IS_NEW_BLOG";
+	static final String POST_ID = "briar.POST_ID";
 
-	public static final String POST_ID = "briar.POST_ID";
-
-	private GroupId groupId;
 	private ProgressBar progressBar;
-	private ViewPager pager;
-	private BlogPagerAdapter blogPagerAdapter;
-	private BlogPostPagerAdapter postPagerAdapter;
-	private String blogName;
-	private boolean isNew;
-	private MessageId savedPostId;
 
 	@Inject
 	BlogController blogController;
@@ -64,68 +40,50 @@ public class BlogActivity extends BriarActivity implements
 		Intent i = getIntent();
 		byte[] b = i.getByteArrayExtra(GROUP_ID);
 		if (b == null) throw new IllegalStateException("No group ID in intent");
-		groupId = new GroupId(b);
+		GroupId groupId = new GroupId(b);
 		blogController.setGroupId(groupId);
 
 		// Name of the blog
-		blogName = i.getStringExtra(BLOG_NAME);
+		String blogName = i.getStringExtra(BLOG_NAME);
 		if (blogName != null) setTitle(blogName);
 
 		// Was this blog just created?
-		isNew = i.getBooleanExtra(IS_NEW_BLOG, false);
-
-		setContentView(R.layout.activity_blog);
+		boolean isNew = i.getBooleanExtra(IS_NEW_BLOG, false);
 
-		pager = (ViewPager) findViewById(R.id.pager);
+		setContentView(R.layout.activity_fragment_container);
 		progressBar = (ProgressBar) findViewById(R.id.progressBar);
 
-		blogPagerAdapter = new BlogPagerAdapter(getSupportFragmentManager());
-		postPagerAdapter = new BlogPostPagerAdapter(
-				getSupportFragmentManager());
-
-		if (state == null || state.getByteArray(POST_ID) == null) {
-			// The blog fragment has its own progress bar
-			hideLoadingScreen();
-			pager.setAdapter(blogPagerAdapter);
-			savedPostId = null;
-		} else {
-			// Adapter will be set in selectPostInPostPager()
-			savedPostId = new MessageId(state.getByteArray(POST_ID));
+		if (state == null) {
+			BlogFragment f = BlogFragment.newInstance(groupId, blogName, isNew);
+			getSupportFragmentManager().beginTransaction()
+					.replace(R.id.fragmentContainer, f, f.getUniqueTag())
+					.commit();
 		}
 	}
 
 	@Override
 	public void onResume() {
 		super.onResume();
-		if (savedPostId == null) {
-			MessageId selected = getSelectedPostInPostPager();
-			if (selected != null) loadBlogPosts(selected);
-		} else {
-			loadBlogPosts(savedPostId);
-		}
 	}
 
 	@Override
-	public void onSaveInstanceState(Bundle outState) {
-		super.onSaveInstanceState(outState);
-		MessageId selected = getSelectedPostInPostPager();
-		if (selected != null)
-			outState.putByteArray(POST_ID, selected.getBytes());
+	public void injectActivity(ActivityComponent component) {
+		component.inject(this);
 	}
 
 	@Override
-	public void onBackPressed() {
-		if (pager.getAdapter() == postPagerAdapter) {
-			pager.setAdapter(blogPagerAdapter);
-			savedPostId = null;
-		} else {
-			super.onBackPressed();
-		}
+	public void onBlogPostAdded(BlogPostHeader header, boolean local) {
+		// all our fragments are implementing and registering that hook,
+		// so we don't need to do that ourselves
 	}
 
 	@Override
-	public void injectActivity(ActivityComponent component) {
-		component.inject(this);
+	public void onBlogPostClick(BlogPostItem post) {
+		BlogPostPagerFragment f = BlogPostPagerFragment.newInstance(post.getId());
+		getSupportFragmentManager().beginTransaction()
+				.replace(R.id.fragmentContainer, f, f.getUniqueTag())
+				.addToBackStack(f.getUniqueTag())
+				.commit();
 	}
 
 	@Override
@@ -135,176 +93,10 @@ public class BlogActivity extends BriarActivity implements
 
 	@Override
 	public void hideLoadingScreen() {
-		progressBar.setVisibility(GONE);
+		progressBar.setVisibility(INVISIBLE);
 	}
 
 	@Override
 	public void onFragmentCreated(String tag) {
-
-	}
-
-	@Override
-	public void onBlogPostClick(BlogPostItem post) {
-		loadBlogPosts(post.getId());
-	}
-
-	private void loadBlogPosts(final MessageId select) {
-		blogController.loadBlogPosts(
-				new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>(
-						this) {
-					@Override
-					public void onResultUi(Collection<BlogPostItem> posts) {
-						hideLoadingScreen();
-						savedPostId = null;
-						postPagerAdapter.setPosts(posts);
-						selectPostInPostPager(select);
-					}
-
-					@Override
-					public void onExceptionUi(DbException exception) {
-						// TODO: Decide how to handle errors in the UI
-						finish();
-					}
-				});
-	}
-
-	@Override
-	public void onBlogPostAdded(BlogPostHeader header, boolean local) {
-		if (pager.getAdapter() == postPagerAdapter) {
-			loadBlogPost(header);
-		} else {
-			BlogFragment f = blogPagerAdapter.getFragment();
-			if (f != null && f.isVisible()) f.onBlogPostAdded(header, local);
-		}
-	}
-
-	private void loadBlogPost(BlogPostHeader header) {
-		blogController.loadBlogPost(header,
-				new UiResultExceptionHandler<BlogPostItem, DbException>(this) {
-					@Override
-					public void onResultUi(BlogPostItem post) {
-						addPostToPostPager(post);
-					}
-
-					@Override
-					public void onExceptionUi(DbException exception) {
-						// TODO: Decide how to handle errors in the UI
-						finish();
-					}
-				});
-	}
-
-	@Nullable
-	private MessageId getSelectedPostInPostPager() {
-		if (pager.getAdapter() != postPagerAdapter) return null;
-		if (postPagerAdapter.getCount() == 0) return null;
-		int position = pager.getCurrentItem();
-		return postPagerAdapter.getPost(position).getId();
-	}
-
-	private void selectPostInPostPager(MessageId m) {
-		int count = postPagerAdapter.getCount();
-		for (int i = 0; i < count; i++) {
-			if (postPagerAdapter.getPost(i).getId().equals(m)) {
-				pager.setAdapter(postPagerAdapter);
-				pager.setCurrentItem(i);
-				return;
-			}
-		}
-	}
-
-	private void addPostToPostPager(BlogPostItem post) {
-		MessageId selected = getSelectedPostInPostPager();
-		postPagerAdapter.addPost(post);
-		if (selected != null) selectPostInPostPager(selected);
 	}
-
-	@Override
-	protected void onActivityResult(int requestCode, int resultCode,
-			Intent data) {
-		super.onActivityResult(requestCode, resultCode, data);
-
-		// The BlogPostAddedEvent arrives when the controller is not listening,
-		// so we need to manually reload the blog posts :(
-		if (requestCode == REQUEST_WRITE_POST && resultCode == RESULT_OK) {
-			if (pager.getAdapter() == postPagerAdapter) {
-				MessageId selected = getSelectedPostInPostPager();
-				if (selected != null) loadBlogPosts(selected);
-			} else {
-				BlogFragment f = blogPagerAdapter.getFragment();
-				if (f != null && f.isVisible()) f.loadBlogPosts(true);
-			}
-		}
-	}
-
-	@UiThread
-	private class BlogPagerAdapter extends FragmentStatePagerAdapter {
-
-		private BlogFragment fragment = null;
-
-		private BlogPagerAdapter(FragmentManager fm) {
-			super(fm);
-		}
-
-		@Override
-		public int getCount() {
-			return 1;
-		}
-
-		@Override
-		public Fragment getItem(int position) {
-			return BlogFragment.newInstance(groupId, blogName, isNew);
-		}
-
-		@Override
-		public Object instantiateItem(ViewGroup container, int position) {
-			// save a reference to the single fragment here for later
-			fragment =
-					(BlogFragment) super.instantiateItem(container, position);
-			return fragment;
-		}
-
-		private BlogFragment getFragment() {
-			return fragment;
-		}
-	}
-
-	@UiThread
-	private static class BlogPostPagerAdapter
-			extends FragmentStatePagerAdapter {
-
-		private final List<BlogPostItem> posts = new ArrayList<>();
-
-		private BlogPostPagerAdapter(FragmentManager fm) {
-			super(fm);
-		}
-
-		@Override
-		public int getCount() {
-			return posts.size();
-		}
-
-		@Override
-		public Fragment getItem(int position) {
-			return BlogPostFragment.newInstance(posts.get(position).getId());
-		}
-
-		private BlogPostItem getPost(int position) {
-			return posts.get(position);
-		}
-
-		private void setPosts(Collection<BlogPostItem> posts) {
-			this.posts.clear();
-			this.posts.addAll(posts);
-			Collections.sort(this.posts);
-			notifyDataSetChanged();
-		}
-
-		private void addPost(BlogPostItem post) {
-			posts.add(post);
-			Collections.sort(posts);
-			notifyDataSetChanged();
-		}
-	}
-
 }
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java
index b3b87a113c..0d9a0fe985 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java
@@ -5,8 +5,8 @@ import android.content.Intent;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
 import android.support.design.widget.Snackbar;
-import android.support.v4.app.ActivityCompat;
 import android.support.v4.app.ActivityOptionsCompat;
+import android.support.v4.content.ContextCompat;
 import android.support.v7.app.AlertDialog;
 import android.support.v7.widget.LinearLayoutManager;
 import android.view.LayoutInflater;
@@ -59,6 +59,7 @@ public class BlogFragment extends BaseFragment implements
 	private BlogPostAdapter adapter;
 	private BriarRecyclerView list;
 	private MenuItem writeButton, deleteButton;
+	private boolean isMyBlog = false, canDeleteBlog = false;
 
 	static BlogFragment newInstance(GroupId groupId, String name,
 			boolean isNew) {
@@ -114,7 +115,7 @@ public class BlogFragment extends BaseFragment implements
 	@Override
 	public void injectFragment(ActivityComponent component) {
 		component.inject(this);
-		blogController.setGroupId(groupId);
+		blogController.setOnBlogPostAddedListener(this);
 	}
 
 	@Override
@@ -141,7 +142,9 @@ public class BlogFragment extends BaseFragment implements
 	public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
 		inflater.inflate(R.menu.blogs_blog_actions, menu);
 		writeButton = menu.findItem(R.id.action_write_blog_post);
+		if (isMyBlog) writeButton.setVisible(true);
 		deleteButton = menu.findItem(R.id.action_blog_delete);
+		if (canDeleteBlog) deleteButton.setVisible(true);
 
 		super.onCreateOptionsMenu(menu, inflater);
 	}
@@ -161,8 +164,8 @@ public class BlogFragment extends BaseFragment implements
 						new Intent(getActivity(), WriteBlogPostActivity.class);
 				i.putExtra(GROUP_ID, groupId.getBytes());
 				i.putExtra(BLOG_NAME, blogName);
-				ActivityCompat.startActivityForResult(getActivity(), i,
-						REQUEST_WRITE_POST, options.toBundle());
+				startActivityForResult(i, REQUEST_WRITE_POST,
+						options.toBundle());
 				return true;
 			case R.id.action_blog_share:
 				Intent i2 = new Intent(getActivity(), ShareBlogActivity.class);
@@ -190,9 +193,10 @@ public class BlogFragment extends BaseFragment implements
 		super.onActivityResult(request, result, data);
 
 		if (request == REQUEST_WRITE_POST && result == RESULT_OK) {
-			displaySnackbar(R.string.blogs_blog_post_created);
+			displaySnackbar(R.string.blogs_blog_post_created, true);
+			loadBlogPosts(true);
 		} else if (request == REQUEST_SHARE && result == RESULT_OK) {
-			displaySnackbar(R.string.blogs_sharing_snackbar);
+			displaySnackbar(R.string.blogs_sharing_snackbar, true);
 		}
 	}
 
@@ -211,9 +215,9 @@ public class BlogFragment extends BaseFragment implements
 						adapter.add(post);
 						if (local) {
 							list.scrollToPosition(0);
-							displaySnackbar(R.string.blogs_blog_post_created);
+							displaySnackbar(R.string.blogs_blog_post_created, false);
 						} else {
-							displaySnackbar(R.string.blogs_blog_post_received);
+							displaySnackbar(R.string.blogs_blog_post_received, true);
 						}
 					}
 
@@ -243,13 +247,13 @@ public class BlogFragment extends BaseFragment implements
 					@Override
 					public void onExceptionUi(DbException exception) {
 						// TODO: Decide how to handle errors in the UI
-						getActivity().finish();
+						finish();
 					}
 				});
 	}
 
 	private void checkIfThisIsMyBlog() {
-		blogController.canDeleteBlog(
+		blogController.isMyBlog(
 				new UiResultExceptionHandler<Boolean, DbException>(
 						getActivity()) {
 					@Override
@@ -262,7 +266,7 @@ public class BlogFragment extends BaseFragment implements
 					@Override
 					public void onExceptionUi(DbException exception) {
 						// TODO: Decide how to handle errors in the UI
-						getActivity().finish();
+						finish();
 					}
 				});
 	}
@@ -281,25 +285,39 @@ public class BlogFragment extends BaseFragment implements
 					@Override
 					public void onExceptionUi(DbException exception) {
 						// TODO: Decide how to handle errors in the UI
-						getActivity().finish();
+						finish();
 					}
 				});
 	}
 
 	private void showWriteButton() {
+		isMyBlog = true;
 		if (writeButton != null)
 			writeButton.setVisible(true);
 	}
 
 	private void showDeleteButton() {
+		canDeleteBlog = true;
 		if (deleteButton != null)
 			deleteButton.setVisible(true);
 	}
 
-	private void displaySnackbar(int stringId) {
+	private void displaySnackbar(int stringId, boolean scroll) {
 		Snackbar snackbar =
-				Snackbar.make(list, stringId, Snackbar.LENGTH_SHORT);
+				Snackbar.make(list, stringId, Snackbar.LENGTH_LONG);
 		snackbar.getView().setBackgroundResource(R.color.briar_primary);
+		if (scroll) {
+			View.OnClickListener onClick = new View.OnClickListener() {
+				@Override
+				public void onClick(View v) {
+					list.smoothScrollToPosition(0);
+				}
+			};
+			snackbar.setActionTextColor(ContextCompat
+					.getColor(getContext(),
+							R.color.briar_button_positive));
+			snackbar.setAction(R.string.blogs_blog_post_scroll_to, onClick);
+		}
 		snackbar.show();
 	}
 
@@ -335,7 +353,7 @@ public class BlogFragment extends BaseFragment implements
 					@Override
 					public void onExceptionUi(DbException exception) {
 						// TODO: Decide how to handle errors in the UI
-						getActivity().finish();
+						finish();
 					}
 				});
 	}
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java
index 926ce7f37d..b70a360625 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java
@@ -3,35 +3,23 @@ package org.briarproject.android.blogs;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
 import android.view.LayoutInflater;
-import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 
-import org.briarproject.R;
 import org.briarproject.android.ActivityComponent;
 import org.briarproject.android.controller.handler.UiResultExceptionHandler;
-import org.briarproject.android.fragment.BaseFragment;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.sync.MessageId;
 
-import java.util.logging.Logger;
-
 import javax.inject.Inject;
 
-import static org.briarproject.android.util.AndroidUtils.MIN_RESOLUTION;
+import static org.briarproject.android.blogs.BlogActivity.POST_ID;
 
-public class BlogPostFragment extends BaseFragment {
+public class BlogPostFragment extends BasePostFragment {
 
 	public final static String TAG = BlogPostFragment.class.getName();
 
-	private static final Logger LOG = Logger.getLogger(TAG);
-	private static final String BLOG_POST_ID = "briar.BLOG_POST_ID";
-
-	private View view;
 	private MessageId postId;
-	private BlogPostViewHolder ui;
-	private BlogPostItem post;
-	private Runnable refresher;
 
 	@Inject
 	BlogController blogController;
@@ -40,7 +28,7 @@ public class BlogPostFragment extends BaseFragment {
 		BlogPostFragment f = new BlogPostFragment();
 
 		Bundle bundle = new Bundle();
-		bundle.putByteArray(BLOG_POST_ID, postId.getBytes());
+		bundle.putByteArray(POST_ID, postId.getBytes());
 
 		f.setArguments(bundle);
 		return f;
@@ -50,16 +38,18 @@ public class BlogPostFragment extends BaseFragment {
 	@Override
 	public View onCreateView(LayoutInflater inflater, ViewGroup container,
 			Bundle savedInstanceState) {
-		setHasOptionsMenu(true);
 
-		byte[] b = getArguments().getByteArray(BLOG_POST_ID);
-		if (b == null) throw new IllegalStateException("No post ID in args");
-		postId = new MessageId(b);
+		Bundle args = getArguments();
+		byte[] p = args.getByteArray(POST_ID);
+		if (p == null) throw new IllegalStateException("No post ID in args");
+		postId = new MessageId(p);
+
+		return super.onCreateView(inflater, container, savedInstanceState);
+	}
 
-		view = inflater.inflate(R.layout.fragment_blog_post, container,
-				false);
-		ui = new BlogPostViewHolder(view);
-		return view;
+	@Override
+	public String getUniqueTag() {
+		return TAG;
 	}
 
 	@Override
@@ -75,62 +65,12 @@ public class BlogPostFragment extends BaseFragment {
 						getActivity()) {
 					@Override
 					public void onResultUi(BlogPostItem post) {
-						listener.hideLoadingScreen();
-						BlogPostFragment.this.post = post;
-						ui.bindItem(post);
-						startPeriodicUpdate();
+						onBlogPostLoaded(post);
 					}
-
 					@Override
 					public void onExceptionUi(DbException exception) {
-						// TODO: Decide how to handle errors in the UI
-						finish();
+						onBlogPostLoadException(exception);
 					}
 				});
 	}
-
-	@Override
-	public void onStop() {
-		super.onStop();
-		stopPeriodicUpdate();
-	}
-
-	@Override
-	public boolean onOptionsItemSelected(final MenuItem item) {
-		switch (item.getItemId()) {
-			case android.R.id.home:
-				getActivity().onBackPressed();
-				return true;
-			default:
-				return super.onOptionsItemSelected(item);
-		}
-	}
-
-	@Override
-	public String getUniqueTag() {
-		return TAG;
-	}
-
-	private void startPeriodicUpdate() {
-		refresher = new Runnable() {
-			@Override
-			public void run() {
-				if (ui == null) return;
-				LOG.info("Updating Content...");
-
-				ui.updateDate(post.getTimestamp());
-				view.postDelayed(refresher, MIN_RESOLUTION);
-			}
-		};
-		LOG.info("Adding Handler Callback");
-		view.postDelayed(refresher, MIN_RESOLUTION);
-	}
-
-	private void stopPeriodicUpdate() {
-		if (refresher != null && ui != null) {
-			LOG.info("Removing Handler Callback");
-			view.removeCallbacks(refresher);
-		}
-	}
-
 }
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostPagerFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogPostPagerFragment.java
new file mode 100644
index 0000000000..54e646f9cf
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/blogs/BlogPostPagerFragment.java
@@ -0,0 +1,66 @@
+package org.briarproject.android.blogs;
+
+import android.os.Bundle;
+
+import org.briarproject.android.ActivityComponent;
+import org.briarproject.android.controller.handler.UiResultExceptionHandler;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.sync.MessageId;
+
+import java.util.Collection;
+
+import javax.inject.Inject;
+
+import static org.briarproject.android.blogs.BlogActivity.POST_ID;
+
+
+public class BlogPostPagerFragment extends BasePostPagerFragment {
+
+	public final static String TAG = BlogPostPagerFragment.class.getName();
+
+	@Inject
+	BlogController blogController;
+
+	static BlogPostPagerFragment newInstance(MessageId postId) {
+		BlogPostPagerFragment f = new BlogPostPagerFragment();
+
+		Bundle args = new Bundle();
+		args.putByteArray(POST_ID, postId.getBytes());
+		f.setArguments(args);
+
+		return f;
+	}
+
+	@Override
+	public void injectFragment(ActivityComponent component) {
+		component.inject(this);
+		blogController.setOnBlogPostAddedListener(this);
+	}
+
+	@Override
+	public String getUniqueTag() {
+		return TAG;
+	}
+
+	@Override
+	BaseController getController() {
+		return blogController;
+	}
+
+	void loadBlogPosts(final MessageId select) {
+		blogController.loadBlogPosts(
+				new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>(
+						getActivity()) {
+					@Override
+					public void onResultUi(Collection<BlogPostItem> posts) {
+						onBlogPostsLoaded(select, posts);
+					}
+
+					@Override
+					public void onExceptionUi(DbException exception) {
+						onBlogPostsLoadedException(exception);
+					}
+				});
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/blogs/FeedFragment.java b/briar-android/src/org/briarproject/android/blogs/FeedFragment.java
index 9e6f577487..dbdd111271 100644
--- a/briar-android/src/org/briarproject/android/blogs/FeedFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/FeedFragment.java
@@ -195,7 +195,12 @@ public class FeedFragment extends BaseFragment implements
 
 	@Override
 	public void onBlogPostClick(BlogPostItem post) {
-		// TODO Open detail view of post
+		FeedPostPagerFragment f = FeedPostPagerFragment
+				.newInstance(post.getId());
+		getActivity().getSupportFragmentManager().beginTransaction()
+				.replace(R.id.content_fragment, f, f.getUniqueTag())
+				.addToBackStack(f.getUniqueTag())
+				.commit();
 	}
 
 	@Override
diff --git a/briar-android/src/org/briarproject/android/blogs/FeedPostFragment.java b/briar-android/src/org/briarproject/android/blogs/FeedPostFragment.java
new file mode 100644
index 0000000000..5b4841cd41
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/blogs/FeedPostFragment.java
@@ -0,0 +1,85 @@
+package org.briarproject.android.blogs;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.briarproject.android.ActivityComponent;
+import org.briarproject.android.controller.handler.UiResultExceptionHandler;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+
+import javax.inject.Inject;
+
+import static org.briarproject.android.BriarActivity.GROUP_ID;
+import static org.briarproject.android.blogs.BlogActivity.POST_ID;
+
+public class FeedPostFragment extends BasePostFragment {
+
+	public final static String TAG = FeedPostFragment.class.getName();
+
+	private MessageId postId;
+	private GroupId blogId;
+
+	@Inject
+	FeedController feedController;
+
+	static FeedPostFragment newInstance(GroupId blogId, MessageId postId) {
+		FeedPostFragment f = new FeedPostFragment();
+
+		Bundle bundle = new Bundle();
+		bundle.putByteArray(GROUP_ID, blogId.getBytes());
+		bundle.putByteArray(POST_ID, postId.getBytes());
+
+		f.setArguments(bundle);
+		return f;
+	}
+
+	@Nullable
+	@Override
+	public View onCreateView(LayoutInflater inflater, ViewGroup container,
+			Bundle savedInstanceState) {
+
+		Bundle args = getArguments();
+		byte[] b = args.getByteArray(GROUP_ID);
+		if (b == null) throw new IllegalStateException("No group ID in args");
+		blogId = new GroupId(b);
+
+		byte[] p = args.getByteArray(POST_ID);
+		if (p == null) throw new IllegalStateException("No post ID in args");
+		postId = new MessageId(p);
+
+		return super.onCreateView(inflater, container, savedInstanceState);
+	}
+
+	@Override
+	public void injectFragment(ActivityComponent component) {
+		component.inject(this);
+	}
+
+	@Override
+	public void onStart() {
+		super.onStart();
+		feedController.loadBlogPost(blogId, postId,
+				new UiResultExceptionHandler<BlogPostItem, DbException>(
+						getActivity()) {
+					@Override
+					public void onResultUi(BlogPostItem post) {
+						onBlogPostLoaded(post);
+					}
+					@Override
+					public void onExceptionUi(DbException exception) {
+						onBlogPostLoadException(exception);
+					}
+				});
+	}
+
+	@Override
+	public String getUniqueTag() {
+		return TAG;
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/blogs/FeedPostPagerFragment.java b/briar-android/src/org/briarproject/android/blogs/FeedPostPagerFragment.java
new file mode 100644
index 0000000000..db30ac1f3c
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/blogs/FeedPostPagerFragment.java
@@ -0,0 +1,65 @@
+package org.briarproject.android.blogs;
+
+import android.os.Bundle;
+
+import org.briarproject.android.ActivityComponent;
+import org.briarproject.android.controller.handler.UiResultExceptionHandler;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.sync.MessageId;
+
+import java.util.Collection;
+
+import javax.inject.Inject;
+
+import static org.briarproject.android.blogs.BlogActivity.POST_ID;
+
+public class FeedPostPagerFragment extends BasePostPagerFragment {
+
+	public final static String TAG = FeedPostPagerFragment.class.getName();
+
+	@Inject
+	FeedController feedController;
+
+	static FeedPostPagerFragment newInstance(MessageId postId) {
+		FeedPostPagerFragment f = new FeedPostPagerFragment();
+
+		Bundle args = new Bundle();
+		args.putByteArray(POST_ID, postId.getBytes());
+		f.setArguments(args);
+
+		return f;
+	}
+
+	@Override
+	public void injectFragment(ActivityComponent component) {
+		component.inject(this);
+		feedController.setOnBlogPostAddedListener(this);
+	}
+
+	@Override
+	public String getUniqueTag() {
+		return TAG;
+	}
+
+	@Override
+	BaseController getController() {
+		return feedController;
+	}
+
+	void loadBlogPosts(final MessageId select) {
+		feedController.loadBlogPosts(
+				new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>(
+						getActivity()) {
+					@Override
+					public void onResultUi(Collection<BlogPostItem> posts) {
+						onBlogPostsLoaded(select, posts);
+					}
+
+					@Override
+					public void onExceptionUi(DbException exception) {
+						onBlogPostsLoadedException(exception);
+					}
+				});
+	}
+
+}
-- 
GitLab