diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml
index 86c5239e5..1bb12bfff 100644
--- a/briar-android/AndroidManifest.xml
+++ b/briar-android/AndroidManifest.xml
@@ -100,6 +100,17 @@
/>
+
+
+
+
+
+
diff --git a/briar-android/res/menu/forum_actions.xml b/briar-android/res/menu/forum_actions.xml
index 2a262cbfa..cd4e4573c 100644
--- a/briar-android/res/menu/forum_actions.xml
+++ b/briar-android/res/menu/forum_actions.xml
@@ -7,7 +7,7 @@
android:id="@+id/action_forum_compose_post"
android:icon="@drawable/forum_item_create_white"
android:title="@string/forum_compose_post"
- app:showAsAction="ifRoom"/>
+ app:showAsAction="always"/>
-
+
\ No newline at end of file
diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index 883dceee6..0f541d6fb 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -155,6 +155,14 @@
This group is dissolved
Remove
Add Private Group
+ This group is empty.\n\nYou can use the pen icon at the top to compose the first message.
+ Compose Message
+ Message sent
+ Message received
+ Member List
+ Invite Members
+ Leave Group
+ Dissolve Group
You don\'t have any forums yet.\n\nWhy don\'t you create a new one yourself by tapping the + icon at the top?\n\nYou can also ask your contacts to share forums with you.
diff --git a/briar-android/src/org/briarproject/android/ActivityComponent.java b/briar-android/src/org/briarproject/android/ActivityComponent.java
index d7cab786b..4e66c87ab 100644
--- a/briar-android/src/org/briarproject/android/ActivityComponent.java
+++ b/briar-android/src/org/briarproject/android/ActivityComponent.java
@@ -28,6 +28,7 @@ import org.briarproject.android.keyagreement.KeyAgreementActivity;
import org.briarproject.android.keyagreement.ShowQrCodeFragment;
import org.briarproject.android.panic.PanicPreferencesActivity;
import org.briarproject.android.panic.PanicResponderActivity;
+import org.briarproject.android.privategroup.conversation.GroupActivity;
import org.briarproject.android.privategroup.list.GroupListFragment;
import org.briarproject.android.sharing.ContactSelectorFragment;
import org.briarproject.android.sharing.InvitationsBlogActivity;
@@ -72,6 +73,8 @@ public interface ActivityComponent {
void inject(InvitationsBlogActivity activity);
+ void inject(GroupActivity activity);
+
void inject(CreateForumActivity activity);
void inject(ShareForumActivity activity);
diff --git a/briar-android/src/org/briarproject/android/ActivityModule.java b/briar-android/src/org/briarproject/android/ActivityModule.java
index 3a81fc60f..07cace566 100644
--- a/briar-android/src/org/briarproject/android/ActivityModule.java
+++ b/briar-android/src/org/briarproject/android/ActivityModule.java
@@ -21,6 +21,8 @@ import org.briarproject.android.controller.SetupController;
import org.briarproject.android.controller.SetupControllerImpl;
import org.briarproject.android.forum.ForumController;
import org.briarproject.android.forum.ForumControllerImpl;
+import org.briarproject.android.privategroup.conversation.GroupController;
+import org.briarproject.android.privategroup.conversation.GroupControllerImpl;
import org.briarproject.android.privategroup.list.GroupListController;
import org.briarproject.android.privategroup.list.GroupListControllerImpl;
@@ -99,6 +101,13 @@ public class ActivityModule {
return groupListController;
}
+ @ActivityScope
+ @Provides
+ protected GroupController provideGroupController(
+ GroupControllerImpl groupController) {
+ return groupController;
+ }
+
@ActivityScope
@Provides
protected ForumController provideForumController(
diff --git a/briar-android/src/org/briarproject/android/AndroidComponent.java b/briar-android/src/org/briarproject/android/AndroidComponent.java
index 9b7e6edba..32f452401 100644
--- a/briar-android/src/org/briarproject/android/AndroidComponent.java
+++ b/briar-android/src/org/briarproject/android/AndroidComponent.java
@@ -35,6 +35,7 @@ import org.briarproject.api.messaging.MessagingManager;
import org.briarproject.api.messaging.PrivateMessageFactory;
import org.briarproject.api.plugins.ConnectionRegistry;
import org.briarproject.api.plugins.PluginManager;
+import org.briarproject.api.privategroup.GroupMessageFactory;
import org.briarproject.api.privategroup.PrivateGroupManager;
import org.briarproject.api.settings.SettingsManager;
import org.briarproject.plugins.AndroidPluginsModule;
@@ -96,6 +97,8 @@ public interface AndroidComponent extends CoreEagerSingletons {
PrivateGroupManager privateGroupManager();
+ GroupMessageFactory groupMessageFactory();
+
ForumManager forumManager();
ForumSharingManager forumSharingManager();
diff --git a/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java b/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java
index 442ce23a5..a3b910390 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java
+++ b/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java
@@ -166,8 +166,7 @@ public class ForumControllerImpl
p.getMessage().getTimestamp(),
p.getAuthor(), OURSELVES, true);
- resultHandler.onResult(new ForumEntry(h,
- bodyCache.get(p.getMessage().getId())));
+ resultHandler.onResult(buildItem(h));
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupActivity.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupActivity.java
new file mode 100644
index 000000000..b00fdde45
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupActivity.java
@@ -0,0 +1,82 @@
+package org.briarproject.android.privategroup.conversation;
+
+import android.os.Bundle;
+import android.support.annotation.LayoutRes;
+import android.support.v7.widget.LinearLayoutManager;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import org.briarproject.R;
+import org.briarproject.android.ActivityComponent;
+import org.briarproject.android.threaded.ThreadListActivity;
+import org.briarproject.android.threaded.ThreadListController;
+import org.briarproject.api.privategroup.GroupMessageHeader;
+import org.briarproject.api.privategroup.PrivateGroup;
+
+import javax.inject.Inject;
+
+public class GroupActivity extends
+ ThreadListActivity {
+
+ @Inject
+ protected GroupController controller;
+
+ @Override
+ public void injectActivity(ActivityComponent component) {
+ component.inject(this);
+ }
+
+ @Override
+ protected ThreadListController getController() {
+ return controller;
+ }
+
+ @Override
+ public void onCreate(Bundle state) {
+ super.onCreate(state);
+ list.setEmptyText(R.string.groups_no_messages);
+ }
+
+ @Override
+ protected @LayoutRes int getLayout() {
+ return R.layout.activity_forum;
+ }
+
+ @Override
+ protected GroupMessageAdapter createAdapter(
+ LinearLayoutManager layoutManager) {
+ return new GroupMessageAdapter(this, layoutManager);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu items for use in the action bar
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.group_actions, menu);
+
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_group_compose_message:
+ showTextInput(null);
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @Override
+ protected int getItemPostedString() {
+ return R.string.groups_message_sent;
+ }
+
+ @Override
+ protected int getItemReceivedString() {
+ return R.string.groups_message_received;
+ }
+
+}
diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupController.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupController.java
new file mode 100644
index 000000000..0292f2278
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupController.java
@@ -0,0 +1,11 @@
+package org.briarproject.android.privategroup.conversation;
+
+import org.briarproject.android.threaded.ThreadListController;
+import org.briarproject.api.privategroup.GroupMessageHeader;
+import org.briarproject.api.privategroup.PrivateGroup;
+
+public interface GroupController
+ extends
+ ThreadListController {
+
+}
diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java
new file mode 100644
index 000000000..40e4d898c
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java
@@ -0,0 +1,165 @@
+package org.briarproject.android.privategroup.conversation;
+
+import android.support.annotation.Nullable;
+
+import org.briarproject.android.controller.handler.ResultExceptionHandler;
+import org.briarproject.android.threaded.ThreadListControllerImpl;
+import org.briarproject.api.FormatException;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.event.Event;
+import org.briarproject.api.event.GroupMessageAddedEvent;
+import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.privategroup.GroupMessage;
+import org.briarproject.api.privategroup.GroupMessageFactory;
+import org.briarproject.api.privategroup.GroupMessageHeader;
+import org.briarproject.api.privategroup.PrivateGroup;
+import org.briarproject.api.privategroup.PrivateGroupManager;
+import org.briarproject.api.sync.MessageId;
+
+import java.security.GeneralSecurityException;
+import java.util.Collection;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+import static java.util.logging.Level.INFO;
+import static java.util.logging.Level.WARNING;
+import static org.briarproject.api.identity.Author.Status.OURSELVES;
+
+public class GroupControllerImpl
+ extends ThreadListControllerImpl
+ implements GroupController {
+
+ private static final Logger LOG =
+ Logger.getLogger(GroupControllerImpl.class.getName());
+
+ @Inject
+ volatile GroupMessageFactory groupMessageFactory;
+ @Inject
+ volatile PrivateGroupManager privateGroupManager;
+
+ @Inject
+ GroupControllerImpl() {
+ super();
+ }
+
+ @Override
+ public void onActivityResume() {
+ super.onActivityResume();
+ notificationManager.clearForumPostNotification(groupId);
+ }
+
+ @Override
+ public void eventOccurred(Event e) {
+ super.eventOccurred(e);
+
+ if (e instanceof GroupMessageAddedEvent) {
+ final GroupMessageAddedEvent pe = (GroupMessageAddedEvent) e;
+ if (!pe.isLocal() && pe.getGroupId().equals(groupId)) {
+ LOG.info("Group message received, adding...");
+ final GroupMessageHeader fph = pe.getHeader();
+ updateNewestTimestamp(fph.getTimestamp());
+ listener.runOnUiThreadUnlessDestroyed(new Runnable() {
+ @Override
+ public void run() {
+ listener.onHeaderReceived(fph);
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ protected PrivateGroup loadGroupItem() throws DbException {
+ return privateGroupManager.getPrivateGroup(groupId);
+ }
+
+ @Override
+ protected Collection loadHeaders() throws DbException {
+ return privateGroupManager.getHeaders(groupId);
+ }
+
+ @Override
+ protected void loadBodies(Collection headers)
+ throws DbException {
+ for (GroupMessageHeader header : headers) {
+ if (!bodyCache.containsKey(header.getId())) {
+ String body =
+ privateGroupManager.getMessageBody(header.getId());
+ bodyCache.put(header.getId(), body);
+ }
+ }
+ }
+
+ @Override
+ protected void markRead(MessageId id) throws DbException {
+ privateGroupManager.setReadFlag(groupId, id, true);
+ }
+
+ @Override
+ public void send(final String body, @Nullable final MessageId parentId,
+ final ResultExceptionHandler handler) {
+ cryptoExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ LOG.info("Create message...");
+ long timestamp = System.currentTimeMillis();
+ timestamp = Math.max(timestamp, newestTimeStamp.get());
+ GroupMessage gm;
+ try {
+ LocalAuthor a = identityManager.getLocalAuthor();
+ gm = groupMessageFactory.createGroupMessage(groupId,
+ timestamp, parentId, a, body);
+ } catch (GeneralSecurityException | FormatException e) {
+ throw new RuntimeException(e);
+ } catch (DbException e) {
+ if (LOG.isLoggable(WARNING))
+ LOG.log(WARNING, e.toString(), e);
+ handler.onException(e);
+ return;
+ }
+ bodyCache.put(gm.getMessage().getId(), body);
+ storeMessage(gm, handler);
+ }
+ });
+ }
+
+ private void storeMessage(final GroupMessage gm,
+ final ResultExceptionHandler handler) {
+ runOnDbThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ LOG.info("Store message...");
+ long now = System.currentTimeMillis();
+ privateGroupManager.addLocalMessage(gm);
+ long duration = System.currentTimeMillis() - now;
+ if (LOG.isLoggable(INFO))
+ LOG.info("Storing message took " + duration + " ms");
+
+ GroupMessageHeader h = new GroupMessageHeader(groupId,
+ gm.getMessage().getId(), gm.getParent(),
+ gm.getMessage().getTimestamp(), gm.getAuthor(),
+ OURSELVES, true);
+
+ handler.onResult(buildItem(h));
+ } catch (DbException e) {
+ if (LOG.isLoggable(WARNING))
+ LOG.log(WARNING, e.toString(), e);
+ handler.onException(e);
+ }
+ }
+ });
+ }
+
+ @Override
+ protected void deleteGroupItem(PrivateGroup group) throws DbException {
+ privateGroupManager.removePrivateGroup(group.getId());
+ }
+
+ @Override
+ protected GroupMessageItem buildItem(GroupMessageHeader header) {
+ return new GroupMessageItem(header, bodyCache.get(header.getId()));
+ }
+
+}
diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageAdapter.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageAdapter.java
new file mode 100644
index 000000000..19ee14adc
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageAdapter.java
@@ -0,0 +1,28 @@
+package org.briarproject.android.privategroup.conversation;
+
+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
+public class GroupMessageAdapter extends ThreadItemAdapter {
+
+ public GroupMessageAdapter(ThreadItemListener listener,
+ LinearLayoutManager layoutManager) {
+ super(listener, layoutManager);
+ }
+
+ @Override
+ public GroupMessageViewHolder onCreateViewHolder(ViewGroup parent,
+ int viewType) {
+ View v = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.list_item_forum_post, parent, false);
+ return new GroupMessageViewHolder(v);
+ }
+
+}
diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageViewHolder.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageViewHolder.java
new file mode 100644
index 000000000..11825b805
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageViewHolder.java
@@ -0,0 +1,14 @@
+package org.briarproject.android.privategroup.conversation;
+
+import android.view.View;
+
+import org.briarproject.android.threaded.ThreadItemViewHolder;
+
+public class GroupMessageViewHolder
+ extends ThreadItemViewHolder {
+
+ public GroupMessageViewHolder(View v) {
+ super(v);
+ }
+
+}
diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupListAdapter.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListAdapter.java
index 20210a486..b29fc227b 100644
--- a/briar-android/src/org/briarproject/android/privategroup/list/GroupListAdapter.java
+++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListAdapter.java
@@ -1,7 +1,6 @@
package org.briarproject.android.privategroup.list;
import android.content.Context;
-import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupViewHolder.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupViewHolder.java
index 12b956df4..3994a0a1e 100644
--- a/briar-android/src/org/briarproject/android/privategroup/list/GroupViewHolder.java
+++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupViewHolder.java
@@ -1,7 +1,9 @@
package org.briarproject.android.privategroup.list;
import android.content.Context;
+import android.content.Intent;
import android.support.annotation.Nullable;
+import android.support.v4.app.ActivityOptionsCompat;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.View.OnClickListener;
@@ -10,13 +12,18 @@ import android.widget.Button;
import android.widget.TextView;
import org.briarproject.R;
+import org.briarproject.android.privategroup.conversation.GroupActivity;
import org.briarproject.android.util.AndroidUtils;
import org.briarproject.android.view.TextAvatarView;
+import org.briarproject.api.sync.GroupId;
import org.jetbrains.annotations.NotNull;
import static android.support.v4.content.ContextCompat.getColor;
+import static android.support.v4.content.ContextCompat.startActivities;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
+import static org.briarproject.android.BriarActivity.GROUP_ID;
+import static org.briarproject.android.BriarActivity.GROUP_NAME;
class GroupViewHolder extends RecyclerView.ViewHolder {
@@ -44,7 +51,7 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
remove = (Button) v.findViewById(R.id.removeButton);
}
- void bindView(Context ctx, @Nullable final GroupItem group,
+ void bindView(final Context ctx, @Nullable final GroupItem group,
@NotNull final OnGroupRemoveClickListener listener) {
if (group == null) return;
@@ -115,15 +122,15 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
layout.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
-/*
Intent i = new Intent(ctx, GroupActivity.class);
- GroupId id = item.getId();
+ GroupId id = group.getId();
i.putExtra(GROUP_ID, id.getBytes());
+ i.putExtra(GROUP_NAME, group.getName());
ActivityOptionsCompat options = ActivityOptionsCompat
.makeCustomAnimation(ctx, android.R.anim.fade_in,
android.R.anim.fade_out);
- ActivityCompat.startActivity(ctx, i, options.toBundle());
-*/
+ Intent[] intents = {i};
+ startActivities(ctx, intents, options.toBundle());
}
});
}
diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java b/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java
index 6a7477923..e175d3034 100644
--- a/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java
+++ b/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java
@@ -90,8 +90,8 @@ public abstract class ThreadListActivity(this) {
@Override
- public void onResultUi(G forum) {
- setTitle(forum.getName());
+ public void onResultUi(G groupItem) {
+ setTitle(groupItem.getName());
}
@Override
diff --git a/briar-api/src/org/briarproject/api/forum/ForumPost.java b/briar-api/src/org/briarproject/api/forum/ForumPost.java
index aef48c78c..bbc23e9db 100644
--- a/briar-api/src/org/briarproject/api/forum/ForumPost.java
+++ b/briar-api/src/org/briarproject/api/forum/ForumPost.java
@@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable;
public class ForumPost extends BaseMessage {
+ @Nullable
private final Author author;
public ForumPost(@NotNull Message message, @Nullable MessageId parent,
diff --git a/briar-api/src/org/briarproject/api/privategroup/GroupMessage.java b/briar-api/src/org/briarproject/api/privategroup/GroupMessage.java
index 8ffe6c6a4..22457de2e 100644
--- a/briar-api/src/org/briarproject/api/privategroup/GroupMessage.java
+++ b/briar-api/src/org/briarproject/api/privategroup/GroupMessage.java
@@ -1,17 +1,24 @@
package org.briarproject.api.privategroup;
-import org.briarproject.api.forum.ForumPost;
+import org.briarproject.api.clients.BaseMessage;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-public class GroupMessage extends ForumPost {
+public class GroupMessage extends BaseMessage {
+
+ private final Author author;
public GroupMessage(@NotNull Message message, @Nullable MessageId parent,
@NotNull Author author) {
- super(message, parent, author);
+ super(message, parent);
+ this.author = author;
+ }
+
+ public Author getAuthor() {
+ return author;
}
}