Show join messages properly in the threaded conversation

This commit is contained in:
Torsten Grote
2016-10-21 12:36:40 -02:00
parent 349a34ffd8
commit 679b54b2b4
22 changed files with 274 additions and 178 deletions

View File

@@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/layout"
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:id="@+id/forum_cell"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:orientation="horizontal"
android:baselineAligned="false">
<RelativeLayout
android:layout_width="wrap_content"

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/layout"
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/margin_medium"
android:baselineAligned="false"
android:orientation="vertical">
<View
android:id="@+id/top_divider"
style="@style/Divider.ForumList"
android:layout_width="match_parent"
android:layout_height="@dimen/margin_separator"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_small"
android:layout_marginLeft="@dimen/margin_medium"
android:layout_marginRight="@dimen/margin_medium"
android:layout_marginTop="@dimen/margin_medium"
android:orientation="horizontal">
<org.briarproject.android.view.AuthorView
android:id="@+id/author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:persona="commenter"/>
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/margin_medium"
android:gravity="center_vertical"
android:textColor="@color/briar_text_secondary"
android:textIsSelectable="true"
android:textSize="@dimen/text_size_medium"
android:textStyle="italic"
tools:text="@string/groups_member_joined"/>
</LinearLayout>
</LinearLayout>

View File

@@ -166,6 +166,7 @@
<string name="groups_invite_members">Invite Members</string>
<string name="groups_leave">Leave Group</string>
<string name="groups_dissolve">Dissolve Group</string>
<string name="groups_member_joined">joined the group.</string>
<!-- Private Group Invitations -->
<string name="groups_invitations_title">Group Invitations</string>

View File

@@ -120,6 +120,7 @@ public class ActivityModule {
@Provides
protected GroupController provideGroupController(
GroupControllerImpl groupController) {
activity.addLifecycleController(groupController);
return groupController;
}

View File

@@ -18,8 +18,9 @@ import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.android.sharing.ShareForumActivity;
import org.briarproject.android.sharing.ForumSharingStatusActivity;
import org.briarproject.android.sharing.ShareForumActivity;
import org.briarproject.android.threaded.ThreadItemAdapter;
import org.briarproject.android.threaded.ThreadListActivity;
import org.briarproject.android.threaded.ThreadListController;
import org.briarproject.api.db.DbException;
@@ -35,7 +36,7 @@ import static android.widget.Toast.LENGTH_SHORT;
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
public class ForumActivity extends
ThreadListActivity<Forum, ForumItem, ForumPostHeader, NestedForumAdapter> {
ThreadListActivity<Forum, ForumItem, ForumPostHeader, ThreadItemAdapter<ForumItem>> {
private static final int REQUEST_FORUM_SHARED = 3;
@@ -74,9 +75,9 @@ public class ForumActivity extends
}
@Override
protected NestedForumAdapter createAdapter(
protected ThreadItemAdapter<ForumItem> createAdapter(
LinearLayoutManager layoutManager) {
return new NestedForumAdapter(this, layoutManager);
return new ThreadItemAdapter<>(this, layoutManager);
}
@Override

View File

@@ -88,8 +88,8 @@ public class ForumControllerImpl
}
@Override
protected String loadMessageBody(MessageId id) throws DbException {
return StringUtils.fromUtf8(forumManager.getPostBody(id));
protected String loadMessageBody(ForumPostHeader h) throws DbException {
return StringUtils.fromUtf8(forumManager.getPostBody(h.getId()));
}
@Override

View File

@@ -1,28 +0,0 @@
package org.briarproject.android.forum;
import android.support.annotation.UiThread;
import android.support.v7.widget.LinearLayoutManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.R;
import org.briarproject.android.threaded.ThreadItemAdapter;
@UiThread
class NestedForumAdapter extends ThreadItemAdapter<ForumItem> {
NestedForumAdapter(ThreadItemListener<ForumItem> listener,
LinearLayoutManager layoutManager) {
super(listener, layoutManager);
}
@Override
public NestedForumHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item_forum_post, parent, false);
return new NestedForumHolder(v);
}
}

View File

@@ -1,13 +0,0 @@
package org.briarproject.android.forum;
import android.view.View;
import org.briarproject.android.threaded.ThreadItemViewHolder;
public class NestedForumHolder extends ThreadItemViewHolder<ForumItem> {
public NestedForumHolder(View v) {
super(v);
}
}

View File

@@ -2,6 +2,7 @@ package org.briarproject.android.privategroup.conversation;
import android.support.annotation.Nullable;
import org.briarproject.R;
import org.briarproject.android.api.AndroidNotificationManager;
import org.briarproject.android.controller.handler.ResultExceptionHandler;
import org.briarproject.android.threaded.ThreadListControllerImpl;
@@ -17,6 +18,7 @@ import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.privategroup.GroupMessage;
import org.briarproject.api.privategroup.GroupMessageFactory;
import org.briarproject.api.privategroup.GroupMessageHeader;
import org.briarproject.api.privategroup.JoinMessageHeader;
import org.briarproject.api.privategroup.PrivateGroup;
import org.briarproject.api.privategroup.PrivateGroupManager;
import org.briarproject.api.sync.MessageId;
@@ -90,8 +92,13 @@ public class GroupControllerImpl extends
}
@Override
protected String loadMessageBody(MessageId id) throws DbException {
return privateGroupManager.getMessageBody(id);
protected String loadMessageBody(GroupMessageHeader header)
throws DbException {
if (header instanceof JoinMessageHeader) {
return listener.getApplicationContext()
.getString(R.string.groups_member_joined);
}
return privateGroupManager.getMessageBody(header.getId());
}
@Override
@@ -162,6 +169,9 @@ public class GroupControllerImpl extends
@Override
protected GroupMessageItem buildItem(GroupMessageHeader header,
String body) {
if (header instanceof JoinMessageHeader) {
return new JoinMessageItem(header, body);
}
return new GroupMessageItem(header, body);
}

View File

@@ -7,7 +7,9 @@ import android.view.View;
import android.view.ViewGroup;
import org.briarproject.R;
import org.briarproject.android.threaded.BaseThreadItemViewHolder;
import org.briarproject.android.threaded.ThreadItemAdapter;
import org.briarproject.android.threaded.ThreadItemViewHolder;
@UiThread
public class GroupMessageAdapter extends ThreadItemAdapter<GroupMessageItem> {
@@ -18,11 +20,23 @@ public class GroupMessageAdapter extends ThreadItemAdapter<GroupMessageItem> {
}
@Override
public GroupMessageViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
public int getItemViewType(int position) {
GroupMessageItem item = getVisibleItem(position);
if (item instanceof JoinMessageItem) {
return R.layout.list_item_thread_notice;
}
return R.layout.list_item_thread;
}
@Override
public BaseThreadItemViewHolder<GroupMessageItem> onCreateViewHolder(
ViewGroup parent, int type) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item_forum_post, parent, false);
return new GroupMessageViewHolder(v);
.inflate(type, parent, false);
if (type == R.layout.list_item_thread_notice) {
return new BaseThreadItemViewHolder<>(v);
}
return new ThreadItemViewHolder<>(v);
}
}

View File

@@ -8,7 +8,7 @@ import org.briarproject.api.sync.MessageId;
class GroupMessageItem extends ThreadItem {
GroupMessageItem(MessageId messageId, MessageId parentId,
private GroupMessageItem(MessageId messageId, MessageId parentId,
String text, long timestamp, Author author, Status status,
boolean isRead) {
super(messageId, parentId, text, timestamp, author, status, isRead);

View File

@@ -1,14 +0,0 @@
package org.briarproject.android.privategroup.conversation;
import android.view.View;
import org.briarproject.android.threaded.ThreadItemViewHolder;
public class GroupMessageViewHolder
extends ThreadItemViewHolder<GroupMessageItem> {
public GroupMessageViewHolder(View v) {
super(v);
}
}

View File

@@ -0,0 +1,22 @@
package org.briarproject.android.privategroup.conversation;
import org.briarproject.api.privategroup.GroupMessageHeader;
class JoinMessageItem extends GroupMessageItem {
JoinMessageItem(GroupMessageHeader h,
String text) {
super(h, text);
}
@Override
public int getLevel() {
return 0;
}
@Override
public boolean hasDescendants() {
return false;
}
}

View File

@@ -0,0 +1,122 @@
package org.briarproject.android.threaded;
import android.animation.Animator;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.android.threaded.ThreadItemAdapter.ThreadItemListener;
import org.briarproject.android.view.AuthorView;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.util.StringUtils;
@UiThread
@NotNullByDefault
public class BaseThreadItemViewHolder<I extends ThreadItem>
extends RecyclerView.ViewHolder {
private final static int ANIMATION_DURATION = 5000;
private final ViewGroup layout;
private final TextView textView;
private final AuthorView author;
private final View topDivider;
public BaseThreadItemViewHolder(View v) {
super(v);
layout = (ViewGroup) v.findViewById(R.id.layout);
textView = (TextView) v.findViewById(R.id.text);
author = (AuthorView) v.findViewById(R.id.author);
topDivider = v.findViewById(R.id.top_divider);
}
// TODO improve encapsulation, so we don't need to pass the adapter here
@CallSuper
public void bind(final ThreadItemAdapter<I> adapter,
final ThreadItemListener<I> listener, final I item, int pos) {
textView.setText(StringUtils.trim(item.getText()));
if (pos == 0) {
topDivider.setVisibility(View.INVISIBLE);
} else {
topDivider.setVisibility(View.VISIBLE);
}
author.setAuthor(item.getAuthor());
author.setDate(item.getTimestamp());
author.setAuthorStatus(item.getStatus());
if (item.equals(adapter.getReplyItem())) {
layout.setBackgroundColor(ContextCompat
.getColor(getContext(), R.color.forum_cell_highlight));
} else if (item.equals(adapter.getAddedItem())) {
layout.setBackgroundColor(ContextCompat
.getColor(getContext(), R.color.forum_cell_highlight));
animateFadeOut(adapter, adapter.getAddedItem());
adapter.clearAddedItem();
} else {
layout.setBackgroundColor(ContextCompat
.getColor(getContext(), R.color.window_background));
}
}
private void animateFadeOut(final ThreadItemAdapter<I> adapter,
final I addedItem) {
setIsRecyclable(false);
ValueAnimator anim = new ValueAnimator();
adapter.addAnimatingItem(addedItem, anim);
ColorDrawable viewColor = (ColorDrawable) layout.getBackground();
anim.setIntValues(viewColor.getColor(), ContextCompat
.getColor(getContext(), R.color.window_background));
anim.setEvaluator(new ArgbEvaluator());
anim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
setIsRecyclable(true);
adapter.removeAnimatingItem(addedItem);
}
@Override
public void onAnimationCancel(Animator animation) {
setIsRecyclable(true);
adapter.removeAnimatingItem(addedItem);
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
layout.setBackgroundColor(
(Integer) valueAnimator.getAnimatedValue());
}
});
anim.setDuration(ANIMATION_DURATION);
anim.start();
}
protected Context getContext() {
return textView.getContext();
}
}

View File

@@ -1,13 +1,18 @@
package org.briarproject.android.threaded;
import android.support.annotation.UiThread;
import org.briarproject.api.clients.MessageTree.MessageNode;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.Author.Status;
import org.briarproject.api.sync.MessageId;
import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.android.threaded.ThreadItemAdapter.UNDEFINED;
/* This class is not thread safe */
@UiThread
@NotThreadSafe
public abstract class ThreadItem implements MessageNode {
private final MessageId messageId;

View File

@@ -5,7 +5,11 @@ import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.R;
import org.briarproject.android.util.VersionedAdapter;
import org.briarproject.api.sync.MessageId;
@@ -17,8 +21,8 @@ import java.util.Map;
import static android.support.v7.widget.RecyclerView.NO_POSITION;
public abstract class ThreadItemAdapter<I extends ThreadItem>
extends RecyclerView.Adapter<ThreadItemViewHolder<I>>
public class ThreadItemAdapter<I extends ThreadItem>
extends RecyclerView.Adapter<BaseThreadItemViewHolder<I>>
implements VersionedAdapter {
static final int UNDEFINED = -1;
@@ -42,7 +46,15 @@ public abstract class ThreadItemAdapter<I extends ThreadItem>
}
@Override
public void onBindViewHolder(ThreadItemViewHolder<I> ui, int position) {
public BaseThreadItemViewHolder<I> onCreateViewHolder(
ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item_thread, parent, false);
return new ThreadItemViewHolder<>(v);
}
@Override
public void onBindViewHolder(BaseThreadItemViewHolder<I> ui, int position) {
I item = getVisibleItem(position);
if (item == null) return;
listener.onItemVisible(item);

View File

@@ -1,45 +1,30 @@
package org.briarproject.android.threaded;
import android.animation.Animator;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.support.annotation.UiThread;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.android.threaded.ThreadItemAdapter.ThreadItemListener;
import org.briarproject.android.view.AuthorView;
import org.briarproject.util.StringUtils;
import org.briarproject.api.nullsafety.NotNullByDefault;
import static android.view.View.GONE;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
@UiThread
public abstract class ThreadItemViewHolder<I extends ThreadItem>
extends RecyclerView.ViewHolder {
@NotNullByDefault
public class ThreadItemViewHolder<I extends ThreadItem>
extends BaseThreadItemViewHolder<I> {
private final static int ANIMATION_DURATION = 5000;
private final TextView textView, lvlText, repliesText;
private final AuthorView author;
private final TextView lvlText, repliesText;
private final View[] lvls;
private final View chevron, replyButton;
private final ViewGroup cell;
private final View topDivider;
public ThreadItemViewHolder(View v) {
super(v);
textView = (TextView) v.findViewById(R.id.text);
lvlText = (TextView) v.findViewById(R.id.nested_line_text);
author = (AuthorView) v.findViewById(R.id.author);
repliesText = (TextView) v.findViewById(R.id.replies);
int[] nestedLineIds = {
R.id.nested_line_1, R.id.nested_line_2, R.id.nested_line_3,
@@ -51,21 +36,13 @@ public abstract class ThreadItemViewHolder<I extends ThreadItem>
}
chevron = v.findViewById(R.id.chevron);
replyButton = v.findViewById(R.id.btn_reply);
cell = (ViewGroup) v.findViewById(R.id.forum_cell);
topDivider = v.findViewById(R.id.top_divider);
}
// TODO improve encapsulation, so we don't need to pass the adapter here
@Override
public void bind(final ThreadItemAdapter<I> adapter,
final ThreadItemListener<I> listener, final I item, int pos) {
textView.setText(StringUtils.trim(item.getText()));
if (pos == 0) {
topDivider.setVisibility(View.INVISIBLE);
} else {
topDivider.setVisibility(View.VISIBLE);
}
super.bind(adapter, listener, item, pos);
for (int i = 0; i < lvls.length; i++) {
lvls[i].setVisibility(i < item.getLevel() ? VISIBLE : GONE);
@@ -76,9 +53,6 @@ public abstract class ThreadItemViewHolder<I extends ThreadItem>
} else {
lvlText.setVisibility(GONE);
}
author.setAuthor(item.getAuthor());
author.setDate(item.getTimestamp());
author.setAuthorStatus(item.getStatus());
int replies = adapter.getReplyCount(item);
if (replies == 0) {
@@ -110,18 +84,6 @@ public abstract class ThreadItemViewHolder<I extends ThreadItem>
} else {
chevron.setVisibility(INVISIBLE);
}
if (item.equals(adapter.getReplyItem())) {
cell.setBackgroundColor(ContextCompat
.getColor(getContext(), R.color.forum_cell_highlight));
} else if (item.equals(adapter.getAddedItem())) {
cell.setBackgroundColor(ContextCompat
.getColor(getContext(), R.color.forum_cell_highlight));
animateFadeOut(adapter, adapter.getAddedItem());
adapter.clearAddedItem();
} else {
cell.setBackgroundColor(ContextCompat
.getColor(getContext(), R.color.window_background));
}
replyButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -131,52 +93,4 @@ public abstract class ThreadItemViewHolder<I extends ThreadItem>
});
}
private void animateFadeOut(final ThreadItemAdapter<I> adapter,
final I addedItem) {
setIsRecyclable(false);
ValueAnimator anim = new ValueAnimator();
adapter.addAnimatingItem(addedItem, anim);
ColorDrawable viewColor = (ColorDrawable) cell.getBackground();
anim.setIntValues(viewColor.getColor(), ContextCompat
.getColor(getContext(), R.color.window_background));
anim.setEvaluator(new ArgbEvaluator());
anim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
setIsRecyclable(true);
adapter.removeAnimatingItem(addedItem);
}
@Override
public void onAnimationCancel(Animator animation) {
setIsRecyclable(true);
adapter.removeAnimatingItem(addedItem);
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
cell.setBackgroundColor(
(Integer) valueAnimator.getAnimatedValue());
}
});
anim.setDuration(ANIMATION_DURATION);
anim.start();
}
private Context getContext() {
return textView.getContext();
}
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.android.threaded;
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
@@ -39,6 +40,8 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem
@UiThread
void onGroupRemoved();
Context getApplicationContext();
}
}

View File

@@ -52,7 +52,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
private volatile GroupId groupId;
protected ThreadListListener<H> listener;
protected volatile ThreadListListener<H> listener;
protected ThreadListControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, IdentityManager identityManager,
@@ -159,7 +159,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
for (H header : headers) {
if (!bodyCache.containsKey(header.getId())) {
bodyCache.put(header.getId(),
loadMessageBody(header.getId()));
loadMessageBody(header));
}
}
duration = System.currentTimeMillis() - now;
@@ -181,7 +181,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
protected abstract Collection<H> loadHeaders() throws DbException;
@DatabaseExecutor
protected abstract String loadMessageBody(MessageId id) throws DbException;
protected abstract String loadMessageBody(H header) throws DbException;
@Override
public void loadItem(final H header,
@@ -193,7 +193,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
long now = System.currentTimeMillis();
String body;
if (!bodyCache.containsKey(header.getId())) {
body = loadMessageBody(header.getId());
body = loadMessageBody(header);
bodyCache.put(header.getId(), body);
} else {
body = bodyCache.get(header.getId());

View File

@@ -8,6 +8,7 @@ import org.briarproject.BuildConfig;
import org.briarproject.TestUtils;
import org.briarproject.android.TestBriarApplication;
import org.briarproject.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.android.threaded.ThreadItemAdapter;
import org.briarproject.api.db.DbException;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorId;
@@ -111,7 +112,7 @@ public class ForumActivityTest {
List<ForumItem> dummyData = getDummyData();
verify(mc, times(1)).loadItems(rc.capture());
rc.getValue().onResult(dummyData);
NestedForumAdapter adapter = forumActivity.getAdapter();
ThreadItemAdapter<ForumItem> adapter = forumActivity.getAdapter();
Assert.assertNotNull(adapter);
// Cascade close
assertEquals(6, adapter.getItemCount());

View File

@@ -3,6 +3,7 @@ package org.briarproject.android.forum;
import org.briarproject.android.ActivityModule;
import org.briarproject.android.controller.BriarController;
import org.briarproject.android.controller.BriarControllerImpl;
import org.briarproject.android.threaded.ThreadItemAdapter;
import org.mockito.Mockito;
/**
@@ -15,7 +16,7 @@ public class TestForumActivity extends ForumActivity {
return forumController;
}
public NestedForumAdapter getAdapter() {
public ThreadItemAdapter<ForumItem> getAdapter() {
return adapter;
}

View File

@@ -26,7 +26,6 @@ import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId;
import org.briarproject.clients.BdfIncomingMessageHook;
import org.briarproject.util.StringUtils;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
@@ -236,10 +235,6 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
@Override
public String getMessageBody(MessageId m) throws DbException {
try {
// TODO remove
if (clientHelper.getMessageMetadataAsDictionary(m).getLong(KEY_TYPE) != POST.getInt())
return "new member joined";
// type(0), member_name(1), member_public_key(2), parent_id(3),
// previous_message_id(4), content(5), signature(6)
return clientHelper.getMessageAsList(m).getString(5);