Add UI for revealing contacts within a private group

This commit is contained in:
Torsten Grote
2016-11-11 14:51:42 -02:00
parent b885e49ba2
commit 59964c5087
20 changed files with 628 additions and 7 deletions

View File

@@ -37,6 +37,8 @@ import org.briarproject.android.privategroup.creation.GroupInviteFragment;
import org.briarproject.android.privategroup.invitation.GroupInvitationActivity;
import org.briarproject.android.privategroup.list.GroupListFragment;
import org.briarproject.android.privategroup.memberlist.GroupMemberListActivity;
import org.briarproject.android.privategroup.reveal.RevealContactsActivity;
import org.briarproject.android.privategroup.reveal.RevealContactsFragment;
import org.briarproject.android.sharing.BlogInvitationActivity;
import org.briarproject.android.sharing.BlogSharingStatusActivity;
import org.briarproject.android.sharing.ForumInvitationActivity;
@@ -91,6 +93,8 @@ public interface ActivityComponent {
void inject(GroupMemberListActivity activity);
void inject(RevealContactsActivity activity);
void inject(CreateForumActivity activity);
void inject(ShareForumActivity activity);
@@ -146,6 +150,8 @@ public interface ActivityComponent {
void inject(GroupInviteFragment fragment);
void inject(RevealContactsFragment activity);
void inject(ForumListFragment fragment);
void inject(FeedFragment fragment);

View File

@@ -31,6 +31,8 @@ import org.briarproject.android.privategroup.list.GroupListController;
import org.briarproject.android.privategroup.list.GroupListControllerImpl;
import org.briarproject.android.privategroup.memberlist.GroupMemberListController;
import org.briarproject.android.privategroup.memberlist.GroupMemberListControllerImpl;
import org.briarproject.android.privategroup.reveal.RevealContactsController;
import org.briarproject.android.privategroup.reveal.RevealContactsControllerImpl;
import org.briarproject.android.sharing.BlogInvitationController;
import org.briarproject.android.sharing.BlogInvitationControllerImpl;
import org.briarproject.android.sharing.ForumInvitationController;
@@ -144,6 +146,13 @@ public class ActivityModule {
return groupMemberListController;
}
@ActivityScope
@Provides
protected RevealContactsController provideRevealContactsController(
RevealContactsControllerImpl revealContactsController) {
return revealContactsController;
}
@ActivityScope
@Provides
protected ForumController provideForumController(

View File

@@ -22,6 +22,7 @@ import org.briarproject.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.android.privategroup.conversation.GroupController.GroupListener;
import org.briarproject.android.privategroup.creation.GroupInviteActivity;
import org.briarproject.android.privategroup.memberlist.GroupMemberListActivity;
import org.briarproject.android.privategroup.reveal.RevealContactsActivity;
import org.briarproject.android.threaded.ThreadListActivity;
import org.briarproject.android.threaded.ThreadListController;
import org.briarproject.api.db.DbException;
@@ -48,8 +49,8 @@ public class GroupActivity extends
GroupController controller;
private boolean isCreator, isDissolved = false;
private MenuItem writeMenuItem, inviteMenuItem, leaveMenuItem,
dissolveMenuItem;
private MenuItem writeMenuItem, revealMenuItem, inviteMenuItem,
leaveMenuItem, dissolveMenuItem;
@Override
public void injectActivity(ActivityComponent component) {
@@ -135,6 +136,7 @@ public class GroupActivity extends
inflater.inflate(R.menu.group_actions, menu);
writeMenuItem = menu.findItem(R.id.action_group_compose_message);
revealMenuItem = menu.findItem(R.id.action_group_reveal);
inviteMenuItem = menu.findItem(R.id.action_group_invite);
leaveMenuItem = menu.findItem(R.id.action_group_leave);
dissolveMenuItem = menu.findItem(R.id.action_group_dissolve);
@@ -157,10 +159,15 @@ public class GroupActivity extends
i1.putExtra(GROUP_ID, groupId.getBytes());
ActivityCompat.startActivity(this, i1, options.toBundle());
return true;
case R.id.action_group_invite:
Intent i2 = new Intent(this, GroupInviteActivity.class);
case R.id.action_group_reveal:
Intent i2 = new Intent(this, RevealContactsActivity.class);
i2.putExtra(GROUP_ID, groupId.getBytes());
ActivityCompat.startActivityForResult(this, i2, REQUEST_INVITE,
ActivityCompat.startActivity(this, i2, options.toBundle());
return true;
case R.id.action_group_invite:
Intent i3 = new Intent(this, GroupInviteActivity.class);
i3.putExtra(GROUP_ID, groupId.getBytes());
ActivityCompat.startActivityForResult(this, i3, REQUEST_INVITE,
options.toBundle());
return true;
case R.id.action_group_leave:
@@ -218,10 +225,12 @@ public class GroupActivity extends
private void showMenuItems() {
if (leaveMenuItem == null || dissolveMenuItem == null) return;
if (isCreator) {
revealMenuItem.setVisible(false);
inviteMenuItem.setVisible(true);
leaveMenuItem.setVisible(false);
dissolveMenuItem.setVisible(true);
} else {
revealMenuItem.setVisible(true);
inviteMenuItem.setVisible(false);
leaveMenuItem.setVisible(true);
dissolveMenuItem.setVisible(false);

View File

@@ -19,7 +19,7 @@ class MemberListItem {
public MemberListItem(GroupMember groupMember) {
this.member = groupMember.getAuthor();
this.sharing = groupMember.getVisibility() != INVISIBLE; // TODO #732
this.sharing = groupMember.getVisibility() != INVISIBLE;
this.status = groupMember.getStatus();
}

View File

@@ -0,0 +1,100 @@
package org.briarproject.android.privategroup.reveal;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.contactselection.ContactSelectorActivity;
import org.briarproject.android.controller.handler.UiExceptionHandler;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
import javax.inject.Inject;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class RevealContactsActivity extends ContactSelectorActivity
implements OnClickListener {
private Button button;
@Inject
RevealContactsController controller;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
@SuppressWarnings("ConstantConditions")
public void onCreate(@Nullable Bundle bundle) {
super.onCreate(bundle);
Intent i = getIntent();
byte[] b = i.getByteArrayExtra(GROUP_ID);
if (b == null) throw new IllegalStateException("No GroupId");
groupId = new GroupId(b);
button = (Button) findViewById(R.id.revealButton);
button.setOnClickListener(this);
button.setEnabled(false);
if (bundle == null) {
RevealContactsFragment fragment =
RevealContactsFragment.newInstance(groupId);
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, fragment)
.commit();
}
}
@Override
@LayoutRes
protected int getLayout() {
return R.layout.activity_reveal_contacts;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void contactsSelected(Collection<ContactId> contacts) {
super.contactsSelected(contacts);
button.setEnabled(!contacts.isEmpty());
}
@Override
public void onClick(View v) {
controller.reveal(groupId, contacts,
new UiExceptionHandler<DbException>(this) {
@Override
public void onExceptionUi(DbException exception) {
// TODO proper error handling
finish();
}
});
supportFinishAfterTransition();
}
}

View File

@@ -0,0 +1,19 @@
package org.briarproject.android.privategroup.reveal;
import org.briarproject.android.contactselection.ContactSelectorController;
import org.briarproject.android.controller.handler.ExceptionHandler;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
@NotNullByDefault
public interface RevealContactsController
extends ContactSelectorController<RevealableContactItem> {
void reveal(GroupId g, Collection<ContactId> contacts,
ExceptionHandler<DbException> handler);
}

View File

@@ -0,0 +1,122 @@
package org.briarproject.android.privategroup.reveal;
import org.briarproject.android.controller.DbControllerImpl;
import org.briarproject.android.controller.handler.ExceptionHandler;
import org.briarproject.android.controller.handler.ResultExceptionHandler;
import org.briarproject.api.clients.ProtocolStateException;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.privategroup.GroupMember;
import org.briarproject.api.privategroup.PrivateGroupManager;
import org.briarproject.api.privategroup.invitation.GroupInvitationManager;
import org.briarproject.api.sync.GroupId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.privategroup.Visibility.INVISIBLE;
@Immutable
@NotNullByDefault
public class RevealContactsControllerImpl extends DbControllerImpl
implements RevealContactsController {
private static final Logger LOG =
Logger.getLogger(RevealContactsControllerImpl.class.getName());
private final PrivateGroupManager groupManager;
private final GroupInvitationManager groupInvitationManager;
private final ContactManager contactManager;
@Inject
public RevealContactsControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, PrivateGroupManager groupManager,
GroupInvitationManager groupInvitationManager,
ContactManager contactManager) {
super(dbExecutor, lifecycleManager);
this.groupManager = groupManager;
this.groupInvitationManager = groupInvitationManager;
this.contactManager = contactManager;
}
@Override
public void loadContacts(final GroupId g,
final Collection<ContactId> selection,
final ResultExceptionHandler<Collection<RevealableContactItem>, DbException> handler) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
Collection<RevealableContactItem> items =
getItems(g, selection);
handler.onResult(items);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
handler.onException(e);
}
}
});
}
@DatabaseExecutor
private Collection<RevealableContactItem> getItems(GroupId g,
Collection<ContactId> selection) throws DbException {
Collection<GroupMember> members =
groupManager.getMembers(g);
Collection<Contact> contacts =
contactManager.getActiveContacts();
Collection<RevealableContactItem> items =
new ArrayList<>(members.size());
for (GroupMember m : members) {
for (Contact c : contacts) {
if (m.getAuthor().equals(c.getAuthor())) {
boolean disabled = m.getVisibility() != INVISIBLE;
boolean selected =
disabled || selection.contains(c.getId());
items.add(new RevealableContactItem(c, selected, disabled,
m.getVisibility()));
}
}
}
return items;
}
@Override
public void reveal(final GroupId g, final Collection<ContactId> contacts,
final ExceptionHandler<DbException> handler) {
runOnDbThread(new Runnable() {
@Override
public void run() {
for (ContactId c : contacts) {
try {
groupInvitationManager.revealRelationship(c, g);
} catch (ProtocolStateException e) {
// action is outdated, move to next contact
if (LOG.isLoggable(INFO))
LOG.log(INFO, e.toString(), e);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
handler.onException(e);
break;
}
}
}
});
}
}

View File

@@ -0,0 +1,70 @@
package org.briarproject.android.privategroup.reveal;
import android.content.Context;
import android.os.Bundle;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.contact.BaseContactListAdapter.OnContactClickListener;
import org.briarproject.android.contactselection.BaseContactSelectorFragment;
import org.briarproject.android.contactselection.ContactSelectorController;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
import javax.inject.Inject;
import static org.briarproject.android.BriarActivity.GROUP_ID;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class RevealContactsFragment extends
BaseContactSelectorFragment<RevealableContactItem, RevealableContactAdapter> {
private final static String TAG = RevealContactsFragment.class.getName();
@Inject
RevealContactsController controller;
public static RevealContactsFragment newInstance(GroupId groupId) {
Bundle args = new Bundle();
args.putByteArray(GROUP_ID, groupId.getBytes());
RevealContactsFragment fragment = new RevealContactsFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
protected ContactSelectorController<RevealableContactItem> getController() {
return controller;
}
@Override
protected RevealableContactAdapter getAdapter(Context context,
OnContactClickListener<RevealableContactItem> listener) {
return new RevealableContactAdapter(context, listener);
}
@Override
protected void onSelectionChanged() {
Collection<ContactId> selected = adapter.getSelectedContactIds();
Collection<ContactId> disabled = adapter.getDisabledContactIds();
selected.removeAll(disabled);
// tell the activity which contacts have been selected
listener.contactsSelected(selected);
}
@Override
public String getUniqueTag() {
return TAG;
}
}

View File

@@ -0,0 +1,43 @@
package org.briarproject.android.privategroup.reveal;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.R;
import org.briarproject.android.contactselection.BaseContactSelectorAdapter;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.nullsafety.NotNullByDefault;
import java.util.ArrayList;
import java.util.Collection;
@NotNullByDefault
class RevealableContactAdapter extends
BaseContactSelectorAdapter<RevealableContactItem, RevealableContactViewHolder> {
RevealableContactAdapter(Context context,
OnContactClickListener<RevealableContactItem> listener) {
super(context, RevealableContactItem.class, listener);
}
@Override
public RevealableContactViewHolder onCreateViewHolder(ViewGroup viewGroup,
int i) {
View v = LayoutInflater.from(ctx).inflate(
R.layout.list_item_revealable_contact, viewGroup, false);
return new RevealableContactViewHolder(v);
}
Collection<ContactId> getDisabledContactIds() {
Collection<ContactId> disabled = new ArrayList<>();
for (int i = 0; i < items.size(); i++) {
RevealableContactItem item = items.get(i);
if (item.isDisabled()) disabled.add(item.getContact().getId());
}
return disabled;
}
}

View File

@@ -0,0 +1,26 @@
package org.briarproject.android.privategroup.reveal;
import org.briarproject.android.contactselection.SelectableContactItem;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.privategroup.Visibility;
import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe
@NotNullByDefault
public class RevealableContactItem extends SelectableContactItem {
private final Visibility visibility;
public RevealableContactItem(Contact contact, boolean selected,
boolean disabled, Visibility visibility) {
super(contact, selected, disabled);
this.visibility = visibility;
}
public Visibility getVisibility() {
return visibility;
}
}

View File

@@ -0,0 +1,64 @@
package org.briarproject.android.privategroup.reveal;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.ImageView;
import org.briarproject.R;
import org.briarproject.android.contact.BaseContactListAdapter.OnContactClickListener;
import org.briarproject.android.contactselection.BaseSelectableContactHolder;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.jetbrains.annotations.Nullable;
import static org.briarproject.android.util.AndroidUtils.GREY_OUT;
import static org.briarproject.api.privategroup.Visibility.INVISIBLE;
@UiThread
@NotNullByDefault
public class RevealableContactViewHolder
extends BaseSelectableContactHolder<RevealableContactItem> {
private final ImageView icon;
RevealableContactViewHolder(View v) {
super(v);
icon = (ImageView) v.findViewById(R.id.visibilityView);
}
@Override
protected void bind(RevealableContactItem item, @Nullable
OnContactClickListener<RevealableContactItem> listener) {
super.bind(item, listener);
switch (item.getVisibility()) {
case VISIBLE:
info.setText(R.string.groups_reveal_visible);
break;
case REVEALED_BY_US:
info.setText(R.string.groups_reveal_visible_revealed_by_us);
break;
case REVEALED_BY_CONTACT:
info.setText(
R.string.groups_reveal_visible_revealed_by_contact);
break;
case INVISIBLE:
info.setText(R.string.groups_reveal_invisible);
break;
}
if (item.getVisibility() == INVISIBLE) {
icon.setImageResource(R.drawable.ic_visibility_off);
} else {
icon.setImageResource(R.drawable.ic_visibility);
}
}
@Override
protected void grayOutItem(boolean gray) {
super.grayOutItem(gray);
float alpha = gray ? GREY_OUT : 1f;
icon.setAlpha(alpha);
}
}

View File

@@ -7,7 +7,6 @@ import android.support.annotation.UiThread;
import org.briarproject.R;
import org.briarproject.android.contactselection.ContactSelectorActivity;
import org.briarproject.android.contactselection.ContactSelectorFragment;
import org.briarproject.android.contactselection.SelectableContactItem;
import org.briarproject.android.sharing.BaseMessageFragment.MessageFragmentListener;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.nullsafety.MethodsNotNullByDefault;