diff --git a/briar-android/res/layout/author_view.xml b/briar-android/res/layout/author_view.xml index 48892daf258b52dd9e311e73a24787f743d1d928..6f389e1de237bb90a63e5ca5c5d3a4cfd779b92e 100644 --- a/briar-android/res/layout/author_view.xml +++ b/briar-android/res/layout/author_view.xml @@ -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" diff --git a/briar-android/res/layout/list_item_group_join_notice.xml b/briar-android/res/layout/list_item_group_join_notice.xml new file mode 100644 index 0000000000000000000000000000000000000000..71d6ceec17f6c855a735bc59b39948a860e146f3 --- /dev/null +++ b/briar-android/res/layout/list_item_group_join_notice.xml @@ -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> diff --git a/briar-android/res/layout/list_item_thread.xml b/briar-android/res/layout/list_item_thread.xml index f788c0ce8e6aff841c4d3f4dcae07e423576966c..3757cc7e7c9e3335e095ef62a2ad709d906b6487 100644 --- a/briar-android/res/layout/list_item_thread.xml +++ b/briar-android/res/layout/list_item_thread.xml @@ -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,60 +76,58 @@ 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" + <TextView + android:id="@+id/replies" 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"/> + android:layout_alignBottom="@+id/author" + android:layout_alignTop="@+id/author" + android:layout_toLeftOf="@+id/btn_reply" + android:layout_toRightOf="@+id/author" + 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_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:layout_alignBottom="@+id/author" + android:layout_toLeftOf="@+id/chevron" 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_toRightOf="@+id/author" - android:gravity="right|end" - android:maxLines="1" + <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:textSize="@dimen/text_size_tiny" - tools:text="2 replies"/> + android:scaleType="center" + android:src="@drawable/selector_chevron"/> <View android:id="@+id/top_divider" diff --git a/briar-android/res/layout/list_item_thread_notice.xml b/briar-android/res/layout/list_item_thread_notice.xml deleted file mode 100644 index 2beecd1f2aabf4c2a2b209974e6d383268e3d8b1..0000000000000000000000000000000000000000 --- a/briar-android/res/layout/list_item_thread_notice.xml +++ /dev/null @@ -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> diff --git a/briar-android/res/values/dimens.xml b/briar-android/res/values/dimens.xml index fc0531ab66293a47c0cc211e5d13da23d2589661..6662acfe4927fed5d6ce40d868c4d6ad15299ef3 100644 --- a/briar-android/res/values/dimens.xml +++ b/briar-android/res/values/dimens.xml @@ -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> diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml index cc6f0f7d64c1be12b2ecfbb9dd4901e25f1c50ac..3dbbb17a0eb36f71837289387329d1a7b6734ccc 100644 --- a/briar-android/res/values/strings.xml +++ b/briar-android/res/values/strings.xml @@ -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> diff --git a/briar-android/res/values/styles.xml b/briar-android/res/values/styles.xml index 71b6c910cecab6412a25ae372a47a5c8eb120c4f..0896e6e6b327e8f68ab6b30593b81ce03cad8a2c 100644 --- a/briar-android/res/values/styles.xml +++ b/briar-android/res/values/styles.xml @@ -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> diff --git a/briar-android/src/org/briarproject/android/forum/ForumActivity.java b/briar-android/src/org/briarproject/android/forum/ForumActivity.java index 22c713889bd2e88c9ff0a5bac7dc0beb2a58f3b5..0fe164367f3e1b8c26bd18f64e15ad88d59e8cf9 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumActivity.java +++ b/briar-android/src/org/briarproject/android/forum/ForumActivity.java @@ -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(); diff --git a/briar-android/src/org/briarproject/android/privategroup/VisibilityStringProvider.java b/briar-android/src/org/briarproject/android/privategroup/VisibilityStringProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..0cd7e0dc7077855516a57ac9d6425d873669914e --- /dev/null +++ b/briar-android/src/org/briarproject/android/privategroup/VisibilityStringProvider.java @@ -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"); + } + } + +} diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupActivity.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupActivity.java index 8a24aaba657bedc4176dc182ff4dfd60ec5e44ab..438c061120faaa8a4549eb8e31ed4fd3abe90318 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupActivity.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupActivity.java @@ -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); diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupController.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupController.java index 18915ab15df9bfb700b69f518d3099c9edae274f..523abc1b9b7b62c9e2d61df072fd57b54274edf5 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupController.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupController.java @@ -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(); } diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java index 1a11bd88ef5a23e35d0e424ecacd76e3dd75cd17..630da0e85b24e0824993ea0122d1441782b380b0 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java @@ -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); diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageAdapter.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageAdapter.java index c042a182965f3dbe067a3a5a343898c1f13cb0f1..4c94438e0739060fa29bed3fc38e24e960836001 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageAdapter.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageAdapter.java @@ -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 + } + } diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageItem.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageItem.java index 9deb0424e574fc582b56dc687246c3f74d3438fb..47104e88284b78e59bcdfa5f215327ef468034b3 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageItem.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupMessageItem.java @@ -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 diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItem.java b/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItem.java index 44c732ffd48970cc59a4abd2a92e4bebac232219..c48f781a086605c1ce92e1ef5077d5bd4e3484a8 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItem.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItem.java @@ -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; } } diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItemViewHolder.java b/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItemViewHolder.java index 972a6149eaa0d3093156c1df28bbe2c43fa77954..d8705c8b949bfaedd584274499fe70b1dbca1dc0 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItemViewHolder.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/JoinMessageItemViewHolder.java @@ -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); + } + } } } diff --git a/briar-android/src/org/briarproject/android/privategroup/reveal/RevealableContactViewHolder.java b/briar-android/src/org/briarproject/android/privategroup/reveal/RevealableContactViewHolder.java index f2131482cb7b0d5c6fdcd9e12f129a83d17a5df4..5261a58e27009e18f02ca1c38cdf1981a82707a4 100644 --- a/briar-android/src/org/briarproject/android/privategroup/reveal/RevealableContactViewHolder.java +++ b/briar-android/src/org/briarproject/android/privategroup/reveal/RevealableContactViewHolder.java @@ -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); diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java b/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java index 7c3ee9079128f1d1424ab570e78f1da3c3b126e4..76b062508acdc56a81027b49fb24b7cbcec60771 100644 --- a/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java +++ b/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java @@ -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) { diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java b/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java index e4bd39e2af758f4af1bddbb61462a9dc780dd74f..ed2ebdfa913c988a20df610348cffcebce41ff6b 100644 --- a/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java +++ b/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java @@ -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( diff --git a/briar-api/src/org/briarproject/api/privategroup/ContactRelationshipRevealedEvent.java b/briar-api/src/org/briarproject/api/privategroup/ContactRelationshipRevealedEvent.java index 40de39d1eb2cd591054c38ecc913f4ccb224a355..2fd120315844edf27ab8a67818086c4e8815c75d 100644 --- a/briar-api/src/org/briarproject/api/privategroup/ContactRelationshipRevealedEvent.java +++ b/briar-api/src/org/briarproject/api/privategroup/ContactRelationshipRevealedEvent.java @@ -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; } diff --git a/briar-api/src/org/briarproject/api/privategroup/JoinMessageHeader.java b/briar-api/src/org/briarproject/api/privategroup/JoinMessageHeader.java index 8b14717a6e622d9f3a52f745673bb90e46088d39..ae83be9327ae6555b9128487760296ff692ebb5a 100644 --- a/briar-api/src/org/briarproject/api/privategroup/JoinMessageHeader.java +++ b/briar-api/src/org/briarproject/api/privategroup/JoinMessageHeader.java @@ -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; + } + } diff --git a/briar-core/src/org/briarproject/privategroup/GroupConstants.java b/briar-core/src/org/briarproject/privategroup/GroupConstants.java index 72f57748c89f72a54b812da31aca614e8acef5eb..b1e203aa7a1da3c23f3732948aa6f74f1e7a1aca 100644 --- a/briar-core/src/org/briarproject/privategroup/GroupConstants.java +++ b/briar-core/src/org/briarproject/privategroup/GroupConstants.java @@ -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"; diff --git a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java index 54b6324fc5b2a3d52d8d87716e364f52905c3d8c..13ab704aaee6dc0e11516dcf63dc9f5edf984cd8 100644 --- a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java +++ b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java @@ -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); } diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java index 0a8c436ec0610b12d5a82d0cf48e2b0ffeef9205..462a8e11c18ffc7a923642e543a3986d1cb18705 100644 --- a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java @@ -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)); } }