Skip to content
Snippets Groups Projects
Verified Commit 761525ad authored by Torsten Grote's avatar Torsten Grote
Browse files

Extend BlogActivity to also show individual posts

This allows for swiping left/right to read other posts by using
a ViewPager.

This hasn't been done as a separate activity, but with
fragments, so both can share the `BlogPersistentData` without
needing to reload it.

Closes #428
parent f7d5c1f6
No related branches found
No related tags found
No related merge requests found
Showing
with 921 additions and 117 deletions
<?xml version="1.0" encoding="utf-8"?>
<org.briarproject.android.util.BriarRecyclerView
android:id="@+id/postList"
<FrameLayout
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="match_parent"
app:scrollToEnd="false"
tools:context=".android.blogs.BlogActivity"/>
android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".android.blogs.BlogActivity"/>
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<org.briarproject.android.util.BriarRecyclerView
android:id="@+id/postList"
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="match_parent"
app:scrollToEnd="false"
tools:context=".android.blogs.BlogActivity"/>
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/margin_activity_horizontal">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatar"
style="@style/BriarAvatar"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginRight="@dimen/margin_medium"
tools:src="@drawable/ic_launcher"/>
<TextView
android:id="@+id/authorName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/avatar"
android:layout_toEndOf="@+id/avatar"
android:layout_toRightOf="@+id/avatar"
android:textSize="@dimen/text_size_tiny"
tools:text="Author Name"/>
<TextView
android:id="@+id/date"
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:textSize="@dimen/text_size_tiny"
tools:text="yesterday"/>
<org.briarproject.android.util.TrustIndicatorView
android:id="@+id/trustIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_small"
android:layout_toRightOf="@+id/authorName"
tools:src="@drawable/trust_indicator_verified"/>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/avatar"
android:layout_marginTop="@dimen/margin_medium"
android:textSize="@dimen/text_size_medium"
android:textStyle="bold"
tools:text="This Is A Blog Post Title"/>
<TextView
android:id="@+id/body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/title"
android:layout_marginTop="@dimen/margin_medium"
tools:text="Body of Blog Post. This could be insanely large or just a short text as well."/>
</RelativeLayout>
</ScrollView>
......@@ -267,6 +267,8 @@
<string name="blogs_write_blog_post_title_hint">Add a title (optional)</string>
<string name="blogs_write_blog_post_body_hint">Type your blog post here</string>
<string name="blogs_publish_blog_post">Publish</string>
<string name="blogs_blog_failed_to_load">Blog failed to load</string>
<string name="blogs_blog_post_failed_to_load">Blog Post failed to load</string>
<string name="blogs_blog_list">Blog List</string>
<string name="blogs_available_blogs">Available Blogs</string>
......
......@@ -3,6 +3,8 @@ package org.briarproject.android;
import android.app.Activity;
import org.briarproject.android.blogs.BlogActivity;
import org.briarproject.android.blogs.BlogFragment;
import org.briarproject.android.blogs.BlogPostFragment;
import org.briarproject.android.blogs.CreateBlogActivity;
import org.briarproject.android.blogs.MyBlogsFragment;
import org.briarproject.android.contact.ContactListFragment;
......@@ -73,6 +75,10 @@ public interface ActivityComponent {
void inject(WriteBlogPostActivity activity);
void inject(BlogFragment fragment);
void inject(BlogPostFragment fragment);
void inject(SettingsActivity activity);
void inject(ChangePasswordActivity activity);
......
......@@ -4,6 +4,8 @@ import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import org.briarproject.android.blogs.BlogController;
import org.briarproject.android.blogs.BlogControllerImpl;
import org.briarproject.android.controller.BriarController;
import org.briarproject.android.controller.BriarControllerImpl;
import org.briarproject.android.controller.ConfigController;
......@@ -107,6 +109,13 @@ public class ActivityModule {
return forumController;
}
@ActivityScope
@Provides
BlogController provideBlogController(BlogControllerImpl blogController) {
activity.addLifecycleController(blogController);
return blogController;
}
@ActivityScope
@Provides
protected NavDrawerController provideNavDrawerController(
......
......@@ -5,6 +5,7 @@ import org.briarproject.CoreModule;
import org.briarproject.android.api.AndroidExecutor;
import org.briarproject.android.api.AndroidNotificationManager;
import org.briarproject.android.api.ReferenceManager;
import org.briarproject.android.blogs.BlogPersistentData;
import org.briarproject.android.forum.ForumPersistentData;
import org.briarproject.android.report.BriarReportSender;
import org.briarproject.api.blogs.BlogManager;
......@@ -118,6 +119,8 @@ public interface AndroidComponent extends CoreEagerSingletons {
ForumPersistentData forumPersistentData();
BlogPersistentData blogPersistentData();
@IoExecutor
Executor ioExecutor();
......
......@@ -4,6 +4,7 @@ import android.app.Application;
import org.briarproject.android.api.AndroidNotificationManager;
import org.briarproject.android.api.ReferenceManager;
import org.briarproject.android.blogs.BlogPersistentData;
import org.briarproject.android.forum.ForumPersistentData;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.PublicKey;
......@@ -143,4 +144,10 @@ public class AppModule {
ForumPersistentData provideForumPersistence() {
return new ForumPersistentData();
}
@Provides
@Singleton
BlogPersistentData provideBlogPersistence() {
return new BlogPersistentData();
}
}
......@@ -2,180 +2,260 @@ package org.briarproject.android.blogs;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
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 android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.util.BriarRecyclerView;
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.android.blogs.BlogController.BlogPostListener;
import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener;
import org.briarproject.android.controller.handler.UiResultHandler;
import org.briarproject.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.logging.Logger;
import javax.inject.Inject;
import static android.support.design.widget.Snackbar.LENGTH_LONG;
import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_SHORT;
public class BlogActivity extends BriarActivity {
public class BlogActivity extends BriarActivity implements BlogPostListener,
OnBlogPostClickListener, BaseFragmentListener {
static final int REQUEST_WRITE_POST = 1;
static final String BLOG_NAME = "briar.BLOG_NAME";
static final String IS_MY_BLOG = "briar.IS_MY_BLOG";
static final String IS_NEW_BLOG = "briar.IS_NEW_BLOG";
private static final int WRITE_POST = 1;
private static final String BLOG_PAGER_ADAPTER = "briar.BLOG_PAGER_ADAPTER";
private static final Logger LOG =
Logger.getLogger(BlogActivity.class.getName());
private BlogPostAdapter adapter;
private BriarRecyclerView list;
private ProgressBar progressBar;
private ViewPager pager;
private BlogPagerAdapter blogPagerAdapter;
private BlogPostPagerAdapter postPagerAdapter;
private String blogName;
private boolean myBlog;
private boolean myBlog, isNew;
// Fields that are accessed from background threads must be volatile
private volatile GroupId groupId = null;
private volatile boolean scrollToTop = false;
@Inject
volatile BlogManager blogManager;
BlogController blogController;
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_blog);
// GroupId from Intent
Intent i = getIntent();
byte[] b = i.getByteArrayExtra(GROUP_ID);
if (b == null) throw new IllegalStateException("No Group in intent.");
groupId = new GroupId(b);
// Name of the Blog from Intent
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);
isNew = i.getBooleanExtra(IS_NEW_BLOG, false);
adapter = new BlogPostAdapter(this, groupId, blogName);
list = (BriarRecyclerView) this.findViewById(R.id.postList);
list.setLayoutManager(new LinearLayoutManager(this));
list.setAdapter(adapter);
if (myBlog) {
list.setEmptyText(
getString(R.string.blogs_my_blogs_blog_empty_state));
setContentView(R.layout.activity_blog);
pager = (ViewPager) findViewById(R.id.pager);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
hideLoadingScreen();
blogPagerAdapter = new BlogPagerAdapter(getSupportFragmentManager());
if (state == null || state.getBoolean(BLOG_PAGER_ADAPTER, true)) {
pager.setAdapter(blogPagerAdapter);
} else {
list.setEmptyText(getString(R.string.blogs_other_blog_empty_state));
// this initializes and restores the postPagerAdapter
loadBlogPosts();
}
}
// show snackbar if this blog was just created
boolean isNew = i.getBooleanExtra(IS_NEW_BLOG, false);
if (isNew) {
Snackbar s = Snackbar.make(list, R.string.blogs_my_blogs_created,
LENGTH_LONG);
s.getView().setBackgroundResource(R.color.briar_primary);
s.show();
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// remember which adapter we had active
outState.putBoolean(BLOG_PAGER_ADAPTER,
pager.getAdapter() == blogPagerAdapter);
}
@Override
public void onBackPressed() {
if (pager.getAdapter() == postPagerAdapter) {
pager.setAdapter(blogPagerAdapter);
} else {
super.onBackPressed();
}
}
@Override
public void onResume() {
super.onResume();
loadBlogPosts();
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (myBlog) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.blogs_my_blog_actions, menu);
}
return super.onCreateOptionsMenu(menu);
public void showLoadingScreen(boolean isBlocking, int stringId) {
progressBar.setVisibility(VISIBLE);
}
private void showLoadingScreen() {
showLoadingScreen(false, 0);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.action_write_blog_post:
Intent i = new Intent(this, WriteBlogPostActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes());
i.putExtra(BLOG_NAME, blogName);
ActivityOptionsCompat options =
makeCustomAnimation(this, android.R.anim.slide_in_left,
android.R.anim.slide_out_right);
ActivityCompat.startActivityForResult(this, i, WRITE_POST,
options.toBundle());
return true;
default:
return super.onOptionsItemSelected(item);
}
public void hideLoadingScreen() {
progressBar.setVisibility(GONE);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == WRITE_POST && resultCode == RESULT_OK) {
scrollToTop = true;
}
public void onFragmentCreated(String tag) {
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
public void onBlogPostClick(final int position) {
loadBlogPosts(position, true);
}
private void loadBlogPosts() {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
// load blog posts
long now = System.currentTimeMillis();
Collection<BlogPostItem> posts = new ArrayList<>();
try {
Collection<BlogPostHeader> header =
blogManager.getPostHeaders(groupId);
for (BlogPostHeader h : header) {
posts.add(new BlogPostItem(h));
loadBlogPosts(0, false);
}
private void loadBlogPosts(final int position, final boolean setItem) {
showLoadingScreen();
blogController
.loadBlog(groupId, false, new UiResultHandler<Boolean>(this) {
@Override
public void onResultUi(Boolean result) {
if (result) {
Collection<BlogPostItem> posts =
blogController.getBlogPosts();
if (postPagerAdapter == null) {
postPagerAdapter = new BlogPostPagerAdapter(
getSupportFragmentManager(),
posts.size());
} else {
postPagerAdapter.setSize(posts.size());
}
pager.setAdapter(postPagerAdapter);
if (setItem) pager.setCurrentItem(position);
} else {
Toast.makeText(BlogActivity.this,
R.string.blogs_blog_post_failed_to_load,
LENGTH_SHORT).show();
}
} catch (NoSuchGroupException e) {
// Continue
}
displayBlogPosts(posts);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Post header load took " + duration + " ms");
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
});
}
private void displayBlogPosts(final Collection<BlogPostItem> items) {
@Override
public void onBlogPostAdded(final BlogPostItem post, final boolean local) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (items.size() == 0) {
list.showData();
} else {
adapter.addAll(items);
if (scrollToTop) list.scrollToPosition(0);
if (blogPagerAdapter != null) {
BlogFragment f = blogPagerAdapter.getFragment();
if (f != null && f.isVisible()) {
f.onBlogPostAdded(post, local);
}
}
if (postPagerAdapter != null) {
postPagerAdapter.onBlogPostAdded();
postPagerAdapter.notifyDataSetChanged();
}
scrollToTop = false;
}
});
}
// TODO listen to events and add new blog posts as they come in
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent 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) {
BlogFragment f = blogPagerAdapter.getFragment();
if (f != null && f.isVisible()) {
f.reload();
}
}
}
private class BlogPagerAdapter extends FragmentStatePagerAdapter {
private BlogFragment fragment = null;
BlogPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
return 1;
}
@Override
public Fragment getItem(int position) {
return BlogFragment.newInstance(groupId, blogName, myBlog, 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;
}
BlogFragment getFragment() {
return fragment;
}
}
private class BlogPostPagerAdapter extends FragmentStatePagerAdapter {
private int size;
BlogPostPagerAdapter(FragmentManager fm, int size) {
super(fm);
this.size = size;
}
@Override
public int getCount() {
return size;
}
@Override
public Fragment getItem(int position) {
MessageId postIdOfPos = blogController.getBlogPostId(position);
return BlogPostFragment.newInstance(groupId, postIdOfPos);
}
void onBlogPostAdded() {
size++;
}
void setSize(int size) {
this.size = size;
}
}
}
package org.briarproject.android.blogs;
import android.support.annotation.Nullable;
import org.briarproject.android.controller.ActivityLifecycleController;
import org.briarproject.android.controller.handler.UiResultHandler;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import java.util.TreeSet;
public interface BlogController extends ActivityLifecycleController {
void loadBlog(final GroupId groupId, final boolean reload,
final UiResultHandler<Boolean> resultHandler);
TreeSet<BlogPostItem> getBlogPosts();
@Nullable
BlogPostItem getBlogPost(MessageId postId);
@Nullable
MessageId getBlogPostId(int position);
interface BlogPostListener {
void onBlogPostAdded(final BlogPostItem post, final boolean local);
}
}
package org.briarproject.android.blogs;
import android.app.Activity;
import android.support.annotation.Nullable;
import org.briarproject.android.controller.DbControllerImpl;
import org.briarproject.android.controller.handler.UiResultHandler;
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.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.TreeSet;
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 {
private static final Logger LOG =
Logger.getLogger(BlogControllerImpl.class.getName());
@Inject
protected Activity activity;
@Inject
protected volatile BlogManager blogManager;
@Inject
protected volatile EventBus eventBus;
@Inject
protected BlogPersistentData data;
private volatile BlogPostListener listener;
@Inject
BlogControllerImpl() {
}
@Override
public void onActivityCreate() {
if (activity instanceof BlogPostListener) {
listener = (BlogPostListener) activity;
} else {
throw new IllegalStateException(
"An activity that injects the BlogController must " +
"implement the BlogPostListener");
}
}
@Override
public void onActivityResume() {
eventBus.addListener(this);
}
@Override
public void onActivityPause() {
eventBus.removeListener(this);
}
@Override
public void onActivityDestroy() {
if (activity.isFinishing()) {
data.clearAll();
}
}
@Override
public void eventOccurred(Event e) {
if (e instanceof BlogPostAddedEvent) {
final BlogPostAddedEvent m = (BlogPostAddedEvent) e;
if (m.getGroupId().equals(data.getGroupId())) {
LOG.info("New blog post added");
final BlogPostHeader header = m.getHeader();
try {
final byte[] body = blogManager.getPostBody(header.getId());
final BlogPostItem post = new BlogPostItem(header, body);
data.addPost(post);
listener.onBlogPostAdded(post, m.isLocal());
} catch (DbException ex) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, ex.toString(), ex);
}
}
} else if (e instanceof GroupRemovedEvent) {
GroupRemovedEvent s = (GroupRemovedEvent) e;
if (s.getGroup().getId().equals(data.getGroupId())) {
LOG.info("Blog removed");
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.finish();
}
});
}
}
}
@Override
public void loadBlog(final GroupId groupId, final boolean reload,
final UiResultHandler<Boolean> resultHandler) {
LOG.info("Loading blog...");
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
if (reload || data.getGroupId() == null ||
!data.getGroupId().equals(groupId)) {
data.setGroupId(groupId);
// load blog posts
long now = System.currentTimeMillis();
Collection<BlogPostItem> posts = new ArrayList<>();
Collection<BlogPostHeader> header =
blogManager.getPostHeaders(groupId);
for (BlogPostHeader h : header) {
byte[] body = blogManager.getPostBody(h.getId());
posts.add(new BlogPostItem(h, body));
}
data.setPosts(posts);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Post header load took " + duration +
" ms");
}
resultHandler.onResult(true);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
resultHandler.onResult(false);
}
}
});
}
@Override
public TreeSet<BlogPostItem> getBlogPosts() {
return data.getBlogPosts();
}
@Override
@Nullable
public BlogPostItem getBlogPost(MessageId id) {
for (BlogPostItem item : getBlogPosts()) {
if (item.getId().equals(id)) return item;
}
return null;
}
@Override
@Nullable
public MessageId getBlogPostId(int position) {
int i = 0;
for (BlogPostItem post : getBlogPosts()) {
if (i == position) return post.getId();
i++;
}
return null;
}
}
package org.briarproject.android.blogs;
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.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 android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.blogs.BlogController.BlogPostListener;
import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener;
import org.briarproject.android.controller.handler.UiResultHandler;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.android.util.BriarRecyclerView;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
import javax.inject.Inject;
import static android.support.design.widget.Snackbar.LENGTH_LONG;
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_WRITE_POST;
public class BlogFragment extends BaseFragment implements BlogPostListener {
public final static String TAG = BlogFragment.class.getName();
@Inject
BlogController blogController;
private GroupId groupId;
private String blogName;
private boolean myBlog;
private BlogPostAdapter adapter;
private BriarRecyclerView list;
static BlogFragment newInstance(GroupId groupId, String name,
boolean myBlog, 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);
return f;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setHasOptionsMenu(true);
Bundle args = getArguments();
byte[] b = args.getByteArray(GROUP_ID);
if (b == null) throw new IllegalStateException("No Group found.");
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);
adapter = new BlogPostAdapter(getActivity(),
(OnBlogPostClickListener) getActivity());
list = (BriarRecyclerView) v.findViewById(R.id.postList);
list.setLayoutManager(new LinearLayoutManager(getActivity()));
list.setAdapter(adapter);
if (myBlog) {
list.setEmptyText(
getString(R.string.blogs_my_blogs_blog_empty_state));
} else {
list.setEmptyText(getString(R.string.blogs_other_blog_empty_state));
}
// show snackbar if this blog was just created
if (isNew) {
Snackbar s = Snackbar.make(list, R.string.blogs_my_blogs_created,
LENGTH_LONG);
s.getView().setBackgroundResource(R.color.briar_primary);
s.show();
// show only once
args.putBoolean(IS_NEW_BLOG, false);
}
return v;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
public void onStart() {
super.onStart();
loadData(false);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (myBlog) {
inflater.inflate(R.menu.blogs_my_blog_actions, menu);
}
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
getActivity().onBackPressed();
return true;
case R.id.action_write_blog_post:
Intent i =
new Intent(getActivity(), WriteBlogPostActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes());
i.putExtra(BLOG_NAME, blogName);
ActivityOptionsCompat options =
makeCustomAnimation(getActivity(),
android.R.anim.slide_in_left,
android.R.anim.slide_out_right);
ActivityCompat.startActivityForResult(getActivity(), i,
REQUEST_WRITE_POST, options.toBundle());
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void onBlogPostAdded(BlogPostItem post, boolean local) {
adapter.add(post);
if (local) list.scrollToPosition(0);
}
private void loadData(final boolean reload) {
blogController.loadBlog(groupId, reload,
new UiResultHandler<Boolean>(getActivity()) {
@Override
public void onResultUi(Boolean result) {
if (result) {
Collection<BlogPostItem> posts =
blogController.getBlogPosts();
if (posts.size() > 0) {
adapter.addAll(posts);
if (reload) list.scrollToPosition(0);
} else {
list.showData();
}
} else {
Toast.makeText(getActivity(),
R.string.blogs_blog_failed_to_load,
LENGTH_SHORT).show();
getActivity().supportFinishAfterTransition();
}
}
});
}
void reload() {
loadData(true);
}
}
package org.briarproject.android.blogs;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
import java.util.TreeSet;
import javax.inject.Inject;
/**
* This class is a singleton that defines the data that should persist, i.e.
* still be present in memory after activity restarts. This class is not thread
* safe.
*/
public class BlogPersistentData {
private volatile GroupId groupId;
private volatile TreeSet<BlogPostItem> posts = new TreeSet<>();
public BlogPersistentData() {
}
public void setGroupId(GroupId groupId) {
this.groupId = groupId;
}
public GroupId getGroupId() {
return groupId;
}
public void setPosts(Collection<BlogPostItem> posts) {
this.posts.clear();
this.posts.addAll(posts);
}
void addPost(BlogPostItem post) {
posts.add(post);
}
TreeSet<BlogPostItem> getBlogPosts() {
return posts;
}
void clearAll() {
groupId = null;
posts.clear();
}
}
......@@ -10,7 +10,6 @@ import android.view.ViewGroup;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.api.sync.GroupId;
import org.briarproject.util.StringUtils;
import java.util.Collection;
......@@ -61,13 +60,11 @@ class BlogPostAdapter extends
});
private final Context ctx;
private final GroupId blogGroupId;
private final String blogTitle;
private final OnBlogPostClickListener listener;
BlogPostAdapter(Context ctx, GroupId blogGroupId, String blogTitle) {
BlogPostAdapter(Context ctx, OnBlogPostClickListener listener) {
this.ctx = ctx;
this.blogGroupId = blogGroupId;
this.blogTitle = blogTitle;
this.listener = listener;
}
@Override
......@@ -78,7 +75,7 @@ class BlogPostAdapter extends
}
@Override
public void onBindViewHolder(BlogPostHolder ui, int position) {
public void onBindViewHolder(final BlogPostHolder ui, int position) {
final BlogPostItem item = getItem(position);
// title
......@@ -95,7 +92,7 @@ class BlogPostAdapter extends
ui.layout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO #428
listener.onBlogPostClick(ui.getAdapterPosition());
}
});
......@@ -154,4 +151,9 @@ class BlogPostAdapter extends
body = (TextView) v.findViewById(R.id.bodyView);
}
}
interface OnBlogPostClickListener {
void onBlogPostClick(int position);
}
}
package org.briarproject.android.blogs;
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.controller.handler.UiResultHandler;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.android.util.TrustIndicatorView;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import org.briarproject.util.StringUtils;
import javax.inject.Inject;
import im.delight.android.identicons.IdenticonDrawable;
import static android.view.View.GONE;
import static android.widget.Toast.LENGTH_SHORT;
import static org.briarproject.android.BriarActivity.GROUP_ID;
public class BlogPostFragment extends BaseFragment {
public final static String TAG = BlogPostFragment.class.getName();
private final static String BLOG_POST_ID = "briar.BLOG_NAME";
private GroupId groupId;
private MessageId postId;
private BlogPostViewHolder ui;
@Inject
BlogController blogController;
static BlogPostFragment newInstance(GroupId groupId, MessageId postId) {
BlogPostFragment f = new BlogPostFragment();
Bundle bundle = new Bundle();
bundle.putByteArray(GROUP_ID, groupId.getBytes());
bundle.putByteArray(BLOG_POST_ID, postId.getBytes());
f.setArguments(bundle);
return f;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setHasOptionsMenu(true);
byte[] b = getArguments().getByteArray(GROUP_ID);
if (b == null) throw new IllegalStateException("No Group found.");
groupId = new GroupId(b);
byte[] p = getArguments().getByteArray(BLOG_POST_ID);
if (p == null) throw new IllegalStateException("No MessageId found.");
postId = new MessageId(p);
View v = inflater.inflate(R.layout.fragment_blog_post, container,
false);
ui = new BlogPostViewHolder(v);
return v;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
public void onStart() {
super.onStart();
blogController.loadBlog(groupId, false,
new UiResultHandler<Boolean>((Activity) listener) {
@Override
public void onResultUi(Boolean result) {
listener.hideLoadingScreen();
if (result) {
BlogPostItem post =
blogController.getBlogPost(postId);
if (post != null) {
bind(post);
}
} else {
Toast.makeText(getActivity(),
R.string.blogs_blog_post_failed_to_load,
LENGTH_SHORT).show();
}
}
});
}
@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 bind(BlogPostItem post) {
Author author = post.getAuthor();
IdenticonDrawable d = new IdenticonDrawable(author.getId().getBytes());
ui.avatar.setImageDrawable(d);
ui.authorName.setText(author.getName());
ui.trust.setTrustLevel(post.getAuthorStatus());
ui.date.setText(
DateUtils.getRelativeTimeSpanString(post.getTimestamp()));
if (post.getTitle() != null) {
ui.title.setText(post.getTitle());
} else {
ui.title.setVisibility(GONE);
}
ui.body.setText(StringUtils.fromUtf8(post.getBody()));
}
private static class BlogPostViewHolder {
private ImageView avatar;
private TextView authorName;
private TrustIndicatorView trust;
private TextView date;
private TextView title;
private TextView body;
BlogPostViewHolder(View v) {
avatar = (ImageView) v.findViewById(R.id.avatar);
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);
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment