diff --git a/briar-android/res/layout/activity_share_forum.xml b/briar-android/res/layout/activity_share_forum.xml
new file mode 100644
index 000000000..b91e96f00
--- /dev/null
+++ b/briar-android/res/layout/activity_share_forum.xml
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/briar-android/res/layout/list_item_forum_invitation_in.xml b/briar-android/res/layout/list_item_forum_invitation_in.xml
new file mode 100644
index 000000000..c6881437b
--- /dev/null
+++ b/briar-android/res/layout/list_item_forum_invitation_in.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/res/layout/list_item_forum_invitation_out.xml b/briar-android/res/layout/list_item_forum_invitation_out.xml
new file mode 100644
index 000000000..88070ea66
--- /dev/null
+++ b/briar-android/res/layout/list_item_forum_invitation_out.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/res/layout/share_forum_message.xml b/briar-android/res/layout/share_forum_message.xml
new file mode 100644
index 000000000..64a027914
--- /dev/null
+++ b/briar-android/res/layout/share_forum_message.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index e5d21dec3..8cbd86d95 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -92,6 +92,11 @@
Forum created
Share this forum with chosen contacts
Share Forum
+ Forum shared with chosen contacts
+ You may compose an optional invitation message that will be sent to the selected contacts.
+ %1$s has shared the forum \"%2$s\" with you.
+ You have shared the forum \"%1$s\" with %2$s.
+ Show Available Forums
New Forum Post
From:
Anonymous
diff --git a/briar-android/src/org/briarproject/android/AndroidComponent.java b/briar-android/src/org/briarproject/android/AndroidComponent.java
index 7c5777e52..8b2ac951a 100644
--- a/briar-android/src/org/briarproject/android/AndroidComponent.java
+++ b/briar-android/src/org/briarproject/android/AndroidComponent.java
@@ -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);
diff --git a/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java b/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
index 0a7b1ea2b..623391cc3 100644
--- a/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
+++ b/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
@@ -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 {
diff --git a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java
index 9838eb06a..82137d92a 100644
--- a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java
+++ b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java
@@ -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 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;
}
}
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
index 213e412b5..5a62bef1f 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java
@@ -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 introductions =
introductionManager
.getIntroductionMessages(contactId);
+ Collection 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 headers,
- final Collection introductions) {
+ final Collection introductions,
+ final Collection 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();
+ }
}
}
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
index 91a006b9f..cf1a4b738 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
@@ -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 items =
- new SortedList(ConversationItem.class,
- new SortedList.Callback() {
- @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 getIncomingMessages() {
- SparseArray messages =
- new SparseArray();
+ SparseArray 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 getOutgoingMessages() {
- SparseArray messages =
- new SparseArray();
+ SparseArray 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 getPrivateMessages() {
- SparseArray messages =
- new SparseArray();
+ SparseArray 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 {
+ @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);
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationForumInvitationInItem.java b/briar-android/src/org/briarproject/android/contact/ConversationForumInvitationInItem.java
new file mode 100644
index 000000000..06bf4a1ad
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/contact/ConversationForumInvitationInItem.java
@@ -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;
+ }
+
+}
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationForumInvitationItem.java b/briar-android/src/org/briarproject/android/contact/ConversationForumInvitationItem.java
new file mode 100644
index 000000000..0bf7d72ab
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/contact/ConversationForumInvitationItem.java
@@ -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;
+ }
+
+}
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationForumInvitationOutItem.java b/briar-android/src/org/briarproject/android/contact/ConversationForumInvitationOutItem.java
new file mode 100644
index 000000000..2d4699abf
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/contact/ConversationForumInvitationOutItem.java
@@ -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;
+ }
+
+}
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationIntroductionInItem.java b/briar-android/src/org/briarproject/android/contact/ConversationIntroductionInItem.java
index fb891d0a1..9ccf21efd 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationIntroductionInItem.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationIntroductionInItem.java
@@ -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 {
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationIntroductionOutItem.java b/briar-android/src/org/briarproject/android/contact/ConversationIntroductionOutItem.java
index a2aba398f..7584c8738 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationIntroductionOutItem.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationIntroductionOutItem.java
@@ -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
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationItem.java b/briar-android/src/org/briarproject/android/contact/ConversationItem.java
index 2fc96b6ab..93e95e24f 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationItem.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationItem.java
@@ -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.
*/
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationNoticeInItem.java b/briar-android/src/org/briarproject/android/contact/ConversationNoticeInItem.java
index 610b703c1..3affb3d48 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationNoticeInItem.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationNoticeInItem.java
@@ -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 {
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationNoticeOutItem.java b/briar-android/src/org/briarproject/android/contact/ConversationNoticeOutItem.java
index b39889701..ca4503cf6 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationNoticeOutItem.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationNoticeOutItem.java
@@ -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 {
diff --git a/briar-android/src/org/briarproject/android/forum/ContactSelectorAdapter.java b/briar-android/src/org/briarproject/android/forum/ContactSelectorAdapter.java
index 0e3e81181..2f1ce156b 100644
--- a/briar-android/src/org/briarproject/android/forum/ContactSelectorAdapter.java
+++ b/briar-android/src/org/briarproject/android/forum/ContactSelectorAdapter.java
@@ -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 getSelectedContactIds() {
- Collection selected = new ArrayList();
+ Collection 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);
+ }
+ }
+
}
diff --git a/briar-android/src/org/briarproject/android/forum/ContactSelectorFragment.java b/briar-android/src/org/briarproject/android/forum/ContactSelectorFragment.java
new file mode 100644
index 000000000..73de55ec1
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/forum/ContactSelectorFragment.java
@@ -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 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 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 selection) {
+ shareForumActivity.runOnDbThread(new Runnable() {
+ public void run() {
+ try {
+ long now = System.currentTimeMillis();
+ List 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.emptyList());
+ if (LOG.isLoggable(WARNING))
+ LOG.log(WARNING, e.toString(), e);
+ }
+ }
+ });
+ }
+
+ private void displayContacts(final List 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);
+ }
+ }
+
+}
diff --git a/briar-android/src/org/briarproject/android/forum/ForumActivity.java b/briar-android/src/org/briarproject/android/forum/ForumActivity.java
index c092791a5..9a2ec711d 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumActivity.java
+++ b/briar-android/src/org/briarproject/android/forum/ForumActivity.java
@@ -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
diff --git a/briar-android/src/org/briarproject/android/forum/SelectableContactListItem.java b/briar-android/src/org/briarproject/android/forum/SelectableContactListItem.java
index 93d266c6c..6b9116ca4 100644
--- a/briar-android/src/org/briarproject/android/forum/SelectableContactListItem.java
+++ b/briar-android/src/org/briarproject/android/forum/SelectableContactListItem.java
@@ -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.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;
+ }
+
}
diff --git a/briar-android/src/org/briarproject/android/forum/ShareForumActivity.java b/briar-android/src/org/briarproject/android/forum/ShareForumActivity.java
index ccb0b365d..820e25666 100644
--- a/briar-android/src/org/briarproject/android/forum/ShareForumActivity.java
+++ b/briar-android/src/org/briarproject/android/forum/ShareForumActivity.java
@@ -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 contacts = new ArrayList<>();
- Collection selectedContacts = new HashSet<>(
- forumSharingManager.getSharedWith(groupId));
+ public void showMessageScreen(GroupId groupId,
+ Collection 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 contact) {
- runOnUiThread(new Runnable() {
- public void run() {
- adapter.addAll(contact);
- }
- });
+ public static ArrayList getContactsFromIds(
+ Collection contacts) {
+
+ // transform ContactIds to Integers so they can be added to a bundle
+ ArrayList 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 getContactsFromIntegers(
+ ArrayList intContacts) {
+
+ // turn contact integers from a bundle back to ContactIds
+ List 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
}
}
diff --git a/briar-android/src/org/briarproject/android/forum/ShareForumMessageFragment.java b/briar-android/src/org/briarproject/android/forum/ShareForumMessageFragment.java
new file mode 100644
index 000000000..7ed79d67c
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/forum/ShareForumMessageFragment.java
@@ -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 contacts;
+
+ public static ShareForumMessageFragment newInstance(GroupId groupId,
+ Collection 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 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);
+ }
+ }
+}