Merge branch '121-new-forum-sharing-ui' into 'master'

Forum Sharing Client UI

This changes `ShareForumActivity` to use two fragments to facilitate
forum sharing with the new Forum Sharing Client backend.

The `ContactSelectorFragment` allows the user to select a
number of contacts. If there is an ongoing sharing session or the forum
is already shared with the contact, it is disabled in the list. If there
is at least one contact selected, a button appears in the toolbar that
brings the user to the `ShareForumMessageFragment` where the user can
write an optional message to be send along with the invitation.

After sending an invitation, the user is brought back to the forum that
she shared and there is a snackbar showing up briefly to indicate the
successful invitation.

The invitation is shown along with the message within the private
conversation of each contact. The person who shares the forum also sees
the invitation and the message as outgoing messages that also display
the current status of the messages.

A notification is shown like for other private messages as well.

Please note that this commit does not include a way for users to respond
to invitations. This is in MR !172. Both MRs are based on the new backend in !170.

![device-2016-05-03-182928](/uploads/48499ca4c149a8e7f13f8b28a24537ad/device-2016-05-03-182928.png)
![device-2016-05-03-182855](/uploads/5f49c43bf8f39780862247848a5b9c27/device-2016-05-03-182855.png)

See merge request !171
This commit is contained in:
akwizgran
2016-05-11 09:13:07 +00:00
24 changed files with 1039 additions and 181 deletions

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
android:id="@+id/shareForumContainer"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include
android:id="@+id/messageLayout"
layout="@layout/list_item_msg_in"/>
<RelativeLayout
android:id="@+id/introductionLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|start"
android:background="@drawable/notice_in"
android:layout_marginLeft="@dimen/message_bubble_margin_tail"
android:layout_marginRight="@dimen/message_bubble_margin_non_tail">
<TextView
android:id="@+id/introductionText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="80dp"
android:textIsSelectable="true"
android:textSize="@dimen/text_size_medium"
android:textStyle="italic"
tools:text="@string/forum_invitation_received"/>
<TextView
android:id="@+id/introductionTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
android:layout_alignEnd="@+id/introductionText"
android:layout_alignRight="@+id/introductionText"
android:layout_below="@+id/showForumsButton"
android:textColor="@color/private_message_date"
android:textSize="@dimen/text_size_tiny"
tools:text="Dec 24, 13:37"/>
<Button
android:id="@+id/showForumsButton"
style="@style/BriarButtonFlat.Positive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="-15dp"
android:layout_alignEnd="@+id/introductionText"
android:layout_alignRight="@+id/introductionText"
android:layout_below="@+id/introductionText"
android:text="@string/forum_show_available"/>
</RelativeLayout>
</LinearLayout>

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include
android:id="@+id/messageLayout"
layout="@layout/list_item_msg_out"/>
<RelativeLayout
android:id="@+id/introductionLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|end"
android:background="@drawable/notice_out"
android:layout_marginLeft="@dimen/message_bubble_margin_non_tail"
android:layout_marginRight="@dimen/message_bubble_margin_tail">
<TextView
android:id="@+id/introductionText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:textSize="@dimen/text_size_medium"
android:textStyle="italic"
tools:text="@string/introduction_request_received"/>
<TextView
android:id="@+id/introductionTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/introductionText"
android:textColor="@color/private_message_date"
android:textSize="@dimen/text_size_tiny"
tools:text="Dec 24, 13:37"/>
<ImageView
android:id="@+id/introductionStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@+id/introductionTime"
android:layout_toRightOf="@+id/introductionTime"
android:layout_alignBottom="@+id/introductionTime"
android:layout_marginLeft="@dimen/margin_medium"
tools:ignore="ContentDescription"
tools:src="@drawable/message_delivered"/>
</RelativeLayout>
</LinearLayout>

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/margin_activity_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/introductionText"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="@dimen/margin_medium"
android:layout_weight="1"
android:gravity="top"
android:textSize="@dimen/text_size_medium"
android:text="@string/forum_share_message"/>
<EditText
android:id="@+id/invitationMessageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_medium"
android:gravity="bottom"
android:hint="@string/introduction_message_hint"
android:inputType="text|textMultiLine|textCapSentences"/>
<Button
android:id="@+id/shareForumButton"
style="@style/BriarButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/forum_share_button"
/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>

View File

@@ -92,6 +92,11 @@
<string name="forum_created_toast">Forum created</string>
<string name="forum_share_action">Share this forum with chosen contacts</string>
<string name="forum_share_button">Share Forum</string>
<string name="forum_shared_snackbar">Forum shared with chosen contacts</string>
<string name="forum_share_message">You may compose an optional invitation message that will be sent to the selected contacts.</string>
<string name="forum_invitation_received">%1$s has shared the forum \"%2$s\" with you.</string>
<string name="forum_invitation_sent">You have shared the forum \"%1$s\" with %2$s.</string>
<string name="forum_show_available">Show Available Forums</string>
<string name="forum_compose_post">New Forum Post</string>
<string name="from">From:</string>
<string name="anonymous">Anonymous</string>

View File

@@ -5,11 +5,13 @@ import org.briarproject.CoreModule;
import org.briarproject.android.contact.ContactListFragment;
import org.briarproject.android.contact.ConversationActivity;
import org.briarproject.android.forum.AvailableForumsActivity;
import org.briarproject.android.forum.ContactSelectorFragment;
import org.briarproject.android.forum.CreateForumActivity;
import org.briarproject.android.forum.ForumActivity;
import org.briarproject.android.forum.ForumListFragment;
import org.briarproject.android.forum.ReadForumPostActivity;
import org.briarproject.android.forum.ShareForumActivity;
import org.briarproject.android.forum.ShareForumMessageFragment;
import org.briarproject.android.forum.WriteForumPostActivity;
import org.briarproject.android.identity.CreateIdentityActivity;
import org.briarproject.android.introduction.ContactChooserFragment;
@@ -70,6 +72,10 @@ public interface AndroidComponent extends CoreEagerSingletons {
void inject(ShareForumActivity activity);
void inject(ContactSelectorFragment fragment);
void inject(ShareForumMessageFragment fragment);
void inject(ReadForumPostActivity activity);
void inject(ForumActivity activity);

View File

@@ -20,6 +20,7 @@ import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.ForumInvitationReceivedEvent;
import org.briarproject.api.event.IntroductionRequestReceivedEvent;
import org.briarproject.api.event.IntroductionResponseReceivedEvent;
import org.briarproject.api.event.IntroductionSucceededEvent;
@@ -164,13 +165,16 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
}
} else if (e instanceof IntroductionRequestReceivedEvent) {
ContactId c = ((IntroductionRequestReceivedEvent) e).getContactId();
showIntroductionNotifications(c);
showNotificationForPrivateConversation(c);
} else if (e instanceof IntroductionResponseReceivedEvent) {
ContactId c = ((IntroductionResponseReceivedEvent) e).getContactId();
showIntroductionNotifications(c);
showNotificationForPrivateConversation(c);
} else if (e instanceof IntroductionSucceededEvent) {
Contact c = ((IntroductionSucceededEvent) e).getContact();
showIntroductionSucceededNotification(c);
} else if (e instanceof ForumInvitationReceivedEvent) {
ContactId c = ((ForumInvitationReceivedEvent) e).getContactId();
showNotificationForPrivateConversation(c);
}
}
@@ -359,7 +363,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
});
}
private void showIntroductionNotifications(final ContactId c) {
private void showNotificationForPrivateConversation(final ContactId c) {
androidExecutor.execute(new Runnable() {
public void run() {
try {

View File

@@ -29,6 +29,8 @@ import org.briarproject.api.event.ContactStatusChangedEvent;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.MessageValidatedEvent;
import org.briarproject.api.forum.ForumInvitationMessage;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.introduction.IntroductionManager;
@@ -86,6 +88,8 @@ public class ContactListFragment extends BaseEventFragment {
@Inject
protected volatile IntroductionManager introductionManager;
@Inject
protected volatile ForumSharingManager forumSharingManager;
@Inject
protected volatile EventBus eventBus;
@Override
@@ -226,7 +230,8 @@ public class ContactListFragment extends BaseEventFragment {
MessageValidatedEvent m = (MessageValidatedEvent) e;
ClientId c = m.getClientId();
if (m.isValid() && (c.equals(messagingManager.getClientId()) ||
c.equals(introductionManager.getClientId()))) {
c.equals(introductionManager.getClientId()) ||
c.equals(forumSharingManager.getClientId()))) {
LOG.info("Message added, reloading");
reloadConversation(m.getMessage().getGroupId());
}
@@ -317,6 +322,16 @@ public class ContactListFragment extends BaseEventFragment {
if (LOG.isLoggable(INFO))
LOG.info("Loading introduction messages took " + duration + " ms");
now = System.currentTimeMillis();
Collection<ForumInvitationMessage> invitations =
forumSharingManager.getForumInvitationMessages(id);
for (ForumInvitationMessage i : invitations) {
messages.add(ConversationItem.from(i));
}
duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Loading forum invitations took " + duration + " ms");
return messages;
}
}

View File

@@ -42,11 +42,14 @@ import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.ForumInvitationReceivedEvent;
import org.briarproject.api.event.IntroductionRequestReceivedEvent;
import org.briarproject.api.event.IntroductionResponseReceivedEvent;
import org.briarproject.api.event.MessageValidatedEvent;
import org.briarproject.api.event.MessagesAckedEvent;
import org.briarproject.api.event.MessagesSentEvent;
import org.briarproject.api.forum.ForumInvitationMessage;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.introduction.IntroductionManager;
import org.briarproject.api.introduction.IntroductionMessage;
import org.briarproject.api.introduction.IntroductionRequest;
@@ -109,6 +112,7 @@ public class ConversationActivity extends BriarActivity
@Inject protected volatile EventBus eventBus;
@Inject protected volatile PrivateMessageFactory privateMessageFactory;
@Inject protected volatile IntroductionManager introductionManager;
@Inject protected volatile ForumSharingManager forumSharingManager;
private volatile GroupId groupId = null;
private volatile ContactId contactId = null;
private volatile String contactName = null;
@@ -278,10 +282,13 @@ public class ConversationActivity extends BriarActivity
Collection<IntroductionMessage> introductions =
introductionManager
.getIntroductionMessages(contactId);
Collection<ForumInvitationMessage> invitations =
forumSharingManager
.getForumInvitationMessages(contactId);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Loading headers took " + duration + " ms");
displayMessages(headers, introductions);
displayMessages(headers, introductions, invitations);
} catch (NoSuchContactException e) {
finishOnUiThread();
} catch (DbException e) {
@@ -293,11 +300,13 @@ public class ConversationActivity extends BriarActivity
}
private void displayMessages(final Collection<PrivateMessageHeader> headers,
final Collection<IntroductionMessage> introductions) {
final Collection<IntroductionMessage> introductions,
final Collection<ForumInvitationMessage> invitations) {
runOnUiThread(new Runnable() {
public void run() {
sendButton.setEnabled(true);
if (headers.isEmpty() && introductions.isEmpty()) {
if (headers.isEmpty() && introductions.isEmpty() &&
invitations.isEmpty()) {
// we have no messages,
// so let the list know to hide progress bar
list.showData();
@@ -326,6 +335,10 @@ public class ConversationActivity extends BriarActivity
}
items.add(item);
}
for (ForumInvitationMessage i : invitations) {
ConversationItem item = ConversationItem.from(i);
items.add(item);
}
adapter.addAll(items);
// Scroll to the bottom
list.scrollToPosition(adapter.getItemCount() - 1);
@@ -476,6 +489,12 @@ public class ConversationActivity extends BriarActivity
ConversationItem.from(this, contactName, ir);
addIntroduction(item);
}
} else if (e instanceof ForumInvitationReceivedEvent) {
ForumInvitationReceivedEvent event =
(ForumInvitationReceivedEvent) e;
if (event.getContactId().equals(contactId)) {
loadMessages();
}
}
}

View File

@@ -1,6 +1,7 @@
package org.briarproject.android.contact;
import android.content.Context;
import android.content.Intent;
import android.support.v7.util.SortedList;
import android.support.v7.widget.RecyclerView;
import android.text.format.DateUtils;
@@ -13,8 +14,10 @@ import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.api.introduction.IntroductionRequest;
import org.briarproject.android.forum.AvailableForumsActivity;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.forum.ForumInvitationMessage;
import org.briarproject.api.introduction.IntroductionRequest;
import org.briarproject.api.messaging.PrivateMessageHeader;
import org.briarproject.util.StringUtils;
@@ -22,6 +25,8 @@ import java.util.List;
import static android.support.v7.util.SortedList.INVALID_POSITION;
import static android.support.v7.widget.RecyclerView.ViewHolder;
import static org.briarproject.android.contact.ConversationItem.FORUM_INVITATION_IN;
import static org.briarproject.android.contact.ConversationItem.FORUM_INVITATION_OUT;
import static org.briarproject.android.contact.ConversationItem.INTRODUCTION_IN;
import static org.briarproject.android.contact.ConversationItem.INTRODUCTION_OUT;
import static org.briarproject.android.contact.ConversationItem.IncomingItem;
@@ -35,50 +40,7 @@ import static org.briarproject.android.contact.ConversationItem.OutgoingItem;
class ConversationAdapter extends RecyclerView.Adapter {
private final SortedList<ConversationItem> items =
new SortedList<ConversationItem>(ConversationItem.class,
new SortedList.Callback<ConversationItem>() {
@Override
public void onInserted(int position, int count) {
notifyItemRangeInserted(position, count);
}
@Override
public void onChanged(int position, int count) {
notifyItemRangeChanged(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onRemoved(int position, int count) {
notifyItemRangeRemoved(position, count);
}
@Override
public int compare(ConversationItem c1,
ConversationItem c2) {
long time1 = c1.getTime();
long time2 = c2.getTime();
if (time1 < time2) return -1;
if (time1 > time2) return 1;
return 0;
}
@Override
public boolean areItemsTheSame(ConversationItem c1,
ConversationItem c2) {
return c1.getId().equals(c2.getId());
}
@Override
public boolean areContentsTheSame(ConversationItem c1,
ConversationItem c2) {
return c1.equals(c2);
}
});
new SortedList<>(ConversationItem.class, new ListCallbacks());
private Context ctx;
private IntroductionHandler intro;
private String contactName;
@@ -129,6 +91,16 @@ class ConversationAdapter extends RecyclerView.Adapter {
.inflate(R.layout.list_item_notice_out, viewGroup, false);
return new NoticeHolder(v, type);
}
else if (type == FORUM_INVITATION_IN) {
v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.list_item_forum_invitation_in, viewGroup, false);
return new InvitationHolder(v, type);
}
else if (type == FORUM_INVITATION_OUT) {
v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.list_item_forum_invitation_out, viewGroup, false);
return new InvitationHolder(v, type);
}
// incoming message (non-local)
else {
v = LayoutInflater.from(viewGroup.getContext())
@@ -152,6 +124,12 @@ class ConversationAdapter extends RecyclerView.Adapter {
bindNotice((NoticeHolder) ui, (ConversationNoticeOutItem) item);
} else if (item instanceof ConversationNoticeInItem) {
bindNotice((NoticeHolder) ui, (ConversationNoticeInItem) item);
} else if (item instanceof ConversationForumInvitationOutItem) {
bindInvitation((InvitationHolder) ui,
(ConversationForumInvitationOutItem) item, position);
} else if (item instanceof ConversationForumInvitationInItem) {
bindInvitation((InvitationHolder) ui,
(ConversationForumInvitationInItem) item, position);
} else {
throw new IllegalArgumentException("Unhandled Conversation Item");
}
@@ -204,7 +182,7 @@ class ConversationAdapter extends RecyclerView.Adapter {
final IntroductionRequest ir = item.getIntroductionRequest();
final String message = ir.getMessage();
String message = ir.getMessage();
if (StringUtils.isNullOrEmpty(message)) {
ui.messageLayout.setVisibility(View.GONE);
} else {
@@ -300,6 +278,63 @@ class ConversationAdapter extends RecyclerView.Adapter {
}
}
private void bindInvitation(InvitationHolder ui,
final ConversationForumInvitationItem item, final int position) {
ForumInvitationMessage fim = item.getForumInvitationMessage();
String message = fim.getMessage();
if (StringUtils.isNullOrEmpty(message)) {
ui.messageLayout.setVisibility(View.GONE);
} else {
ui.messageLayout.setVisibility(View.VISIBLE);
ui.message.body.setText(message);
ui.message.date.setText(
DateUtils.getRelativeTimeSpanString(ctx, item.getTime()));
}
// Outgoing Invitation
if (item instanceof ConversationForumInvitationOutItem) {
ui.text.setText(ctx.getString(R.string.forum_invitation_sent,
fim.getForumName(), contactName));
ConversationForumInvitationOutItem i =
(ConversationForumInvitationOutItem) item;
if (i.isSeen()) {
ui.status.setImageResource(R.drawable.message_delivered);
ui.message.status.setImageResource(R.drawable.message_delivered_white);
} else if (i.isSent()) {
ui.status.setImageResource(R.drawable.message_sent);
ui.message.status.setImageResource(R.drawable.message_sent_white);
} else {
ui.status.setImageResource(R.drawable.message_stored);
ui.message.status.setImageResource(R.drawable.message_stored_white);
}
}
// Incoming Invitation
else {
ui.text.setText(ctx.getString(R.string.forum_invitation_received,
contactName, fim.getForumName()));
if (fim.isAvailable()) {
ui.showForumsButton.setVisibility(View.VISIBLE);
ui.showForumsButton
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent =
new Intent(ctx,
AvailableForumsActivity.class);
ctx.startActivity(intent);
}
});
} else {
ui.showForumsButton.setVisibility(View.GONE);
}
}
ui.date.setText(
DateUtils.getRelativeTimeSpanString(ctx, item.getTime()));
}
@Override
public int getItemCount() {
return items.size();
@@ -321,8 +356,7 @@ class ConversationAdapter extends RecyclerView.Adapter {
}
public SparseArray<IncomingItem> getIncomingMessages() {
SparseArray<IncomingItem> messages =
new SparseArray<IncomingItem>();
SparseArray<IncomingItem> messages = new SparseArray<>();
for (int i = 0; i < items.size(); i++) {
ConversationItem item = items.get(i);
@@ -334,8 +368,7 @@ class ConversationAdapter extends RecyclerView.Adapter {
}
public SparseArray<OutgoingItem> getOutgoingMessages() {
SparseArray<OutgoingItem> messages =
new SparseArray<OutgoingItem>();
SparseArray<OutgoingItem> messages = new SparseArray<>();
for (int i = 0; i < items.size(); i++) {
ConversationItem item = items.get(i);
@@ -347,8 +380,7 @@ class ConversationAdapter extends RecyclerView.Adapter {
}
public SparseArray<ConversationMessageItem> getPrivateMessages() {
SparseArray<ConversationMessageItem> messages =
new SparseArray<ConversationMessageItem>();
SparseArray<ConversationMessageItem> messages = new SparseArray<>();
for (int i = 0; i < items.size(); i++) {
ConversationItem item = items.get(i);
@@ -394,14 +426,14 @@ class ConversationAdapter extends RecyclerView.Adapter {
private static class IntroductionHolder extends RecyclerView.ViewHolder {
public ViewGroup layout;
public View messageLayout;
public MessageHolder message;
public TextView text;
public Button acceptButton;
public Button declineButton;
public TextView date;
public ImageView status;
final private ViewGroup layout;
final private View messageLayout;
final private MessageHolder message;
final private TextView text;
final private Button acceptButton;
final private Button declineButton;
final private TextView date;
final private ImageView status;
public IntroductionHolder(View v, int type) {
super(v);
@@ -417,16 +449,18 @@ class ConversationAdapter extends RecyclerView.Adapter {
if (type == INTRODUCTION_OUT) {
status = (ImageView) v.findViewById(R.id.introductionStatus);
} else {
status = null;
}
}
}
private static class NoticeHolder extends RecyclerView.ViewHolder {
public ViewGroup layout;
public TextView text;
public TextView date;
public ImageView status;
final private ViewGroup layout;
final private TextView text;
final private TextView date;
final private ImageView status;
public NoticeHolder(View v, int type) {
super(v);
@@ -437,10 +471,85 @@ class ConversationAdapter extends RecyclerView.Adapter {
if (type == NOTICE_OUT) {
status = (ImageView) v.findViewById(R.id.noticeStatus);
} else {
status = null;
}
}
}
private static class InvitationHolder extends RecyclerView.ViewHolder {
final private ViewGroup layout;
final private View messageLayout;
final private MessageHolder message;
final private TextView text;
final private Button showForumsButton;
final private TextView date;
final private ImageView status;
public InvitationHolder(View v, int type) {
super(v);
layout = (ViewGroup) v.findViewById(R.id.introductionLayout);
messageLayout = v.findViewById(R.id.messageLayout);
message = new MessageHolder(messageLayout,
type == FORUM_INVITATION_IN ? MSG_IN : MSG_OUT);
text = (TextView) v.findViewById(R.id.introductionText);
showForumsButton = (Button) v.findViewById(R.id.showForumsButton);
date = (TextView) v.findViewById(R.id.introductionTime);
if (type == FORUM_INVITATION_OUT) {
status = (ImageView) v.findViewById(R.id.introductionStatus);
} else {
status = null;
}
}
}
private class ListCallbacks extends SortedList.Callback<ConversationItem> {
@Override
public void onInserted(int position, int count) {
notifyItemRangeInserted(position, count);
}
@Override
public void onChanged(int position, int count) {
notifyItemRangeChanged(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onRemoved(int position, int count) {
notifyItemRangeRemoved(position, count);
}
@Override
public int compare(ConversationItem c1,
ConversationItem c2) {
long time1 = c1.getTime();
long time2 = c2.getTime();
if (time1 < time2) return -1;
if (time1 > time2) return 1;
return 0;
}
@Override
public boolean areItemsTheSame(ConversationItem c1,
ConversationItem c2) {
return c1.getId().equals(c2.getId());
}
@Override
public boolean areContentsTheSame(ConversationItem c1,
ConversationItem c2) {
return c1.equals(c2);
}
}
public interface IntroductionHandler {
void respondToIntroduction(final SessionId sessionId,
final boolean accept);

View File

@@ -0,0 +1,33 @@
package org.briarproject.android.contact;
import org.briarproject.api.forum.ForumInvitationMessage;
// This class is not thread-safe
public class ConversationForumInvitationInItem
extends ConversationForumInvitationItem
implements ConversationItem.IncomingItem {
private boolean read;
public ConversationForumInvitationInItem(ForumInvitationMessage fim) {
super(fim);
this.read = fim.isRead();
}
@Override
int getType() {
return FORUM_INVITATION_IN;
}
@Override
public boolean isRead() {
return read;
}
@Override
public void setRead(boolean read) {
this.read = read;
}
}

View File

@@ -0,0 +1,19 @@
package org.briarproject.android.contact;
import org.briarproject.api.forum.ForumInvitationMessage;
abstract class ConversationForumInvitationItem extends ConversationItem {
private ForumInvitationMessage fim;
public ConversationForumInvitationItem(ForumInvitationMessage fim) {
super(fim.getId(), fim.getTimestamp());
this.fim = fim;
}
public ForumInvitationMessage getForumInvitationMessage() {
return fim;
}
}

View File

@@ -0,0 +1,49 @@
package org.briarproject.android.contact;
import org.briarproject.api.forum.ForumInvitationMessage;
/**
* This class is needed and can not be replaced by an ConversationNoticeOutItem,
* because it carries the optional invitation message
* to be displayed as a regular private message.
*
* This class is not thread-safe
*/
public class ConversationForumInvitationOutItem
extends ConversationForumInvitationItem
implements ConversationItem.OutgoingItem {
private boolean sent, seen;
public ConversationForumInvitationOutItem(ForumInvitationMessage fim) {
super(fim);
this.sent = fim.isSent();
this.seen = fim.isSeen();
}
@Override
int getType() {
return FORUM_INVITATION_OUT;
}
@Override
public boolean isSent() {
return sent;
}
@Override
public void setSent(boolean sent) {
this.sent = sent;
}
@Override
public boolean isSeen() {
return seen;
}
@Override
public void setSeen(boolean seen) {
this.seen = seen;
}
}

View File

@@ -3,6 +3,7 @@ package org.briarproject.android.contact;
import org.briarproject.api.introduction.IntroductionRequest;
import org.briarproject.api.sync.MessageId;
// This class is not thread-safe
public class ConversationIntroductionInItem extends ConversationIntroductionItem
implements ConversationItem.IncomingItem {

View File

@@ -6,6 +6,8 @@ import org.briarproject.api.introduction.IntroductionRequest;
* This class is needed and can not be replaced by an ConversationNoticeOutItem,
* because it carries the optional introduction message
* to be displayed as a regular private message.
*
* This class is not thread-safe
*/
public class ConversationIntroductionOutItem
extends ConversationIntroductionItem

View File

@@ -3,6 +3,7 @@ package org.briarproject.android.contact;
import android.content.Context;
import org.briarproject.R;
import org.briarproject.api.forum.ForumInvitationMessage;
import org.briarproject.api.introduction.IntroductionMessage;
import org.briarproject.api.introduction.IntroductionRequest;
import org.briarproject.api.introduction.IntroductionResponse;
@@ -20,6 +21,8 @@ public abstract class ConversationItem {
final static int INTRODUCTION_OUT = 4;
final static int NOTICE_IN = 5;
final static int NOTICE_OUT = 6;
final static int FORUM_INVITATION_IN = 7;
final static int FORUM_INVITATION_OUT = 8;
private MessageId id;
private long time;
@@ -92,6 +95,14 @@ public abstract class ConversationItem {
}
}
public static ConversationItem from(ForumInvitationMessage fim) {
if (fim.isLocal()) {
return new ConversationForumInvitationOutItem(fim);
} else {
return new ConversationForumInvitationInItem(fim);
}
}
/** This method should not be used to get user-facing objects,
* Its purpose is to provider data for the contact list.
*/

View File

@@ -2,6 +2,7 @@ package org.briarproject.android.contact;
import org.briarproject.api.sync.MessageId;
// This class is not thread-safe
public class ConversationNoticeInItem extends ConversationNoticeItem implements
ConversationItem.IncomingItem {

View File

@@ -2,6 +2,7 @@ package org.briarproject.android.contact;
import org.briarproject.api.sync.MessageId;
// This class is not thread-safe
public class ConversationNoticeOutItem extends ConversationNoticeItem implements
ConversationItem.OutgoingItem {

View File

@@ -1,6 +1,11 @@
package org.briarproject.android.forum;
import android.content.Context;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.os.Build;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -47,10 +52,16 @@ public class ContactSelectorAdapter
} else {
ui.checkBox.setChecked(false);
}
if (item.isDisabled()) {
// we share this forum already with that contact
ui.layout.setEnabled(false);
grayOutItem(ui);
}
}
public Collection<ContactId> getSelectedContactIds() {
Collection<ContactId> selected = new ArrayList<ContactId>();
Collection<ContactId> selected = new ArrayList<>();
for (int i = 0; i < contacts.size(); i++) {
SelectableContactListItem item =
@@ -78,4 +89,19 @@ public class ContactSelectorAdapter
return compareByName(c1, c2);
}
private void grayOutItem(final SelectableContactHolder ui) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
float alpha = 0.25f;
ui.avatar.setAlpha(alpha);
ui.name.setAlpha(alpha);
ui.checkBox.setAlpha(alpha);
} else {
ColorFilter colorFilter = new PorterDuffColorFilter(Color.GRAY,
PorterDuff.Mode.MULTIPLY);
ui.avatar.setColorFilter(colorFilter);
ui.name.setEnabled(false);
ui.checkBox.setEnabled(false);
}
}
}

View File

@@ -0,0 +1,247 @@
package org.briarproject.android.forum;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.transition.Fade;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.R;
import org.briarproject.android.AndroidComponent;
import org.briarproject.android.contact.BaseContactListAdapter;
import org.briarproject.android.contact.ContactListItem;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.android.util.BriarRecyclerView;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.db.DbException;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.GroupId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.forum.ShareForumActivity.CONTACTS;
import static org.briarproject.android.forum.ShareForumActivity.getContactsFromIds;
import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
public class ContactSelectorFragment extends BaseFragment implements
BaseContactListAdapter.OnItemClickListener {
public final static String TAG = "ContactSelectorFragment";
private ShareForumActivity shareForumActivity;
private Menu menu;
private BriarRecyclerView list;
private ContactSelectorAdapter adapter;
private Collection<ContactId> selectedContacts;
private static final Logger LOG =
Logger.getLogger(ContactSelectorFragment.class.getName());
// Fields that are accessed from background threads must be volatile
protected volatile GroupId groupId;
@Inject
protected volatile ContactManager contactManager;
@Inject
protected volatile IdentityManager identityManager;
@Inject
protected volatile ForumSharingManager forumSharingManager;
public static ContactSelectorFragment newInstance(GroupId groupId) {
Bundle args = new Bundle();
args.putByteArray(GROUP_ID, groupId.getBytes());
ContactSelectorFragment fragment = new ContactSelectorFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
shareForumActivity = (ShareForumActivity) context;
} catch (ClassCastException e) {
throw new InstantiationError(
"This fragment is only meant to be attached to the ShareForumActivity");
}
}
@Override
public void injectActivity(AndroidComponent component) {
component.inject(this);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
groupId = new GroupId(getArguments().getByteArray(GROUP_ID));
if (groupId == null) throw new IllegalStateException("No GroupId");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View contentView =
inflater.inflate(R.layout.introduction_contact_chooser,
container, false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setExitTransition(new Fade());
}
adapter = new ContactSelectorAdapter(getActivity(), this);
list = (BriarRecyclerView) contentView.findViewById(R.id.contactList);
list.setLayoutManager(new LinearLayoutManager(getActivity()));
list.setAdapter(adapter);
list.setEmptyText(getString(R.string.no_contacts));
// restore selected contacts if available
if (savedInstanceState != null) {
ArrayList<Integer> intContacts =
savedInstanceState.getIntegerArrayList(CONTACTS);
selectedContacts = ShareForumActivity.getContactsFromIntegers(intContacts);
}
return contentView;
}
@Override
public void onResume() {
super.onResume();
if (selectedContacts != null) {
loadContacts(Collections.unmodifiableCollection(selectedContacts));
} else {
loadContacts(null);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (adapter != null) {
selectedContacts = adapter.getSelectedContactIds();
outState.putIntegerArrayList(CONTACTS,
getContactsFromIds(selectedContacts));
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.forum_share_actions, menu);
super.onCreateOptionsMenu(menu, inflater);
this.menu = menu;
// hide sharing action initially, if no contact is selected
updateMenuItem();
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case android.R.id.home:
shareForumActivity.onBackPressed();
return true;
case R.id.action_share_forum:
selectedContacts = adapter.getSelectedContactIds();
shareForumActivity.showMessageScreen(groupId, selectedContacts);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void onItemClick(View view, ContactListItem item) {
((SelectableContactListItem) item).toggleSelected();
adapter.notifyItemChanged(adapter.findItemPosition(item), item);
updateMenuItem();
}
private void loadContacts(final Collection<ContactId> selection) {
shareForumActivity.runOnDbThread(new Runnable() {
public void run() {
try {
long now = System.currentTimeMillis();
List<ContactListItem> contacts =
new ArrayList<>();
for (Contact c : contactManager.getActiveContacts()) {
LocalAuthor localAuthor = identityManager
.getLocalAuthor(c.getLocalAuthorId());
// was this contact already selected?
boolean selected = selection != null &&
selection.contains(c.getId());
// do we have already some sharing with that contact?
boolean disabled =
!forumSharingManager.canBeShared(groupId, c);
contacts.add(
new SelectableContactListItem(c, localAuthor,
groupId, selected, disabled));
}
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Load took " + duration + " ms");
displayContacts(Collections.unmodifiableList(contacts));
} catch (DbException e) {
displayContacts(Collections.<ContactListItem>emptyList());
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void displayContacts(final List<ContactListItem> contacts) {
shareForumActivity.runOnUiThread(new Runnable() {
public void run() {
if (!contacts.isEmpty()) {
adapter.addAll(contacts);
} else {
list.showData();
}
updateMenuItem();
}
});
}
private void updateMenuItem() {
if (menu == null) return;
MenuItem item = menu.findItem(R.id.action_share_forum);
if (item == null) return;
selectedContacts = adapter.getSelectedContactIds();
if (selectedContacts.size() > 0) {
item.setVisible(true);
} else {
item.setVisible(false);
}
}
}

View File

@@ -3,6 +3,7 @@ package org.briarproject.android.forum;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v7.app.AlertDialog;
@@ -49,6 +50,7 @@ import javax.inject.Inject;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import static android.support.design.widget.Snackbar.LENGTH_LONG;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.View.GONE;
@@ -68,6 +70,7 @@ public class ForumActivity extends BriarActivity implements EventListener,
public static final String MIN_TIMESTAMP = "briar.MIN_TIMESTAMP";
private static final int REQUEST_READ = 2;
private static final int REQUEST_FORUM_SHARED = 3;
private static final Logger LOG =
Logger.getLogger(ForumActivity.class.getName());
@@ -165,7 +168,9 @@ public class ForumActivity extends BriarActivity implements EventListener,
ActivityOptionsCompat options = ActivityOptionsCompat
.makeCustomAnimation(this, android.R.anim.slide_in_left,
android.R.anim.slide_out_right);
ActivityCompat.startActivity(this, i2, options.toBundle());
ActivityCompat
.startActivityForResult(this, i2, REQUEST_FORUM_SHARED,
options.toBundle());
return true;
case R.id.action_forum_delete:
showUnsubscribeDialog();
@@ -297,6 +302,12 @@ public class ForumActivity extends BriarActivity implements EventListener,
if (position >= 0 && position < adapter.getCount())
displayPost(position);
}
else if (request == REQUEST_FORUM_SHARED && result == RESULT_OK) {
Snackbar s = Snackbar.make(list, R.string.forum_shared_snackbar,
LENGTH_LONG);
s.getView().setBackgroundResource(R.color.briar_primary);
s.show();
}
}
@Override

View File

@@ -11,14 +11,15 @@ import java.util.Collections;
// This class is not thread-safe
public class SelectableContactListItem extends ContactListItem {
private boolean selected;
private boolean selected, disabled;
public SelectableContactListItem(Contact contact, LocalAuthor localAuthor,
GroupId groupId, boolean selected) {
GroupId groupId, boolean selected, boolean disabled) {
super(contact, localAuthor, false, groupId, Collections.<ConversationItem>emptyList());
this.selected = selected;
this.disabled = disabled;
}
public void setSelected(boolean selected) {
@@ -33,4 +34,8 @@ public class SelectableContactListItem extends ContactListItem {
selected = !selected;
}
public boolean isDisabled() {
return disabled;
}
}

View File

@@ -2,98 +2,41 @@ package org.briarproject.android.forum;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import org.briarproject.R;
import org.briarproject.android.AndroidComponent;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.contact.BaseContactListAdapter;
import org.briarproject.android.contact.ContactListItem;
import org.briarproject.android.util.BriarRecyclerView;
import org.briarproject.api.contact.Contact;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.db.DbException;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.GroupId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
public class ShareForumActivity extends BriarActivity implements
BaseContactListAdapter.OnItemClickListener {
BaseFragment.BaseFragmentListener {
private static final Logger LOG =
Logger.getLogger(ShareForumActivity.class.getName());
private ContactSelectorAdapter adapter;
// Fields that are accessed from background threads must be volatile
@Inject protected volatile IdentityManager identityManager;
@Inject protected volatile ContactManager contactManager;
@Inject protected volatile ForumSharingManager forumSharingManager;
private volatile GroupId groupId;
public final static String CONTACTS = "contacts";
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.introduction_contact_chooser);
setContentView(R.layout.activity_share_forum);
Intent i = getIntent();
byte[] b = i.getByteArrayExtra(GROUP_ID);
if (b == null) throw new IllegalStateException();
groupId = new GroupId(b);
if (b == null) throw new IllegalStateException("No GroupId");
GroupId groupId = new GroupId(b);
adapter = new ContactSelectorAdapter(this, this);
BriarRecyclerView list =
(BriarRecyclerView) findViewById(R.id.contactList);
list.setLayoutManager(new LinearLayoutManager(this));
list.setAdapter(adapter);
list.setEmptyText(getString(R.string.no_contacts));
}
@Override
public void onResume() {
super.onResume();
loadContactsAndVisibility();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu items for use in the action bar
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.forum_share_actions, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
case R.id.action_share_forum:
return true;
default:
return super.onOptionsItemSelected(item);
if (savedInstanceState == null) {
ContactSelectorFragment contactSelectorFragment =
ContactSelectorFragment.newInstance(groupId);
getSupportFragmentManager().beginTransaction()
.add(R.id.shareForumContainer, contactSelectorFragment)
.commit();
}
}
@@ -102,47 +45,59 @@ public class ShareForumActivity extends BriarActivity implements
component.inject(this);
}
private void loadContactsAndVisibility() {
runOnDbThread(new Runnable() {
public void run() {
try {
long now = System.currentTimeMillis();
List<ContactListItem> contacts = new ArrayList<>();
Collection<ContactId> selectedContacts = new HashSet<>(
forumSharingManager.getSharedWith(groupId));
public void showMessageScreen(GroupId groupId,
Collection<ContactId> contacts) {
for (Contact c : contactManager.getActiveContacts()) {
LocalAuthor localAuthor = identityManager
.getLocalAuthor(c.getLocalAuthorId());
boolean selected = selectedContacts.contains(c.getId());
contacts.add(
new SelectableContactListItem(c, localAuthor,
groupId, selected));
}
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Load took " + duration + " ms");
displayContacts(contacts);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
ShareForumMessageFragment messageFragment =
ShareForumMessageFragment.newInstance(groupId, contacts);
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(android.R.anim.fade_in,
android.R.anim.fade_out,
android.R.anim.slide_in_left,
android.R.anim.slide_out_right)
.replace(R.id.shareForumContainer, messageFragment,
ContactSelectorFragment.TAG)
.addToBackStack(null)
.commit();
}
private void displayContacts(final List<ContactListItem> contact) {
runOnUiThread(new Runnable() {
public void run() {
adapter.addAll(contact);
}
});
public static ArrayList<Integer> getContactsFromIds(
Collection<ContactId> contacts) {
// transform ContactIds to Integers so they can be added to a bundle
ArrayList<Integer> intContacts = new ArrayList<>(contacts.size());
for (ContactId contactId : contacts) {
intContacts.add(contactId.getInt());
}
return intContacts;
}
public void sharingSuccessful(View v) {
setResult(RESULT_OK);
hideSoftKeyboard(v);
supportFinishAfterTransition();
}
protected static Collection<ContactId> getContactsFromIntegers(
ArrayList<Integer> intContacts) {
// turn contact integers from a bundle back to ContactIds
List<ContactId> contacts = new ArrayList<>(intContacts.size());
for(Integer c : intContacts) {
contacts.add(new ContactId(c));
}
return contacts;
}
@Override
public void onItemClick(View view, ContactListItem item) {
((SelectableContactListItem) item).toggleSelected();
adapter.notifyItemChanged(adapter.findItemPosition(item), item);
public void showLoadingScreen(boolean isBlocking, int stringId) {
// this is handled by the recycler view in ContactSelectorFragment
}
@Override
public void hideLoadingScreen() {
// this is handled by the recycler view in ContactSelectorFragment
}
}

View File

@@ -0,0 +1,177 @@
package org.briarproject.android.forum;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.AndroidComponent;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.sync.GroupId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.forum.ShareForumActivity.CONTACTS;
import static org.briarproject.android.forum.ShareForumActivity.getContactsFromIds;
import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
public class ShareForumMessageFragment extends BaseFragment {
private static final Logger LOG =
Logger.getLogger(ShareForumMessageFragment.class.getName());
public final static String TAG = "IntroductionMessageFragment";
private ShareForumActivity shareForumActivity;
private ViewHolder ui;
// Fields that are accessed from background threads must be volatile
@Inject protected volatile ForumSharingManager forumSharingManager;
private volatile GroupId groupId;
private volatile Collection<ContactId> contacts;
public static ShareForumMessageFragment newInstance(GroupId groupId,
Collection<ContactId> contacts) {
ShareForumMessageFragment f = new ShareForumMessageFragment();
Bundle args = new Bundle();
args.putByteArray(GROUP_ID, groupId.getBytes());
args.putIntegerArrayList(CONTACTS, getContactsFromIds(contacts));
f.setArguments(args);
return f;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
shareForumActivity = (ShareForumActivity) context;
} catch (ClassCastException e) {
throw new InstantiationError(
"This fragment is only meant to be attached to the ShareForumActivity");
}
}
@Override
public void injectActivity(AndroidComponent component) {
component.inject(this);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// change toolbar text
ActionBar actionBar = shareForumActivity.getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(R.string.forum_share_button);
}
// allow for home button to act as back button
setHasOptionsMenu(true);
// inflate view
View v =
inflater.inflate(R.layout.share_forum_message, container,
false);
ui = new ViewHolder(v);
ui.button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onButtonClick();
}
});
// get groupID and contactIDs from fragment arguments
groupId = new GroupId(getArguments().getByteArray(GROUP_ID));
ArrayList<Integer> intContacts =
getArguments().getIntegerArrayList(CONTACTS);
if (intContacts == null) throw new IllegalArgumentException();
contacts = ShareForumActivity.getContactsFromIntegers(intContacts);
return v;
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
shareForumActivity.onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public String getUniqueTag() {
return TAG;
}
public void onButtonClick() {
// disable button to prevent accidental double invitations
ui.button.setEnabled(false);
String msg = ui.message.getText().toString();
shareForum(msg);
// don't wait for the introduction to be made before finishing activity
shareForumActivity.sharingSuccessful(ui.message);
}
private void shareForum(final String msg) {
shareForumActivity.runOnDbThread(new Runnable() {
public void run() {
try {
for (ContactId c : contacts) {
forumSharingManager
.sendForumInvitation(groupId, c, msg);
}
} catch (DbException e) {
sharingError();
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void sharingError() {
shareForumActivity.runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(shareForumActivity,
R.string.introduction_error, Toast.LENGTH_SHORT)
.show();
}
});
}
private static class ViewHolder {
final private TextView text;
final private EditText message;
final private Button button;
ViewHolder(View v) {
text = (TextView) v.findViewById(R.id.introductionText);
message = (EditText) v.findViewById(R.id.invitationMessageView);
button = (Button) v.findViewById(R.id.shareForumButton);
}
}
}