My Blogs tab: Show all blogs the user created

This does not yet support multiple identities. It just shows blogs
created by the first identity, but can easily be adapted for
multi-identity support.

Closes #410
This commit is contained in:
Torsten Grote
2016-06-07 15:30:26 -03:00
parent 4c4f4ad2d5
commit d05237d2c1
8 changed files with 438 additions and 38 deletions

View File

@@ -0,0 +1,197 @@
package org.briarproject.android.blogs;
import android.content.Context;
import android.support.annotation.Nullable;
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;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.android.util.TextAvatarView;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
class BlogListAdapter extends
RecyclerView.Adapter<BlogListAdapter.BlogViewHolder> {
private SortedList<BlogListItem> blogs = new SortedList<>(
BlogListItem.class, new SortedList.Callback<BlogListItem>() {
@Override
public int compare(BlogListItem a, BlogListItem b) {
if (a == b) return 0;
// The blog with the newest message comes first
long aTime = a.getTimestamp(), bTime = b.getTimestamp();
if (aTime > bTime) return -1;
if (aTime < bTime) return 1;
// Break ties by blog name
String aName = a.getName();
String bName = b.getName();
return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
}
@Override
public void onInserted(int position, int count) {
notifyItemRangeInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
notifyItemRangeRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count) {
notifyItemRangeChanged(position, count);
}
@Override
public boolean areContentsTheSame(BlogListItem a, BlogListItem b) {
return a.getBlog().equals(b.getBlog()) &&
a.getTimestamp() == b.getTimestamp() &&
a.getUnreadCount() == b.getUnreadCount();
}
@Override
public boolean areItemsTheSame(BlogListItem a, BlogListItem b) {
return a.getBlog().equals(b.getBlog());
}
});
private final Context ctx;
BlogListAdapter(Context ctx) {
this.ctx = ctx;
}
@Override
public BlogViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(ctx).inflate(
R.layout.list_item_blog, parent, false);
return new BlogViewHolder(v);
}
@Override
public void onBindViewHolder(BlogViewHolder ui, int position) {
final BlogListItem item = getItem(position);
// Avatar
ui.avatar.setText(item.getName().substring(0, 1));
ui.avatar.setBackgroundBytes(item.getBlog().getId().getBytes());
ui.avatar.setUnreadCount(item.getUnreadCount());
// Blog Name
ui.name.setText(item.getName());
// Post Count
int postCount = item.getPostCount();
ui.postCount.setText(ctx.getResources()
.getQuantityString(R.plurals.posts, postCount, postCount));
ui.postCount.setTextColor(
ContextCompat.getColor(ctx, R.color.briar_text_secondary));
// Date and Status
if (item.isEmpty()) {
ui.date.setVisibility(GONE);
ui.avatar.setProblem(true);
ui.status.setText(ctx.getString(R.string.blogs_blog_is_empty));
ui.status.setVisibility(VISIBLE);
} else {
long timestamp = item.getTimestamp();
ui.date.setText(
DateUtils.getRelativeTimeSpanString(ctx, timestamp));
ui.date.setVisibility(VISIBLE);
ui.status.setVisibility(GONE);
}
// Open Blog on Click
ui.layout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO #415
/* Intent i = new Intent(ctx, BlogActivity.class);
Blog b = item.getBlog();
i.putExtra(GROUP_ID, b.getId().getBytes());
i.putExtra(BLOG_NAME, b.getName());
ctx.startActivity(i);
*/ }
});
}
@Override
public int getItemCount() {
return blogs.size();
}
public BlogListItem getItem(int position) {
return blogs.get(position);
}
@Nullable
public BlogListItem getItem(GroupId g) {
for (int i = 0; i < blogs.size(); i++) {
BlogListItem item = blogs.get(i);
if (item.getBlog().getGroup().getId().equals(g)) {
return item;
}
}
return null;
}
public void addAll(Collection<BlogListItem> items) {
blogs.addAll(items);
}
void updateItem(BlogListItem item) {
BlogListItem oldItem = getItem(item.getBlog().getGroup().getId());
int position = blogs.indexOf(oldItem);
blogs.updateItemAt(position, item);
}
public void remove(BlogListItem item) {
blogs.remove(item);
}
public void clear() {
blogs.clear();
}
public boolean isEmpty() {
return blogs.size() == 0;
}
static class BlogViewHolder extends RecyclerView.ViewHolder {
private final ViewGroup layout;
private final TextAvatarView avatar;
private final TextView name;
private final TextView postCount;
private final TextView date;
private final TextView status;
BlogViewHolder(View v) {
super(v);
layout = (ViewGroup) v;
avatar = (TextAvatarView) v.findViewById(R.id.avatarView);
name = (TextView) v.findViewById(R.id.nameView);
postCount = (TextView) v.findViewById(R.id.postCountView);
date = (TextView) v.findViewById(R.id.dateView);
status = (TextView) v.findViewById(R.id.statusView);
}
}
}

View File

@@ -0,0 +1,61 @@
package org.briarproject.android.blogs;
import org.briarproject.api.blogs.Blog;
import org.briarproject.api.blogs.BlogPostHeader;
import java.util.Collection;
class BlogListItem {
private final Blog blog;
private final int postCount;
private final long timestamp;
private final int unread;
BlogListItem(Blog blog, Collection<BlogPostHeader> headers) {
this.blog = blog;
if (headers.isEmpty()) {
postCount = 0;
timestamp = 0;
unread = 0;
} else {
BlogPostHeader newest = null;
long timestamp = -1;
int unread = 0;
for (BlogPostHeader h : headers) {
if (h.getTimestamp() > timestamp) {
timestamp = h.getTimestamp();
newest = h;
}
if (!h.isRead()) unread++;
}
this.postCount = headers.size();
this.timestamp = newest.getTimestamp();
this.unread = unread;
}
}
Blog getBlog() {
return blog;
}
String getName() {
return blog.getName();
}
boolean isEmpty() {
return postCount == 0;
}
int getPostCount() {
return postCount;
}
long getTimestamp() {
return timestamp;
}
int getUnreadCount() {
return unread;
}
}

View File

@@ -5,26 +5,50 @@ 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 android.widget.TextView;
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() {
}
@@ -35,19 +59,30 @@ public class MyBlogsFragment extends BaseFragment {
Bundle savedInstanceState) {
setHasOptionsMenu(true);
View v = inflater.inflate(R.layout.fragment_blogs_my, container,
false);
TextView numView = (TextView) v.findViewById(R.id.num);
numView.setText("My Blogs");
adapter = new BlogListAdapter(getActivity());
return v;
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();
loadBlogs();
}
@Override
@@ -85,4 +120,49 @@ public class MyBlogsFragment extends BaseFragment {
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));
} catch (NoSuchGroupException e) {
// Continue
}
}
displayBlogs(blogs);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Full blog load took " + duration + " ms");
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void displayBlogs(final Collection<BlogListItem> items) {
listener.runOnUiThread(new Runnable() {
@Override
public void run() {
if (items.size() == 0) {
list.showData();
} else {
adapter.addAll(items);
}
}
});
}
}

View File

@@ -104,16 +104,16 @@ class ForumListAdapter extends
// Post Count
int postCount = item.getPostCount();
if (postCount > 0) {
ui.unread.setText(ctx.getResources()
.getQuantityString(R.plurals.forum_posts, postCount,
ui.postCount.setText(ctx.getResources()
.getQuantityString(R.plurals.posts, postCount,
postCount));
ui.unread.setTextColor(
ui.postCount.setTextColor(
ContextCompat
.getColor(ctx, R.color.briar_text_secondary));
} else {
ui.avatar.setProblem(true);
ui.unread.setText(ctx.getString(R.string.forum_no_posts));
ui.unread.setTextColor(
ui.postCount.setText(ctx.getString(R.string.no_posts));
ui.postCount.setTextColor(
ContextCompat
.getColor(ctx, R.color.briar_text_tertiary));
}
@@ -187,7 +187,7 @@ class ForumListAdapter extends
private final ViewGroup layout;
private final TextAvatarView avatar;
private final TextView name;
private final TextView unread;
private final TextView postCount;
private final TextView date;
ForumViewHolder(View v) {
@@ -196,7 +196,7 @@ class ForumListAdapter extends
layout = (ViewGroup) v;
avatar = (TextAvatarView) v.findViewById(R.id.avatarView);
name = (TextView) v.findViewById(R.id.forumNameView);
unread = (TextView) v.findViewById(R.id.unreadView);
postCount = (TextView) v.findViewById(R.id.postCountView);
date = (TextView) v.findViewById(R.id.dateView);
}
}