Merge branch '678-implement-ux-for-viewing-the-membership-of-a-private-group' into 'master'

Implement UX for viewing the membership of a private group

This MR is the second and last MR to address #678. The first part is in !377.

![device-2016-10-26-112000](/uploads/8cbdee65c123a6d5329e208d9983d0b0/device-2016-10-26-112000.png)

Closes #678

See merge request !364
This commit is contained in:
akwizgran
2016-11-01 11:12:21 +00:00
16 changed files with 394 additions and 15 deletions

View File

@@ -128,7 +128,17 @@
android:parentActivityName=".android.NavDrawerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.NavDrawerActivity"
android:value=".android.NavDrawerActivity"/>
</activity>
<activity
android:name=".android.privategroup.memberlist.GroupMemberListActivity"
android:label="@string/groups_member_list"
android:parentActivityName=".android.privategroup.conversation.GroupActivity"
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.privategroup.conversation.GroupActivity"
/>
</activity>

View File

@@ -1,5 +1,10 @@
<vector android:alpha="0.56" android:height="48dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M9.01,14L2,14v2h7.01v3L13,15l-3.99,-4v3zM14.99,13v-3L22,10L22,8h-7.01L14.99,5L11,9l3.99,4z"/>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:alpha="0.54"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M9.01,14L2,14v2h7.01v3L13,15l-3.99,-4v3zM14.99,13v-3L22,10L22,8h-7.01L14.99,5L11,9l3.99,4z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:alpha="0.54"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M6.99,11L3,15l3.99,4v-3H14v-2H6.99v-3zM21,9l-3.99,-4v3H10v2h7.01v3L21,9z"/>
</vector>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/listitem_horizontal_margin"
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginRight="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin"
android:orientation="horizontal"
android:paddingBottom="@dimen/margin_medium"
android:paddingTop="@dimen/margin_medium">
<org.briarproject.android.view.AuthorView
android:id="@+id/authorView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:persona="list"/>
<ImageView
android:id="@+id/sharingView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_sharing"
android:layout_marginLeft="@dimen/margin_medium"
android:contentDescription="@string/forum_invitation_already_sharing"/>
</LinearLayout>

View File

@@ -11,7 +11,6 @@
<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"/>

View File

@@ -11,6 +11,7 @@
<enum name="normal" value="0"/>
<enum name="reblogger" value="1"/>
<enum name="commenter" value="2"/>
<enum name="list" value="3"/>
</attr>
</declare-styleable>

View File

@@ -28,21 +28,22 @@ 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.creation.CreateGroupActivity;
import org.briarproject.android.privategroup.creation.CreateGroupFragment;
import org.briarproject.android.privategroup.conversation.GroupActivity;
import org.briarproject.android.privategroup.creation.CreateGroupMessageFragment;
import org.briarproject.android.privategroup.list.GroupListFragment;
import org.briarproject.android.privategroup.invitation.GroupInvitationActivity;
import org.briarproject.android.sharing.ShareBlogActivity;
import org.briarproject.android.privategroup.list.GroupListFragment;
import org.briarproject.android.privategroup.memberlist.GroupMemberListActivity;
import org.briarproject.android.sharing.BlogInvitationActivity;
import org.briarproject.android.sharing.BlogSharingStatusActivity;
import org.briarproject.android.sharing.ContactSelectorFragment;
import org.briarproject.android.sharing.BlogInvitationActivity;
import org.briarproject.android.sharing.ForumInvitationActivity;
import org.briarproject.android.sharing.ForumSharingStatusActivity;
import org.briarproject.android.sharing.ShareBlogActivity;
import org.briarproject.android.sharing.ShareBlogMessageFragment;
import org.briarproject.android.sharing.ShareForumActivity;
import org.briarproject.android.sharing.ShareForumMessageFragment;
import org.briarproject.android.sharing.ForumSharingStatusActivity;
import org.briarproject.android.sharing.ShareBlogMessageFragment;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel;
@@ -80,6 +81,7 @@ public interface ActivityComponent {
void inject(CreateGroupActivity activity);
void inject(GroupActivity activity);
void inject(GroupInvitationActivity activity);
void inject(GroupMemberListActivity activity);
void inject(CreateForumActivity activity);

View File

@@ -29,6 +29,8 @@ import org.briarproject.android.privategroup.invitation.GroupInvitationControlle
import org.briarproject.android.privategroup.invitation.GroupInvitationControllerImpl;
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.sharing.BlogInvitationController;
import org.briarproject.android.sharing.BlogInvitationControllerImpl;
import org.briarproject.android.sharing.ForumInvitationController;
@@ -131,6 +133,13 @@ public class ActivityModule {
return groupInvitationController;
}
@ActivityScope
@Provides
protected GroupMemberListController provideGroupMemberListController(
GroupMemberListControllerImpl groupMemberListController) {
return groupMemberListController;
}
@ActivityScope
@Provides
protected ForumController provideForumController(

View File

@@ -6,6 +6,8 @@ import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.annotation.StringRes;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
@@ -16,6 +18,7 @@ import android.view.MenuItem;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.android.privategroup.memberlist.GroupMemberListActivity;
import org.briarproject.android.threaded.ThreadListActivity;
import org.briarproject.android.threaded.ThreadListController;
import org.briarproject.api.db.DbException;
@@ -24,6 +27,7 @@ import org.briarproject.api.privategroup.PrivateGroup;
import javax.inject.Inject;
import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH;
public class GroupActivity extends
@@ -133,12 +137,19 @@ public class GroupActivity extends
case R.id.action_group_compose_message:
showTextInput(null);
return true;
case R.id.action_group_member_list:
Intent i = new Intent(this, GroupMemberListActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes());
ActivityOptionsCompat options =
makeCustomAnimation(this, android.R.anim.slide_in_left,
android.R.anim.slide_out_right);
ActivityCompat.startActivity(this, i, options.toBundle());
return true;
case R.id.action_group_leave:
showLeaveGroupDialog();
return true;
case R.id.action_group_dissolve:
showDissolveGroupDialog();
return true;
default:
return super.onOptionsItemSelected(item);
}

View File

@@ -0,0 +1,88 @@
package org.briarproject.android.privategroup.memberlist;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.view.MenuItem;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.android.view.BriarRecyclerView;
import org.briarproject.api.db.DbException;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
import javax.inject.Inject;
public class GroupMemberListActivity extends BriarActivity {
@Inject
GroupMemberListController controller;
private MemberListAdapter adapter;
private BriarRecyclerView list;
private GroupId groupId;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onCreate(final Bundle state) {
super.onCreate(state);
setContentView(R.layout.list);
Intent i = getIntent();
byte[] b = i.getByteArrayExtra(GROUP_ID);
if (b == null) throw new IllegalStateException("No GroupId in intent.");
groupId = new GroupId(b);
list = (BriarRecyclerView) findViewById(R.id.list);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
list.setLayoutManager(linearLayoutManager);
adapter = new MemberListAdapter(this);
list.setAdapter(adapter);
}
@Override
public void onStart() {
super.onStart();
controller.loadMembers(groupId,
new UiResultExceptionHandler<Collection<MemberListItem>, DbException>(this) {
@Override
public void onResultUi(Collection<MemberListItem> members) {
adapter.addAll(members);
}
@Override
public void onExceptionUi(DbException exception) {
// TODO proper error handling
finish();
}
});
list.startPeriodicUpdate();
}
@Override
public void onStop() {
super.onStop();
list.stopPeriodicUpdate();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

View File

@@ -0,0 +1,15 @@
package org.briarproject.android.privategroup.memberlist;
import org.briarproject.android.controller.DbController;
import org.briarproject.android.controller.handler.ResultExceptionHandler;
import org.briarproject.api.db.DbException;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
public interface GroupMemberListController extends DbController {
void loadMembers(GroupId groupId,
ResultExceptionHandler<Collection<MemberListItem>, DbException> handler);
}

View File

@@ -0,0 +1,60 @@
package org.briarproject.android.privategroup.memberlist;
import org.briarproject.android.controller.DbControllerImpl;
import org.briarproject.android.controller.handler.ResultExceptionHandler;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.privategroup.GroupMember;
import org.briarproject.api.privategroup.PrivateGroupManager;
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.inject.Inject;
import static java.util.logging.Level.WARNING;
public class GroupMemberListControllerImpl extends DbControllerImpl
implements GroupMemberListController {
private static final Logger LOG =
Logger.getLogger(GroupMemberListControllerImpl.class.getName());
private final PrivateGroupManager privateGroupManager;
@Inject
GroupMemberListControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
PrivateGroupManager privateGroupManager) {
super(dbExecutor, lifecycleManager);
this.privateGroupManager = privateGroupManager;
}
@Override
public void loadMembers(final GroupId groupId, final
ResultExceptionHandler<Collection<MemberListItem>, DbException> handler) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
Collection<MemberListItem> items = new ArrayList<>();
Collection<GroupMember> members =
privateGroupManager.getMembers(groupId);
for (GroupMember m : members) {
items.add(new MemberListItem(m));
}
handler.onResult(items);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
handler.onException(e);
}
}
});
}
}

View File

@@ -0,0 +1,48 @@
package org.briarproject.android.privategroup.memberlist;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.R;
import org.briarproject.android.util.BriarAdapter;
class MemberListAdapter extends
BriarAdapter<MemberListItem, MemberListItemHolder> {
MemberListAdapter(Context context) {
super(context, MemberListItem.class);
}
@Override
public MemberListItemHolder onCreateViewHolder(ViewGroup viewGroup,
int i) {
View v = LayoutInflater.from(ctx).inflate(
R.layout.list_item_group_member, viewGroup, false);
return new MemberListItemHolder(v);
}
@Override
public void onBindViewHolder(MemberListItemHolder ui, int position) {
ui.bind(items.get(position));
}
@Override
public int compare(MemberListItem m1, MemberListItem m2) {
return m1.getMember().getName().compareTo(m2.getMember().getName());
}
@Override
public boolean areContentsTheSame(MemberListItem m1, MemberListItem m2) {
if (m1.isSharing() != m2.isSharing()) return false;
if (m1.getStatus() != m2.getStatus()) return false;
return true;
}
@Override
public boolean areItemsTheSame(MemberListItem m1, MemberListItem m2) {
return m1.getMember().equals(m2.getMember());
}
}

View File

@@ -0,0 +1,36 @@
package org.briarproject.android.privategroup.memberlist;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.Author.Status;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.privategroup.GroupMember;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
class MemberListItem {
private final Author member;
private final Status status;
private final boolean sharing;
public MemberListItem(GroupMember groupMember) {
this.member = groupMember.getAuthor();
this.sharing = groupMember.isShared();
this.status = groupMember.getStatus();
}
public Author getMember() {
return member;
}
public boolean isSharing() {
return sharing;
}
public Status getStatus() {
return status;
}
}

View File

@@ -0,0 +1,38 @@
package org.briarproject.android.privategroup.memberlist;
import android.support.annotation.UiThread;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ImageView;
import org.briarproject.R;
import org.briarproject.android.view.AuthorView;
import org.briarproject.api.nullsafety.NotNullByDefault;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
@UiThread
@NotNullByDefault
class MemberListItemHolder extends RecyclerView.ViewHolder {
private final AuthorView author;
private final ImageView sharing;
MemberListItemHolder(View v) {
super(v);
author = (AuthorView) v.findViewById(R.id.authorView);
sharing = (ImageView) v.findViewById(R.id.sharingView);
}
protected void bind(MemberListItem item) {
author.setAuthor(item.getMember());
author.setAuthorStatus(item.getStatus());
if (item.isSharing()) {
sharing.setVisibility(VISIBLE);
} else {
sharing.setVisibility(INVISIBLE);
}
}
}

View File

@@ -12,7 +12,6 @@ import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
@@ -136,7 +135,7 @@ public class AuthorView extends RelativeLayout {
break;
// commenter
case 2:
ViewGroup.LayoutParams params = avatar.getLayoutParams();
LayoutParams params = (LayoutParams) avatar.getLayoutParams();
int size = getResources().getDimensionPixelSize(
R.dimen.blogs_avatar_comment_size);
params.height = size;
@@ -146,6 +145,25 @@ public class AuthorView extends RelativeLayout {
.getDimensionPixelSize(R.dimen.text_size_tiny);
authorName.setTextSize(COMPLEX_UNIT_PX, textSize);
break;
// list
case 3:
date.setVisibility(GONE);
params = (LayoutParams) avatar.getLayoutParams();
size = getResources().getDimensionPixelSize(
R.dimen.listitem_picture_size_small);
params.height = size;
params.width = size;
avatar.setLayoutParams(params);
textSize = getResources()
.getDimensionPixelSize(R.dimen.text_size_medium);
authorName.setTextSize(COMPLEX_UNIT_PX, textSize);
params = (LayoutParams) authorName.getLayoutParams();
params.addRule(CENTER_VERTICAL);
authorName.setLayoutParams(params);
params = (LayoutParams) trustIndicator.getLayoutParams();
params.addRule(CENTER_VERTICAL);
trustIndicator.setLayoutParams(params);
break;
}
}