Merge branch '732-reveal-contacts-ui-join-notices' into 'master'

Add visibility and OPTIONS button to private group join notices

![device-2016-11-11-180658](/uploads/e00d175a1e1f34307c5f0d80fa0d1cdf/device-2016-11-11-180658.png)
![device-2016-11-11-181325](/uploads/0f6010094d529a4f151db8ebce974885/device-2016-11-11-181325.png)

Closes #732

See merge request !408
This commit is contained in:
akwizgran
2016-11-16 13:44:32 +00:00
24 changed files with 400 additions and 151 deletions

View File

@@ -9,7 +9,7 @@
style="@style/BriarAvatar"
android:layout_width="@dimen/blogs_avatar_normal_size"
android:layout_height="@dimen/blogs_avatar_normal_size"
android:layout_centerVertical="true"
android:layout_alignTop="@+id/authorName"
android:layout_marginRight="@dimen/margin_medium"
tools:src="@drawable/ic_launcher"/>
@@ -30,7 +30,6 @@
android:id="@+id/authorName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/avatar"
android:layout_toEndOf="@+id/avatar"
android:layout_toRightOf="@+id/avatar"
android:textColor="@color/briar_text_primary"

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:id="@+id/layout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_medium"
android:baselineAligned="false"
android:orientation="vertical">
<View
android:id="@+id/top_divider"
style="@style/Divider.ForumList"
android:layout_alignParentTop="true"/>
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/top_divider"
android:layout_marginBottom="@dimen/margin_small"
android:layout_marginRight="@dimen/margin_medium"
android:layout_marginTop="@dimen/margin_medium"
android:textColor="@color/briar_text_secondary"
android:textSize="@dimen/text_size_medium"
android:textStyle="italic"
tools:text="@string/groups_member_joined"/>
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/text"
android:layout_below="@+id/text"
android:layout_marginRight="@dimen/margin_small"
tools:ignore="ContentDescription"
tools:src="@drawable/ic_visibility"/>
<TextView
android:id="@+id/info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignEnd="@+id/text"
android:layout_alignRight="@+id/text"
android:layout_below="@+id/text"
android:layout_toRightOf="@+id/icon"
android:gravity="center_vertical"
android:minHeight="24dp"
android:textColor="@color/briar_text_secondary"
android:textIsSelectable="true"
android:textSize="@dimen/text_size_tiny"
android:textStyle="italic"
tools:text="@string/groups_reveal_visible_revealed_by_contact"/>
<org.briarproject.android.view.AuthorView
android:id="@+id/author"
android:layout_width="wrap_content"
android:layout_height="@dimen/button_size"
android:layout_alignLeft="@+id/text"
android:layout_below="@+id/info"
android:gravity="center"
app:persona="commenter"/>
<Button
android:id="@+id/optionsButton"
style="@style/BriarButtonFlat.Positive.Tiny"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignEnd="@+id/text"
android:layout_alignRight="@+id/text"
android:layout_alignTop="@+id/author"
android:layout_toRightOf="@+id/author"
android:gravity="right|center_vertical"
android:text="@string/options"/>
</RelativeLayout>

View File

@@ -6,8 +6,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:baselineAligned="false">
android:baselineAligned="false"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="wrap_content"
@@ -76,61 +76,59 @@
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_small"
android:layout_marginLeft="@dimen/margin_medium"
android:layout_marginRight="@dimen/margin_medium"
android:layout_marginTop="@dimen/margin_medium"
android:paddingRight="@dimen/margin_medium"
android:paddingTop="@dimen/margin_medium"
android:textColor="@color/briar_text_primary"
android:textIsSelectable="true"
android:textSize="@dimen/text_size_medium"
android:textColor="@color/briar_text_primary"
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."/>
<org.briarproject.android.view.AuthorView
android:id="@+id/author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="@dimen/button_size"
android:layout_alignLeft="@id/text"
android:layout_below="@id/text"
android:gravity="center"
app:persona="commenter"/>
<ImageView
android:id="@+id/chevron"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_below="@id/text"
android:layout_marginRight="@dimen/margin_medium"
android:layout_marginTop="@dimen/margin_small"
android:clickable="true"
android:src="@drawable/selector_chevron"/>
<TextView
android:id="@+id/btn_reply"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/text"
android:layout_marginRight="@dimen/margin_medium"
android:layout_toLeftOf="@id/chevron"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:padding="@dimen/margin_medium"
android:text="@string/btn_reply"
android:textColor="@color/briar_button_positive"
android:textSize="@dimen/text_size_tiny"/>
<TextView
android:id="@+id/replies"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@id/btn_reply"
android:layout_toLeftOf="@id/btn_reply"
android:layout_alignBottom="@+id/author"
android:layout_alignTop="@+id/author"
android:layout_toLeftOf="@+id/btn_reply"
android:layout_toRightOf="@+id/author"
android:gravity="right|end"
android:ellipsize="end"
android:gravity="right|end|center_vertical"
android:maxLines="1"
android:padding="@dimen/margin_medium"
android:textSize="@dimen/text_size_tiny"
tools:text="2 replies"/>
<TextView
android:id="@+id/btn_reply"
style="@style/BriarButtonFlat.Positive.Tiny"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/author"
android:layout_toLeftOf="@+id/chevron"
android:text="@string/btn_reply"
android:textSize="@dimen/text_size_tiny"/>
<ImageView
android:id="@+id/chevron"
android:layout_width="@dimen/button_size"
android:layout_height="@dimen/button_size"
android:layout_alignBottom="@+id/author"
android:layout_alignParentRight="true"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:padding="@dimen/margin_medium"
android:scaleType="center"
android:src="@drawable/selector_chevron"/>
<View
android:id="@+id/top_divider"
style="@style/Divider.ForumList"

View File

@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/layout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_medium"
android:baselineAligned="false"
android:orientation="vertical">
<View
android:id="@+id/top_divider"
style="@style/Divider.ForumList"
android:layout_width="match_parent"
android:layout_height="@dimen/margin_separator"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_small"
android:layout_marginLeft="@dimen/margin_medium"
android:layout_marginRight="@dimen/margin_medium"
android:layout_marginTop="@dimen/margin_medium"
android:orientation="horizontal">
<org.briarproject.android.view.AuthorView
android:id="@+id/author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:persona="commenter"/>
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/margin_medium"
android:gravity="center_vertical"
android:textColor="@color/briar_text_secondary"
android:textIsSelectable="true"
android:textSize="@dimen/text_size_medium"
android:textStyle="italic"
tools:text="@string/groups_member_joined"/>
</LinearLayout>
</LinearLayout>

View File

@@ -31,6 +31,7 @@
<dimen name="avatar_forum_size">48dp</dimen>
<dimen name="avatar_border_width">2dp</dimen>
<dimen name="avatar_text_size">30sp</dimen>
<dimen name="button_size">48dp</dimen>
<dimen name="unread_bubble_text_size">12sp</dimen>
<dimen name="unread_bubble_padding_horizontal">6dp</dimen>

View File

@@ -74,6 +74,7 @@
<string name="delete">Delete</string>
<string name="accept">Accept</string>
<string name="decline">Decline</string>
<string name="options">Options</string>
<string name="online">Online</string>
<string name="offline">Offline</string>
<string name="send">Send</string>
@@ -169,7 +170,10 @@
<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_member_joined">joined the group.</string>
<string name="groups_member_created_you">You created the group</string>
<string name="groups_member_created">%s created the group</string>
<string name="groups_member_joined_you">You joined the group</string>
<string name="groups_member_joined">%s joined the group</string>
<string name="groups_leave">Leave Group</string>
<string name="groups_leave_dialog_title">Confirm Leaving Group</string>
<string name="groups_leave_dialog_message">Are you sure that you want to leave this group?</string>
@@ -220,7 +224,7 @@
<string name="forum_new_entry_received">New forum entry</string>
<string name="forum_new_message_hint">New Entry</string>
<string name="forum_message_reply_hint">New Reply</string>
<string name="btn_reply">REPLY</string>
<string name="btn_reply">Reply</string>
<plurals name="message_replies">
<item quantity="one">%1$d reply</item>
<item quantity="other">%1$d replies</item>

View File

@@ -45,6 +45,12 @@
<item name="android:padding">@dimen/margin_large</item>
</style>
<style name="BriarButtonFlat.Positive.Tiny" parent="BriarButtonFlat.Positive">
<item name="android:textSize">@dimen/text_size_tiny</item>
<item name="android:padding">@dimen/margin_medium</item>
<item name="android:minWidth">@dimen/button_size</item>
</style>
<style name="BriarTextTitle">
<item name="android:textSize">@dimen/text_size_medium</item>
<item name="android:textColor">@android:color/primary_text_light</item>

View File

@@ -5,6 +5,7 @@ import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
@@ -26,6 +27,8 @@ import org.briarproject.android.threaded.ThreadListController;
import org.briarproject.api.db.DbException;
import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumPostHeader;
import org.briarproject.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.api.nullsafety.ParametersNotNullByDefault;
import javax.inject.Inject;
@@ -35,8 +38,10 @@ import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
import static android.widget.Toast.LENGTH_SHORT;
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ForumActivity extends
ThreadListActivity<Forum, ForumItem, ForumPostHeader> {
ThreadListActivity<Forum, ThreadItemAdapter<ForumItem>, ForumItem, ForumPostHeader> {
private static final int REQUEST_FORUM_SHARED = 3;
@@ -54,7 +59,7 @@ public class ForumActivity extends
}
@Override
public void onCreate(Bundle state) {
public void onCreate(@Nullable Bundle state) {
super.onCreate(state);
Intent i = getIntent();

View File

@@ -0,0 +1,26 @@
package org.briarproject.android.privategroup;
import android.support.annotation.StringRes;
import org.briarproject.R;
import org.briarproject.api.privategroup.Visibility;
public class VisibilityStringProvider {
@StringRes
public static int getVisibilityStringId(Visibility v) {
switch (v) {
case VISIBLE:
return R.string.groups_reveal_visible;
case REVEALED_BY_US:
return R.string.groups_reveal_visible_revealed_by_us;
case REVEALED_BY_CONTACT:
return R.string.groups_reveal_visible_revealed_by_contact;
case INVISIBLE:
return R.string.groups_reveal_invisible;
default:
throw new IllegalArgumentException("Unknown visibility");
}
}
}

View File

@@ -5,6 +5,7 @@ import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
@@ -26,10 +27,13 @@ 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;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.api.privategroup.GroupMessageHeader;
import org.briarproject.api.privategroup.PrivateGroup;
import org.briarproject.api.privategroup.Visibility;
import javax.inject.Inject;
@@ -40,7 +44,7 @@ import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class GroupActivity extends
ThreadListActivity<PrivateGroup, GroupMessageItem, GroupMessageHeader>
ThreadListActivity<PrivateGroup, GroupMessageAdapter, GroupMessageItem, GroupMessageHeader>
implements GroupListener, OnClickListener {
private final static int REQUEST_INVITE = 2;
@@ -63,7 +67,7 @@ public class GroupActivity extends
}
@Override
public void onCreate(Bundle state) {
public void onCreate(@Nullable Bundle state) {
super.onCreate(state);
Intent i = getIntent();
@@ -105,7 +109,7 @@ public class GroupActivity extends
}
@Override
protected void onNamedGroupLoaded(PrivateGroup group) {
protected void onNamedGroupLoaded(final PrivateGroup group) {
setTitle(group.getName());
// Created by
ActionBar actionBar = getSupportActionBar();
@@ -113,11 +117,12 @@ public class GroupActivity extends
actionBar.setSubtitle(getString(R.string.groups_created_by,
group.getCreator().getName()));
}
controller.isCreator(group,
new UiResultExceptionHandler<Boolean, DbException>(this) {
controller.loadLocalAuthor(
new UiResultExceptionHandler<LocalAuthor, DbException>(this) {
@Override
public void onResultUi(Boolean isCreator) {
GroupActivity.this.isCreator = isCreator;
public void onResultUi(LocalAuthor author) {
isCreator = group.getCreator().equals(author);
adapter.setPerspective(isCreator);
showMenuItems();
}
@@ -272,6 +277,11 @@ public class GroupActivity extends
});
}
@Override
public void onContactRelationshipRevealed(AuthorId memberId, Visibility v) {
adapter.updateVisibility(memberId, v);
}
@Override
public void onGroupDissolved() {
setGroupEnabled(false);

View File

@@ -5,20 +5,26 @@ import android.support.annotation.UiThread;
import org.briarproject.android.controller.handler.ResultExceptionHandler;
import org.briarproject.android.threaded.ThreadListController;
import org.briarproject.api.db.DbException;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.privategroup.GroupMessageHeader;
import org.briarproject.api.privategroup.PrivateGroup;
import org.briarproject.api.privategroup.Visibility;
public interface GroupController
extends
ThreadListController<PrivateGroup, GroupMessageItem, GroupMessageHeader> {
void isCreator(PrivateGroup group,
ResultExceptionHandler<Boolean, DbException> handler);
void loadLocalAuthor(
ResultExceptionHandler<LocalAuthor, DbException> handler);
void isDissolved(
ResultExceptionHandler<Boolean, DbException> handler);
interface GroupListener extends ThreadListListener<GroupMessageHeader> {
@UiThread
void onContactRelationshipRevealed(AuthorId memberId, Visibility v);
@UiThread
void onGroupDissolved();
}

View File

@@ -17,6 +17,7 @@ import org.briarproject.api.event.GroupMessageAddedEvent;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.privategroup.ContactRelationshipRevealedEvent;
import org.briarproject.api.privategroup.GroupMessage;
import org.briarproject.api.privategroup.GroupMessageFactory;
import org.briarproject.api.privategroup.GroupMessageHeader;
@@ -33,7 +34,6 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static java.lang.Math.max;
import static java.util.logging.Level.WARNING;
public class GroupControllerImpl extends
@@ -81,6 +81,18 @@ public class GroupControllerImpl extends
}
});
}
} else if (e instanceof ContactRelationshipRevealedEvent) {
final ContactRelationshipRevealedEvent c =
(ContactRelationshipRevealedEvent) e;
if (getGroupId().equals(c.getGroupId())) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override
public void run() {
listener.onContactRelationshipRevealed(c.getMemberId(),
c.getVisibility());
}
});
}
} else if (e instanceof GroupDissolvedEvent) {
GroupDissolvedEvent g = (GroupDissolvedEvent) e;
if (getGroupId().equals(g.getGroupId())) {
@@ -178,22 +190,20 @@ public class GroupControllerImpl extends
protected GroupMessageItem buildItem(GroupMessageHeader header,
String body) {
if (header instanceof JoinMessageHeader) {
return new JoinMessageItem(header, body);
return new JoinMessageItem((JoinMessageHeader) header, body);
}
return new GroupMessageItem(header, body);
}
@Override
public void isCreator(final PrivateGroup group,
final ResultExceptionHandler<Boolean, DbException> handler) {
public void loadLocalAuthor(
final ResultExceptionHandler<LocalAuthor, DbException> handler) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
LocalAuthor author = identityManager.getLocalAuthor();
boolean isCreator =
author.getId().equals(group.getCreator().getId());
handler.onResult(isCreator);
handler.onResult(author);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);

View File

@@ -11,9 +11,17 @@ import org.briarproject.R;
import org.briarproject.android.threaded.BaseThreadItemViewHolder;
import org.briarproject.android.threaded.ThreadItemAdapter;
import org.briarproject.android.threaded.ThreadPostViewHolder;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.privategroup.Visibility;
import static android.support.v7.widget.RecyclerView.NO_POSITION;
@UiThread
public class GroupMessageAdapter extends ThreadItemAdapter<GroupMessageItem> {
@NotNullByDefault
class GroupMessageAdapter extends ThreadItemAdapter<GroupMessageItem> {
private boolean isCreator = false;
public GroupMessageAdapter(ThreadItemListener<GroupMessageItem> listener,
LinearLayoutManager layoutManager) {
@@ -33,10 +41,36 @@ public class GroupMessageAdapter extends ThreadItemAdapter<GroupMessageItem> {
ViewGroup parent, int type) {
View v = LayoutInflater.from(parent.getContext())
.inflate(type, parent, false);
if (type == R.layout.list_item_thread_notice) {
return new JoinMessageItemViewHolder(v);
if (type == R.layout.list_item_group_join_notice) {
return new JoinMessageItemViewHolder(v, isCreator);
}
return new ThreadPostViewHolder<>(v);
}
void setPerspective(boolean isCreator) {
this.isCreator = isCreator;
notifyDataSetChanged();
}
void updateVisibility(AuthorId memberId, Visibility v) {
int position = findItemPosition(memberId);
if (position != NO_POSITION) {
GroupMessageItem item = items.get(position);
if (item instanceof JoinMessageItem) {
((JoinMessageItem) item).setVisibility(v);
notifyItemChanged(getVisiblePos(item), item);
}
}
}
private int findItemPosition(AuthorId a) {
int count = items.size();
for (int i = 0; i < count; i++) {
GroupMessageItem item = items.get(i);
if (item.getAuthor().getId().equals(a))
return i;
}
return NO_POSITION; // Not found
}
}

View File

@@ -8,6 +8,7 @@ import org.briarproject.android.threaded.ThreadItem;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.Author.Status;
import org.briarproject.api.privategroup.GroupMessageHeader;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import javax.annotation.concurrent.NotThreadSafe;
@@ -16,15 +17,23 @@ import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe
class GroupMessageItem extends ThreadItem {
private GroupMessageItem(MessageId messageId, MessageId parentId,
private final GroupId groupId;
private GroupMessageItem(MessageId messageId, GroupId groupId,
MessageId parentId,
String text, long timestamp, Author author, Status status,
boolean isRead) {
super(messageId, parentId, text, timestamp, author, status, isRead);
this.groupId = groupId;
}
GroupMessageItem(GroupMessageHeader h, String text) {
this(h.getId(), h.getParentId(), text, h.getTimestamp(), h.getAuthor(),
h.getAuthorStatus(), h.isRead());
this(h.getId(), h.getGroupId(), h.getParentId(), text, h.getTimestamp(),
h.getAuthor(), h.getAuthorStatus(), h.isRead());
}
public GroupId getGroupId() {
return groupId;
}
@LayoutRes

View File

@@ -4,7 +4,8 @@ import android.support.annotation.LayoutRes;
import android.support.annotation.UiThread;
import org.briarproject.R;
import org.briarproject.api.privategroup.GroupMessageHeader;
import org.briarproject.api.privategroup.JoinMessageHeader;
import org.briarproject.api.privategroup.Visibility;
import javax.annotation.concurrent.NotThreadSafe;
@@ -12,9 +13,13 @@ import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe
class JoinMessageItem extends GroupMessageItem {
JoinMessageItem(GroupMessageHeader h,
String text) {
private Visibility visibility;
private final boolean isInitial;
JoinMessageItem(JoinMessageHeader h, String text) {
super(h, text);
this.visibility = h.getVisibility();
this.isInitial = h.isInitial();
}
@Override
@@ -29,7 +34,19 @@ class JoinMessageItem extends GroupMessageItem {
@LayoutRes
public int getLayout() {
return R.layout.list_item_thread_notice;
return R.layout.list_item_group_join_notice;
}
public Visibility getVisibility() {
return visibility;
}
public void setVisibility(Visibility visibility) {
this.visibility = visibility;
}
public boolean isInitial() {
return isInitial;
}
}

View File

@@ -1,30 +1,108 @@
package org.briarproject.android.privategroup.conversation;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.android.privategroup.reveal.RevealContactsActivity;
import org.briarproject.android.threaded.BaseThreadItemViewHolder;
import org.briarproject.android.threaded.ThreadItemAdapter;
import org.briarproject.android.threaded.ThreadItemAdapter.ThreadItemListener;
import org.briarproject.api.nullsafety.NotNullByDefault;
import static org.briarproject.android.BriarActivity.GROUP_ID;
import static org.briarproject.android.privategroup.VisibilityStringProvider.getVisibilityStringId;
import static org.briarproject.api.identity.Author.Status.OURSELVES;
import static org.briarproject.api.identity.Author.Status.UNKNOWN;
import static org.briarproject.api.privategroup.Visibility.INVISIBLE;
@UiThread
@NotNullByDefault
public class JoinMessageItemViewHolder
class JoinMessageItemViewHolder
extends BaseThreadItemViewHolder<GroupMessageItem> {
public JoinMessageItemViewHolder(View v) {
private final boolean isCreator;
private final ImageView icon;
private final TextView info;
private final Button options;
public JoinMessageItemViewHolder(View v, boolean isCreator) {
super(v);
this.isCreator = isCreator;
icon = (ImageView) v.findViewById(R.id.icon);
info = (TextView) v.findViewById(R.id.info);
options = (Button) v.findViewById(R.id.optionsButton);
}
@Override
public void bind(final ThreadItemAdapter<GroupMessageItem> adapter,
final ThreadItemListener<GroupMessageItem> listener,
final GroupMessageItem item, int pos) {
public void bind(ThreadItemAdapter<GroupMessageItem> adapter,
ThreadItemListener<GroupMessageItem> listener,
GroupMessageItem item, int pos) {
super.bind(adapter, listener, item, pos);
textView.setText(getContext().getString(R.string.groups_member_joined));
if (isCreator) bindForCreator((JoinMessageItem) item);
else bind((JoinMessageItem) item);
}
private void bindForCreator(final JoinMessageItem item) {
if (item.isInitial()) {
textView.setText(R.string.groups_member_created_you);
} else {
textView.setText(
getContext().getString(R.string.groups_member_joined,
item.getAuthor().getName()));
}
icon.setVisibility(View.GONE);
info.setVisibility(View.GONE);
options.setVisibility(View.GONE);
}
private void bind(final JoinMessageItem item) {
final Context ctx = getContext();
if (item.isInitial()) {
textView.setText(ctx.getString(R.string.groups_member_created,
item.getAuthor().getName()));
} else {
if (item.getStatus() == OURSELVES) {
textView.setText(R.string.groups_member_joined_you);
} else {
textView.setText(ctx.getString(R.string.groups_member_joined,
item.getAuthor().getName()));
}
}
if (item.getStatus() == OURSELVES || item.getStatus() == UNKNOWN) {
icon.setVisibility(View.GONE);
info.setVisibility(View.GONE);
options.setVisibility(View.GONE);
} else {
icon.setVisibility(View.VISIBLE);
info.setVisibility(View.VISIBLE);
info.setText(getVisibilityStringId(item.getVisibility()));
if (item.getVisibility() == INVISIBLE) {
icon.setImageResource(R.drawable.ic_visibility_off);
options.setVisibility(View.VISIBLE);
options.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i =
new Intent(ctx, RevealContactsActivity.class);
i.putExtra(GROUP_ID, item.getGroupId().getBytes());
ctx.startActivity(i);
}
});
} else {
icon.setImageResource(R.drawable.ic_visibility);
options.setVisibility(View.GONE);
}
}
}
}

View File

@@ -10,6 +10,7 @@ import org.briarproject.android.contactselection.BaseSelectableContactHolder;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.jetbrains.annotations.Nullable;
import static org.briarproject.android.privategroup.VisibilityStringProvider.getVisibilityStringId;
import static org.briarproject.android.util.AndroidUtils.GREY_OUT;
import static org.briarproject.api.privategroup.Visibility.INVISIBLE;
@@ -31,21 +32,7 @@ public class RevealableContactViewHolder
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;
}
info.setText(getVisibilityStringId(item.getVisibility()));
if (item.getVisibility() == INVISIBLE) {
icon.setImageResource(R.drawable.ic_visibility_off);

View File

@@ -27,7 +27,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
static final int UNDEFINED = -1;
private final NestedTreeList<I> items = new NestedTreeList<>();
protected final NestedTreeList<I> items = new NestedTreeList<>();
private final Map<I, ValueAnimator> animatingItems = new HashMap<>();
private final ThreadItemListener<I> listener;
private final LinearLayoutManager layoutManager;
@@ -61,6 +61,11 @@ public class ThreadItemAdapter<I extends ThreadItem>
ui.bind(this, listener, item, position);
}
/**
* Contrary to the super class adapter,
* this returns the number of <b>visible</b> items,
* not all items in the dataset.
*/
@Override
public int getItemCount() {
return getVisiblePos(null);
@@ -268,7 +273,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
* items if 'item' is null. If 'item' is not visible, NO_POSITION is
* returned.
*/
private int getVisiblePos(@Nullable I item) {
protected int getVisiblePos(@Nullable I item) {
int visibleCounter = 0;
int levelLimit = UNDEFINED;
for (I i : items) {

View File

@@ -24,6 +24,8 @@ import org.briarproject.android.view.TextInputView.TextInputListener;
import org.briarproject.api.clients.NamedGroup;
import org.briarproject.api.clients.PostHeader;
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 org.briarproject.api.sync.MessageId;
import org.briarproject.util.StringUtils;
@@ -35,7 +37,9 @@ import static android.support.design.widget.Snackbar.make;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadItem, H extends PostHeader>
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadItemAdapter<I>, I extends ThreadItem, H extends PostHeader>
extends BriarActivity
implements ThreadListListener<H>, TextInputListener,
ThreadItemListener<I> {
@@ -46,7 +50,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
private static final Logger LOG =
Logger.getLogger(ThreadListActivity.class.getName());
protected ThreadItemAdapter<I> adapter;
protected A adapter;
protected BriarRecyclerView list;
protected TextInputView textInput;
protected GroupId groupId;
@@ -57,7 +61,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
@CallSuper
@Override
@SuppressWarnings("ConstantConditions")
public void onCreate(final Bundle state) {
public void onCreate(@Nullable Bundle state) {
super.onCreate(state);
setContentView(getLayout());
@@ -88,8 +92,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
@LayoutRes
protected abstract int getLayout();
protected abstract ThreadItemAdapter<I> createAdapter(
LinearLayoutManager layoutManager);
protected abstract A createAdapter(LinearLayoutManager layoutManager);
protected void loadNamedGroup() {
getController().loadNamedGroup(

View File

@@ -1,6 +1,7 @@
package org.briarproject.api.privategroup;
import org.briarproject.api.event.Event;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.sync.GroupId;
@@ -11,11 +12,13 @@ import javax.annotation.concurrent.Immutable;
public class ContactRelationshipRevealedEvent extends Event {
private final GroupId groupId;
private final AuthorId memberId;
private final Visibility visibility;
public ContactRelationshipRevealedEvent(GroupId groupId,
public ContactRelationshipRevealedEvent(GroupId groupId, AuthorId memberId,
Visibility visibility) {
this.groupId = groupId;
this.memberId = memberId;
this.visibility = visibility;
}
@@ -23,6 +26,10 @@ public class ContactRelationshipRevealedEvent extends Event {
return groupId;
}
public AuthorId getMemberId() {
return memberId;
}
public Visibility getVisibility() {
return visibility;
}

View File

@@ -9,15 +9,21 @@ import javax.annotation.concurrent.Immutable;
public class JoinMessageHeader extends GroupMessageHeader {
private final Visibility visibility;
private final boolean isInitial;
public JoinMessageHeader(GroupMessageHeader h, Visibility visibility) {
public JoinMessageHeader(GroupMessageHeader h, Visibility visibility, boolean isInitial) {
super(h.getGroupId(), h.getId(), h.getParentId(), h.getTimestamp(),
h.getAuthor(), h.getAuthorStatus(), h.isRead());
this.visibility = visibility;
this.isInitial = isInitial;
}
public Visibility getVisibility() {
return visibility;
}
public boolean isInitial() {
return isInitial;
}
}

View File

@@ -13,6 +13,7 @@ interface GroupConstants {
String KEY_MEMBER_ID = "memberId";
String KEY_MEMBER_NAME = "memberName";
String KEY_MEMBER_PUBLIC_KEY = "memberPublicKey";
String KEY_INITIAL_JOIN_MSG = "initialJoinMsg";
String GROUP_KEY_MEMBERS = "members";
String GROUP_KEY_OUR_GROUP = "ourGroup";

View File

@@ -29,6 +29,7 @@ import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH
import static org.briarproject.api.privategroup.MessageType.JOIN;
import static org.briarproject.api.privategroup.MessageType.POST;
import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH;
import static org.briarproject.privategroup.GroupConstants.KEY_INITIAL_JOIN_MSG;
import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_ID;
import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_NAME;
import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_PUBLIC_KEY;
@@ -99,10 +100,12 @@ class GroupMessageValidator extends BdfMessageValidator {
// invite is null if the member is the creator of the private group
Author creator = pg.getCreator();
boolean isCreator = false;
BdfList invite = body.getOptionalList(3);
if (invite == null) {
if (!member.equals(creator))
throw new InvalidMessageException();
isCreator = true;
} else {
if (member.equals(creator))
throw new InvalidMessageException();
@@ -149,6 +152,7 @@ class GroupMessageValidator extends BdfMessageValidator {
// Return the metadata and no dependencies
BdfDictionary meta = new BdfDictionary();
meta.put(KEY_INITIAL_JOIN_MSG, isCreator);
return new BdfMessageContext(meta);
}

View File

@@ -60,6 +60,7 @@ import static org.briarproject.privategroup.GroupConstants.GROUP_KEY_DISSOLVED;
import static org.briarproject.privategroup.GroupConstants.GROUP_KEY_MEMBERS;
import static org.briarproject.privategroup.GroupConstants.GROUP_KEY_OUR_GROUP;
import static org.briarproject.privategroup.GroupConstants.GROUP_KEY_VISIBILITY;
import static org.briarproject.privategroup.GroupConstants.KEY_INITIAL_JOIN_MSG;
import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_ID;
import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_NAME;
import static org.briarproject.privategroup.GroupConstants.KEY_MEMBER_PUBLIC_KEY;
@@ -114,16 +115,17 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
new BdfEntry(GROUP_KEY_DISSOLVED, false)
);
clientHelper.mergeGroupMetadata(txn, group.getId(), meta);
joinPrivateGroup(txn, joinMsg);
joinPrivateGroup(txn, joinMsg, creator);
} catch (FormatException e) {
throw new DbException(e);
}
}
private void joinPrivateGroup(Transaction txn, GroupMessage m)
throws DbException, FormatException {
private void joinPrivateGroup(Transaction txn, GroupMessage m,
boolean creator) throws DbException, FormatException {
BdfDictionary meta = new BdfDictionary();
meta.put(KEY_TYPE, JOIN.getInt());
meta.put(KEY_INITIAL_JOIN_MSG, creator);
addMessageMetadata(meta, m, true);
clientHelper.addLocalMessage(txn, m.getMessage(), meta, true);
trackOutgoingMessage(txn, m.getMessage());
@@ -377,7 +379,8 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
GroupMessageHeader header =
getGroupMessageHeader(txn, g, id, meta, statuses);
return new JoinMessageHeader(header, v);
boolean creator = meta.getBoolean(KEY_INITIAL_JOIN_MSG);
return new JoinMessageHeader(header, v, creator);
}
@Override
@@ -451,7 +454,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
if (!foundMember) throw new ProtocolStateException();
if (changed) {
clientHelper.mergeGroupMetadata(txn, g, meta);
txn.attach(new ContactRelationshipRevealedEvent(g, v));
txn.attach(new ContactRelationshipRevealedEvent(g, a, v));
}
}