Implement first prototype of private group message threads

This commit is contained in:
Torsten Grote
2016-10-11 16:18:21 -03:00
parent 65b47bb5d2
commit c83d4bbb39
19 changed files with 411 additions and 14 deletions

View File

@@ -100,6 +100,17 @@
/>
</activity>
<activity
android:name=".android.privategroup.conversation.GroupActivity"
android:label="@string/app_name"
android:parentActivityName=".android.NavDrawerActivity"
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.NavDrawerActivity"
/>
</activity>
<activity
android:name=".android.sharing.InvitationsForumActivity"
android:label="@string/forum_invitations_title"

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zM8,11c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zM8,13c-2.33,0 -7,1.17 -7,3.5L1,19h14v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5zM16,13c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45L17,19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z"/>
</vector>

View File

@@ -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"/>
<item
android:id="@+id/action_forum_share"

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_group_compose_message"
android:icon="@drawable/forum_item_create_white"
android:title="@string/groups_compose_message"
app:showAsAction="always"/>
<item
android:id="@+id/action_group_member_list"
android:enabled="false"
android:icon="@drawable/ic_group_white"
android:title="@string/groups_member_list"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/action_group_invite"
android:enabled="false"
android:icon="@drawable/ic_add_white"
android:title="@string/groups_invite_members"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/action_group_leave"
android:enabled="false"
android:icon="@drawable/action_delete_white"
android:title="@string/groups_leave"
android:visible="false"
app:showAsAction="never"/>
<item
android:id="@+id/action_group_dissolve"
android:enabled="false"
android:icon="@drawable/action_delete_white"
android:title="@string/groups_dissolve"
app:showAsAction="never"/>
</menu>

View File

@@ -155,6 +155,14 @@
<string name="groups_group_is_dissolved">This group is dissolved</string>
<string name="groups_remove">Remove</string>
<string name="groups_add_group_title">Add Private Group</string>
<string name="groups_no_messages">This group is empty.\n\nYou can use the pen icon at the top to compose the first message.</string>
<string name="groups_compose_message">Compose Message</string>
<string name="groups_message_sent">Message sent</string>
<string name="groups_message_received">Message received</string>
<string name="groups_member_list">Member List</string>
<string name="groups_invite_members">Invite Members</string>
<string name="groups_leave">Leave Group</string>
<string name="groups_dissolve">Dissolve Group</string>
<!-- Forums -->
<string name="no_forums">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.</string>

View File

@@ -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);

View File

@@ -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(

View File

@@ -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();

View File

@@ -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);

View File

@@ -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<PrivateGroup, GroupMessageItem, GroupMessageHeader, GroupMessageAdapter> {
@Inject
protected GroupController controller;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
protected ThreadListController<PrivateGroup, GroupMessageItem, GroupMessageHeader> 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;
}
}

View File

@@ -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<PrivateGroup, GroupMessageItem, GroupMessageHeader> {
}

View File

@@ -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<PrivateGroup, GroupMessageItem, GroupMessageHeader>
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<GroupMessageHeader> loadHeaders() throws DbException {
return privateGroupManager.getHeaders(groupId);
}
@Override
protected void loadBodies(Collection<GroupMessageHeader> 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<GroupMessageItem, DbException> 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<GroupMessageItem, DbException> 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()));
}
}

View File

@@ -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<GroupMessageItem> {
public GroupMessageAdapter(ThreadItemListener<GroupMessageItem> 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);
}
}

View File

@@ -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<GroupMessageItem> {
public GroupMessageViewHolder(View v) {
super(v);
}
}

View File

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

View File

@@ -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());
}
});
}

View File

@@ -90,8 +90,8 @@ public abstract class ThreadListActivity<G extends BaseGroup, I extends ThreadIt
getController().loadGroupItem(
new UiResultExceptionHandler<G, DbException>(this) {
@Override
public void onResultUi(G forum) {
setTitle(forum.getName());
public void onResultUi(G groupItem) {
setTitle(groupItem.getName());
}
@Override

View File

@@ -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,

View File

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