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; } }