From e4f5d8e6e7c768b7930b6eac8d85df97d4669729 Mon Sep 17 00:00:00 2001
From: Torsten Grote <t@grobox.de>
Date: Tue, 2 Aug 2016 16:48:27 -0300
Subject: [PATCH] Show Blog Invitations

This refactors the forum invitation code,
so it can be used by both: forums and blogs.
---
 briar-android/AndroidManifest.xml             |   2 +-
 ...le_forums.xml => activity_invitations.xml} |   2 +-
 ...le_forum.xml => list_item_invitations.xml} |   0
 briar-android/res/values/strings.xml          |   4 +
 .../android/ActivityComponent.java            |   4 +-
 .../android/contact/ConversationAdapter.java  |  16 +-
 .../forum/ForumInvitationsActivity.java       | 188 -----------
 .../android/forum/ForumListFragment.java      |   7 +-
 .../sharing/BlogInvitationAdapter.java        |  37 +++
 .../sharing/ForumInvitationAdapter.java       |  31 ++
 .../InvitationAdapter.java}                   |  91 +++---
 .../InvitationItem.java}                      |  18 +-
 .../android/sharing/InvitationsActivity.java  | 308 ++++++++++++++++++
 .../android/util/TextAvatarView.java          |   9 +
 14 files changed, 461 insertions(+), 256 deletions(-)
 rename briar-android/res/layout/{activity_available_forums.xml => activity_invitations.xml} (85%)
 rename briar-android/res/layout/{list_item_available_forum.xml => list_item_invitations.xml} (100%)
 delete mode 100644 briar-android/src/org/briarproject/android/forum/ForumInvitationsActivity.java
 create mode 100644 briar-android/src/org/briarproject/android/sharing/BlogInvitationAdapter.java
 create mode 100644 briar-android/src/org/briarproject/android/sharing/ForumInvitationAdapter.java
 rename briar-android/src/org/briarproject/android/{forum/ForumInvitationAdapter.java => sharing/InvitationAdapter.java} (55%)
 rename briar-android/src/org/briarproject/android/{forum/ForumInvitationItem.java => sharing/InvitationItem.java} (53%)
 create mode 100644 briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java

diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml
index 55b48260ee..7b2c1ceacf 100644
--- a/briar-android/AndroidManifest.xml
+++ b/briar-android/AndroidManifest.xml
@@ -102,7 +102,7 @@
 		</activity>
 
 		<activity
-			android:name=".android.forum.ForumInvitationsActivity"
+			android:name=".android.sharing.InvitationsActivity"
 			android:label="@string/forum_invitations_title"
 			android:parentActivityName=".android.NavDrawerActivity">
 			<meta-data
diff --git a/briar-android/res/layout/activity_available_forums.xml b/briar-android/res/layout/activity_invitations.xml
similarity index 85%
rename from briar-android/res/layout/activity_available_forums.xml
rename to briar-android/res/layout/activity_invitations.xml
index 7177fe175f..25cd76af43 100644
--- a/briar-android/res/layout/activity_available_forums.xml
+++ b/briar-android/res/layout/activity_invitations.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <org.briarproject.android.util.BriarRecyclerView
-	android:id="@+id/availableForumsView"
+	android:id="@+id/invitationsView"
 	xmlns:android="http://schemas.android.com/apk/res/android"
 	android:layout_width="match_parent"
 	android:layout_height="match_parent"/>
diff --git a/briar-android/res/layout/list_item_available_forum.xml b/briar-android/res/layout/list_item_invitations.xml
similarity index 100%
rename from briar-android/res/layout/list_item_available_forum.xml
rename to briar-android/res/layout/list_item_invitations.xml
diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index 5a3f510b53..54c7316a6b 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -316,6 +316,10 @@
 	<string name="blogs_sharing_invitation_received">%1$s has shared the personal blog of %2$s with you.</string>
 	<string name="blogs_sharing_invitation_sent">You have shared the personal blog of %1$s with %2$s.</string>
 	<string name="blogs_sharing_show_invitations">Show Blog Invitations</string>
+	<string name="blogs_sharing_invitations_title">Blog Invitations</string>
+	<string name="blogs_sharing_exists">You are subscribed to this blog already.\nWarning: Accepting again might reveal that %s is your contact.</string>
+	<string name="blogs_sharing_joined_toast">Subscribed to Blog</string>
+	<string name="blogs_sharing_declined_toast">Blog Invitation Declined</string>
 
 	<string name="blogs_blog_list">Blog List</string>
 	<string name="blogs_available_blogs">Available Blogs</string>
diff --git a/briar-android/src/org/briarproject/android/ActivityComponent.java b/briar-android/src/org/briarproject/android/ActivityComponent.java
index 8d4d85225e..81ef8bd57e 100644
--- a/briar-android/src/org/briarproject/android/ActivityComponent.java
+++ b/briar-android/src/org/briarproject/android/ActivityComponent.java
@@ -15,7 +15,7 @@ import org.briarproject.android.blogs.RssFeedManageActivity;
 import org.briarproject.android.blogs.WriteBlogPostActivity;
 import org.briarproject.android.contact.ContactListFragment;
 import org.briarproject.android.contact.ConversationActivity;
-import org.briarproject.android.forum.ForumInvitationsActivity;
+import org.briarproject.android.sharing.InvitationsActivity;
 import org.briarproject.android.sharing.ContactSelectorFragment;
 import org.briarproject.android.forum.CreateForumActivity;
 import org.briarproject.android.forum.ForumActivity;
@@ -63,7 +63,7 @@ public interface ActivityComponent {
 
 	void inject(CreateIdentityActivity activity);
 
-	void inject(ForumInvitationsActivity activity);
+	void inject(InvitationsActivity activity);
 
 	void inject(CreateForumActivity activity);
 
diff --git a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
index 6741890c5e..353a7e1ce2 100644
--- a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
+++ b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java
@@ -13,7 +13,7 @@ import android.widget.ImageView;
 import android.widget.TextView;
 
 import org.briarproject.R;
-import org.briarproject.android.forum.ForumInvitationsActivity;
+import org.briarproject.android.sharing.InvitationsActivity;
 import org.briarproject.android.util.AndroidUtils;
 import org.briarproject.api.blogs.BlogInvitationRequest;
 import org.briarproject.api.clients.SessionId;
@@ -42,6 +42,9 @@ import static org.briarproject.android.contact.ConversationItem.MSG_OUT;
 import static org.briarproject.android.contact.ConversationItem.NOTICE_IN;
 import static org.briarproject.android.contact.ConversationItem.NOTICE_OUT;
 import static org.briarproject.android.contact.ConversationItem.OutgoingItem;
+import static org.briarproject.android.sharing.ShareActivity.BLOG;
+import static org.briarproject.android.sharing.ShareActivity.FORUM;
+import static org.briarproject.android.sharing.ShareActivity.SHAREABLE;
 
 class ConversationAdapter extends RecyclerView.Adapter {
 
@@ -286,7 +289,7 @@ class ConversationAdapter extends RecyclerView.Adapter {
 	private void bindInvitation(InvitationHolder ui,
 			final ConversationShareableInvitationItem item) {
 
-		InvitationRequest ir = item.getInvitationRequest();
+		final InvitationRequest ir = item.getInvitationRequest();
 		String name = "";
 		int receivedRes =  0, sentRes = 0, buttonRes = 0;
 		if (ir instanceof ForumInvitationRequest) {
@@ -335,15 +338,18 @@ class ConversationAdapter extends RecyclerView.Adapter {
 			ui.text.setText(ctx.getString(receivedRes, contactName, name));
 
 			if (ir.isAvailable()) {
+				final int type =
+						ir instanceof ForumInvitationRequest ? FORUM : BLOG;
 				ui.showInvitationsButton.setText(ctx.getString(buttonRes));
 				ui.showInvitationsButton.setVisibility(VISIBLE);
 				ui.showInvitationsButton
 						.setOnClickListener(new View.OnClickListener() {
 							@Override
 							public void onClick(View v) {
-								Intent intent = new Intent(ctx,
-										ForumInvitationsActivity.class);
-								ctx.startActivity(intent);
+								Intent i = new Intent(ctx,
+										InvitationsActivity.class);
+								i.putExtra(SHAREABLE, type);
+								ctx.startActivity(i);
 							}
 						});
 			} else {
diff --git a/briar-android/src/org/briarproject/android/forum/ForumInvitationsActivity.java b/briar-android/src/org/briarproject/android/forum/ForumInvitationsActivity.java
deleted file mode 100644
index fd3e24196b..0000000000
--- a/briar-android/src/org/briarproject/android/forum/ForumInvitationsActivity.java
+++ /dev/null
@@ -1,188 +0,0 @@
-package org.briarproject.android.forum;
-
-import android.os.Bundle;
-import android.support.v7.widget.LinearLayoutManager;
-import android.widget.Toast;
-
-import org.briarproject.R;
-import org.briarproject.android.ActivityComponent;
-import org.briarproject.android.BriarActivity;
-import org.briarproject.android.util.BriarRecyclerView;
-import org.briarproject.api.contact.Contact;
-import org.briarproject.api.db.DbException;
-import org.briarproject.api.db.NoSuchGroupException;
-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.GroupAddedEvent;
-import org.briarproject.api.event.GroupRemovedEvent;
-import org.briarproject.api.forum.Forum;
-import org.briarproject.api.forum.ForumManager;
-import org.briarproject.api.forum.ForumSharingManager;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.logging.Logger;
-
-import javax.inject.Inject;
-
-import static android.widget.Toast.LENGTH_SHORT;
-import static java.util.logging.Level.INFO;
-import static java.util.logging.Level.WARNING;
-import static org.briarproject.android.forum.ForumInvitationAdapter.AvailableForumClickListener;
-
-public class ForumInvitationsActivity extends BriarActivity
-		implements EventListener, AvailableForumClickListener {
-
-	private static final Logger LOG =
-			Logger.getLogger(ForumInvitationsActivity.class.getName());
-
-	private ForumInvitationAdapter adapter;
-
-	// Fields that are accessed from background threads must be volatile
-	@Inject
-	protected volatile ForumManager forumManager;
-	@Inject
-	protected volatile ForumSharingManager forumSharingManager;
-	@Inject
-	protected volatile EventBus eventBus;
-
-	@Override
-	public void onCreate(Bundle state) {
-		super.onCreate(state);
-
-		setContentView(R.layout.activity_available_forums);
-
-		adapter = new ForumInvitationAdapter(this, this);
-		BriarRecyclerView list =
-				(BriarRecyclerView) findViewById(R.id.availableForumsView);
-		list.setLayoutManager(new LinearLayoutManager(this));
-		list.setAdapter(adapter);
-	}
-
-	@Override
-	public void injectActivity(ActivityComponent component) {
-		component.inject(this);
-	}
-
-	@Override
-	public void onResume() {
-		super.onResume();
-		eventBus.addListener(this);
-		loadForums(false);
-	}
-
-	@Override
-	public void onPause() {
-		super.onPause();
-		eventBus.removeListener(this);
-		adapter.clear();
-	}
-
-	private void loadForums(final boolean clear) {
-		runOnDbThread(new Runnable() {
-			@Override
-			public void run() {
-				try {
-					Collection<ForumInvitationItem> forums = new ArrayList<>();
-					long now = System.currentTimeMillis();
-					for (Forum f : forumSharingManager.getInvited()) {
-						boolean subscribed;
-						try {
-							forumManager.getForum(f.getId());
-							subscribed = true;
-						} catch (NoSuchGroupException e) {
-							subscribed = false;
-						}
-						Collection<Contact> c =
-								forumSharingManager.getSharedBy(f.getId());
-						forums.add(
-								new ForumInvitationItem(f, subscribed, c));
-					}
-					long duration = System.currentTimeMillis() - now;
-					if (LOG.isLoggable(INFO))
-						LOG.info("Load took " + duration + " ms");
-					displayForums(forums, clear);
-				} catch (DbException e) {
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, e.toString(), e);
-				}
-			}
-		});
-	}
-
-	private void displayForums(final Collection<ForumInvitationItem> forums,
-			final boolean clear) {
-		runOnUiThread(new Runnable() {
-			@Override
-			public void run() {
-				if (forums.isEmpty()) {
-					LOG.info("No forums available, finishing");
-					finish();
-				} else {
-					if (clear) adapter.clear();
-					adapter.addAll(forums);
-				}
-			}
-		});
-	}
-
-	@Override
-	public void eventOccurred(Event e) {
-		if (e instanceof ContactRemovedEvent) {
-			LOG.info("Contact removed, reloading");
-			loadForums(true);
-		} else if (e instanceof GroupAddedEvent) {
-			GroupAddedEvent g = (GroupAddedEvent) e;
-			if (g.getGroup().getClientId().equals(forumManager.getClientId())) {
-				LOG.info("Forum added, reloading");
-				loadForums(false);
-			}
-		} else if (e instanceof GroupRemovedEvent) {
-			GroupRemovedEvent g = (GroupRemovedEvent) e;
-			if (g.getGroup().getClientId().equals(forumManager.getClientId())) {
-				LOG.info("Forum removed, reloading");
-				loadForums(true);
-			}
-		} else if (e instanceof ForumInvitationReceivedEvent) {
-			LOG.info("Available forums updated, reloading");
-			loadForums(false);
-		}
-	}
-
-	@Override
-	public void onItemClick(ForumInvitationItem item, boolean accept) {
-		respondToInvitation(item, accept);
-
-		// show toast
-		int res = R.string.forum_declined_toast;
-		if (accept) res = R.string.forum_joined_toast;
-		Toast.makeText(this, res, LENGTH_SHORT).show();
-
-		// remove item and finish if it was the last
-		adapter.remove(item);
-		if (adapter.getItemCount() == 0) {
-			supportFinishAfterTransition();
-		}
-	}
-
-	private void respondToInvitation(final ForumInvitationItem item,
-			final boolean accept) {
-		runOnDbThread(new Runnable() {
-			@Override
-			public void run() {
-				try {
-					Forum f = item.getForum();
-					for (Contact c : item.getContacts()) {
-						forumSharingManager.respondToInvitation(f, c, accept);
-					}
-				} catch (DbException e) {
-					if (LOG.isLoggable(WARNING))
-						LOG.log(WARNING, e.toString(), e);
-				}
-			}
-		});
-	}
-}
diff --git a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java
index cca381a80f..26721db36d 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java
+++ b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java
@@ -16,6 +16,7 @@ import android.view.ViewGroup;
 import org.briarproject.R;
 import org.briarproject.android.ActivityComponent;
 import org.briarproject.android.fragment.BaseEventFragment;
+import org.briarproject.android.sharing.InvitationsActivity;
 import org.briarproject.android.util.BriarRecyclerView;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.db.NoSuchGroupException;
@@ -40,6 +41,8 @@ import javax.inject.Inject;
 import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.android.sharing.ShareActivity.FORUM;
+import static org.briarproject.android.sharing.ShareActivity.SHAREABLE;
 
 public class ForumListFragment extends BaseEventFragment implements
 		View.OnClickListener {
@@ -285,6 +288,8 @@ public class ForumListFragment extends BaseEventFragment implements
 	@Override
 	public void onClick(View view) {
 		// snackbar click
-		startActivity(new Intent(getContext(), ForumInvitationsActivity.class));
+		Intent i = new Intent(getContext(), InvitationsActivity.class);
+		i.putExtra(SHAREABLE, FORUM);
+		startActivity(i);
 	}
 }
diff --git a/briar-android/src/org/briarproject/android/sharing/BlogInvitationAdapter.java b/briar-android/src/org/briarproject/android/sharing/BlogInvitationAdapter.java
new file mode 100644
index 0000000000..c6127143e6
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/sharing/BlogInvitationAdapter.java
@@ -0,0 +1,37 @@
+package org.briarproject.android.sharing;
+
+import android.content.Context;
+
+import org.briarproject.R;
+import org.briarproject.api.blogs.Blog;
+
+class BlogInvitationAdapter extends InvitationAdapter {
+
+	BlogInvitationAdapter(Context ctx, AvailableForumClickListener listener) {
+		super(ctx, listener);
+	}
+
+	@Override
+	public void onBindViewHolder(InvitationsViewHolder ui, int position) {
+		super.onBindViewHolder(ui, position);
+		InvitationItem item = getItem(position);
+		Blog blog = (Blog) item.getShareable();
+
+		ui.avatar.setAuthorAvatar(blog.getAuthor());
+
+		ui.name.setText(ctx.getString(R.string.blogs_personal_blog,
+				blog.getAuthor().getName()));
+
+		if (item.isSubscribed()) {
+			ui.subscribed.setText(ctx.getString(R.string.blogs_sharing_exists,
+					blog.getAuthor().getName()));
+		}
+	}
+
+	int compareInvitations(InvitationItem o1, InvitationItem o2) {
+		return String.CASE_INSENSITIVE_ORDER
+				.compare(((Blog) o1.getShareable()).getAuthor().getName(),
+						((Blog) o2.getShareable()).getAuthor().getName());
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/sharing/ForumInvitationAdapter.java b/briar-android/src/org/briarproject/android/sharing/ForumInvitationAdapter.java
new file mode 100644
index 0000000000..a0e898715e
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/sharing/ForumInvitationAdapter.java
@@ -0,0 +1,31 @@
+package org.briarproject.android.sharing;
+
+import android.content.Context;
+
+import org.briarproject.api.forum.Forum;
+
+class ForumInvitationAdapter extends InvitationAdapter {
+
+	ForumInvitationAdapter(Context ctx, AvailableForumClickListener listener) {
+		super(ctx, listener);
+	}
+
+	@Override
+	public void onBindViewHolder(InvitationsViewHolder ui, int position) {
+		super.onBindViewHolder(ui, position);
+		InvitationItem item = getItem(position);
+		Forum forum = (Forum) item.getShareable();
+
+		ui.avatar.setText(forum.getName().substring(0, 1));
+		ui.avatar.setBackgroundBytes(item.getShareable().getId().getBytes());
+
+		ui.name.setText(forum.getName());
+	}
+
+	int compareInvitations(InvitationItem o1, InvitationItem o2) {
+		return String.CASE_INSENSITIVE_ORDER
+				.compare(((Forum) o1.getShareable()).getName(),
+						((Forum) o2.getShareable()).getName());
+	}
+
+}
diff --git a/briar-android/src/org/briarproject/android/forum/ForumInvitationAdapter.java b/briar-android/src/org/briarproject/android/sharing/InvitationAdapter.java
similarity index 55%
rename from briar-android/src/org/briarproject/android/forum/ForumInvitationAdapter.java
rename to briar-android/src/org/briarproject/android/sharing/InvitationAdapter.java
index 28ef9e2f1c..22a5f3e023 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumInvitationAdapter.java
+++ b/briar-android/src/org/briarproject/android/sharing/InvitationAdapter.java
@@ -1,4 +1,4 @@
-package org.briarproject.android.forum;
+package org.briarproject.android.sharing;
 
 import android.content.Context;
 import android.support.v7.util.SortedList;
@@ -12,7 +12,6 @@ import android.widget.TextView;
 import org.briarproject.R;
 import org.briarproject.android.util.TextAvatarView;
 import org.briarproject.api.contact.Contact;
-import org.briarproject.api.forum.ForumSharingMessage;
 import org.briarproject.util.StringUtils;
 
 import java.util.ArrayList;
@@ -21,37 +20,32 @@ import java.util.Collection;
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 
-class ForumInvitationAdapter extends
-		RecyclerView.Adapter<ForumInvitationAdapter.AvailableForumViewHolder> {
+abstract class InvitationAdapter extends
+		RecyclerView.Adapter<InvitationAdapter.InvitationsViewHolder> {
 
-	private final Context ctx;
+	protected final Context ctx;
 	private final AvailableForumClickListener listener;
-	private final SortedList<ForumInvitationItem> forums =
-			new SortedList<>(ForumInvitationItem.class,
+	private final SortedList<InvitationItem> invitations =
+			new SortedList<>(InvitationItem.class,
 					new SortedListCallBacks());
 
-	ForumInvitationAdapter(Context ctx, AvailableForumClickListener listener) {
+	InvitationAdapter(Context ctx, AvailableForumClickListener listener) {
 		this.ctx = ctx;
 		this.listener = listener;
 	}
 
 	@Override
-	public AvailableForumViewHolder onCreateViewHolder(ViewGroup parent,
+	public InvitationsViewHolder onCreateViewHolder(ViewGroup parent,
 			int viewType) {
 
 		View v = LayoutInflater.from(ctx)
-				.inflate(R.layout.list_item_available_forum, parent,  false);
-		return new AvailableForumViewHolder(v);
+				.inflate(R.layout.list_item_invitations, parent,  false);
+		return new InvitationsViewHolder(v);
 	}
 
 	@Override
-	public void onBindViewHolder(AvailableForumViewHolder ui, int position) {
-		final ForumInvitationItem item = getItem(position);
-
-		ui.avatar.setText(item.getForum().getName().substring(0, 1));
-		ui.avatar.setBackgroundBytes(item.getForum().getId().getBytes());
-
-		ui.name.setText(item.getForum().getName());
+	public void onBindViewHolder(InvitationsViewHolder ui, int position) {
+		final InvitationItem item = getItem(position);
 
 		Collection<String> names = new ArrayList<>();
 		for (Contact c : item.getContacts()) names.add(c.getAuthor().getName());
@@ -80,40 +74,39 @@ class ForumInvitationAdapter extends
 
 	@Override
 	public int getItemCount() {
-		return forums.size();
+		return invitations.size();
 	}
 
-	public ForumInvitationItem getItem(int position) {
-		return forums.get(position);
+	public InvitationItem getItem(int position) {
+		return invitations.get(position);
 	}
 
-	public void add(ForumInvitationItem item) {
-		forums.add(item);
+	public void add(InvitationItem item) {
+		invitations.add(item);
 	}
 
-	public void addAll(Collection<ForumInvitationItem> list) {
-		forums.addAll(list);
+	public void addAll(Collection<InvitationItem> list) {
+		invitations.addAll(list);
 	}
 
-	public void remove(ForumInvitationItem item) {
-		forums.remove(item);
+	public void remove(InvitationItem item) {
+		invitations.remove(item);
 	}
 
 	public void clear() {
-		forums.clear();
+		invitations.clear();
 	}
 
-	static class AvailableForumViewHolder
-			extends RecyclerView.ViewHolder {
+	static class InvitationsViewHolder extends RecyclerView.ViewHolder {
 
-		private final TextAvatarView avatar;
-		private final TextView name;
-		private final TextView sharedBy;
-		private final TextView subscribed;
-		private final Button accept;
-		private final Button decline;
+		final TextAvatarView avatar;
+		final TextView name;
+		final TextView sharedBy;
+		final TextView subscribed;
+		final Button accept;
+		final Button decline;
 
-		AvailableForumViewHolder(View v) {
+		InvitationsViewHolder(View v) {
 			super(v);
 
 			avatar = (TextAvatarView) v.findViewById(R.id.avatarView);
@@ -125,15 +118,15 @@ class ForumInvitationAdapter extends
 		}
 	}
 
+	abstract int compareInvitations(InvitationItem o1, InvitationItem o2);
+
 	private class SortedListCallBacks
-			extends SortedList.Callback<ForumInvitationItem> {
+			extends SortedList.Callback<InvitationItem> {
 
 		@Override
-		public int compare(ForumInvitationItem o1,
-				ForumInvitationItem o2) {
-			return String.CASE_INSENSITIVE_ORDER
-					.compare(o1.getForum().getName(),
-							o2.getForum().getName());
+		public int compare(InvitationItem o1,
+				InvitationItem o2) {
+			return compareInvitations(o1, o2);
 		}
 
 		@Override
@@ -157,21 +150,21 @@ class ForumInvitationAdapter extends
 		}
 
 		@Override
-		public boolean areContentsTheSame(ForumInvitationItem oldItem,
-				ForumInvitationItem newItem) {
+		public boolean areContentsTheSame(InvitationItem oldItem,
+				InvitationItem newItem) {
 			return oldItem.isSubscribed() == newItem.isSubscribed() &&
 					oldItem.getContacts().equals(newItem.getContacts());
 		}
 
 		@Override
-		public boolean areItemsTheSame(ForumInvitationItem oldItem,
-				ForumInvitationItem newItem) {
-			return oldItem.getForum().equals(newItem.getForum());
+		public boolean areItemsTheSame(InvitationItem oldItem,
+				InvitationItem newItem) {
+			return oldItem.getShareable().equals(newItem.getShareable());
 		}
 	}
 
 
 	interface AvailableForumClickListener {
-		void onItemClick(ForumInvitationItem item, boolean accept);
+		void onItemClick(InvitationItem item, boolean accept);
 	}
 }
diff --git a/briar-android/src/org/briarproject/android/forum/ForumInvitationItem.java b/briar-android/src/org/briarproject/android/sharing/InvitationItem.java
similarity index 53%
rename from briar-android/src/org/briarproject/android/forum/ForumInvitationItem.java
rename to briar-android/src/org/briarproject/android/sharing/InvitationItem.java
index 9db87abe73..65dccd4ffd 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumInvitationItem.java
+++ b/briar-android/src/org/briarproject/android/sharing/InvitationItem.java
@@ -1,29 +1,29 @@
-package org.briarproject.android.forum;
+package org.briarproject.android.sharing;
 
 import org.briarproject.api.contact.Contact;
-import org.briarproject.api.forum.Forum;
+import org.briarproject.api.sharing.Shareable;
 
 import java.util.Collection;
 
-class ForumInvitationItem {
+class InvitationItem {
 
-	private final Forum forum;
+	private final Shareable shareable;
 	private final boolean subscribed;
 	private final Collection<Contact> contacts;
 
-	ForumInvitationItem(Forum forum, boolean subscribed,
+	InvitationItem(Shareable shareable, boolean subscribed,
 			Collection<Contact> contacts) {
 
-		this.forum = forum;
+		this.shareable = shareable;
 		this.subscribed = subscribed;
 		this.contacts = contacts;
 	}
 
-	Forum getForum() {
-		return forum;
+	Shareable getShareable() {
+		return shareable;
 	}
 
-	public boolean isSubscribed() {
+	boolean isSubscribed() {
 		return subscribed;
 	}
 
diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java b/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java
new file mode 100644
index 0000000000..614f54cbf3
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java
@@ -0,0 +1,308 @@
+package org.briarproject.android.sharing;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.widget.LinearLayoutManager;
+import android.widget.Toast;
+
+import org.briarproject.R;
+import org.briarproject.android.ActivityComponent;
+import org.briarproject.android.BriarActivity;
+import org.briarproject.android.util.BriarRecyclerView;
+import org.briarproject.api.blogs.Blog;
+import org.briarproject.api.blogs.BlogManager;
+import org.briarproject.api.blogs.BlogSharingManager;
+import org.briarproject.api.contact.Contact;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.db.NoSuchGroupException;
+import org.briarproject.api.event.BlogInvitationReceivedEvent;
+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.GroupAddedEvent;
+import org.briarproject.api.event.GroupRemovedEvent;
+import org.briarproject.api.event.InvitationReceivedEvent;
+import org.briarproject.api.forum.Forum;
+import org.briarproject.api.forum.ForumManager;
+import org.briarproject.api.forum.ForumSharingManager;
+import org.briarproject.api.sync.ClientId;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+import static android.widget.Toast.LENGTH_SHORT;
+import static java.util.logging.Level.INFO;
+import static java.util.logging.Level.WARNING;
+import static org.briarproject.android.sharing.InvitationAdapter.AvailableForumClickListener;
+import static org.briarproject.android.sharing.ShareActivity.BLOG;
+import static org.briarproject.android.sharing.ShareActivity.FORUM;
+import static org.briarproject.android.sharing.ShareActivity.SHAREABLE;
+
+public class InvitationsActivity extends BriarActivity
+		implements EventListener, AvailableForumClickListener {
+
+	private static final Logger LOG =
+			Logger.getLogger(InvitationsActivity.class.getName());
+
+	private int shareable;
+	private InvitationAdapter adapter;
+
+	// Fields that are accessed from background threads must be volatile
+	@Inject
+	protected volatile ForumManager forumManager;
+	@Inject
+	protected volatile ForumSharingManager forumSharingManager;
+	@Inject
+	protected volatile BlogManager blogManager;
+	@Inject
+	protected volatile BlogSharingManager blogSharingManager;
+	@Inject
+	protected volatile EventBus eventBus;
+
+	@Override
+	public void onCreate(Bundle state) {
+		super.onCreate(state);
+
+		setContentView(R.layout.activity_invitations);
+
+		Intent i = getIntent();
+		shareable = i.getIntExtra(SHAREABLE, 0);
+		if (shareable == 0) throw new IllegalStateException("No Shareable");
+
+		if (shareable == FORUM) {
+			adapter = new ForumInvitationAdapter(this, this);
+		} else if (shareable == BLOG) {
+			adapter = new BlogInvitationAdapter(this, this);
+			setTitle(getString(R.string.blogs_sharing_invitations_title));
+		} else {
+			throw new IllegalArgumentException("Unknown Shareable Type");
+		}
+
+		BriarRecyclerView list =
+				(BriarRecyclerView) findViewById(R.id.invitationsView);
+		if (list != null) {
+			list.setLayoutManager(new LinearLayoutManager(this));
+			list.setAdapter(adapter);
+		}
+	}
+
+	@Override
+	public void injectActivity(ActivityComponent component) {
+		component.inject(this);
+	}
+
+	@Override
+	public void onResume() {
+		super.onResume();
+		eventBus.addListener(this);
+		loadShareables(false);
+	}
+
+	@Override
+	public void onPause() {
+		super.onPause();
+		eventBus.removeListener(this);
+		adapter.clear();
+	}
+
+	private void loadShareables(boolean clear) {
+		if (shareable == FORUM) {
+			loadForums(clear);
+		} else if (shareable == BLOG) {
+			loadBlogs(clear);
+		}
+	}
+
+	private void loadForums(final boolean clear) {
+		runOnDbThread(new Runnable() {
+			@Override
+			public void run() {
+				try {
+					Collection<InvitationItem> forums = new ArrayList<>();
+					long now = System.currentTimeMillis();
+					for (Forum f : forumSharingManager.getInvited()) {
+						boolean subscribed;
+						try {
+							forumManager.getForum(f.getId());
+							subscribed = true;
+						} catch (NoSuchGroupException e) {
+							subscribed = false;
+						}
+						Collection<Contact> c =
+								forumSharingManager.getSharedBy(f.getId());
+						forums.add(
+								new InvitationItem(f, subscribed, c));
+					}
+					long duration = System.currentTimeMillis() - now;
+					if (LOG.isLoggable(INFO))
+						LOG.info("Load took " + duration + " ms");
+					displayInvitations(forums, clear);
+				} catch (DbException e) {
+					if (LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				}
+			}
+		});
+	}
+
+	private void loadBlogs(final boolean clear) {
+		runOnDbThread(new Runnable() {
+			@Override
+			public void run() {
+				try {
+					Collection<InvitationItem> invitations = new ArrayList<>();
+					long now = System.currentTimeMillis();
+					for (Blog b : blogSharingManager.getInvited()) {
+						boolean subscribed;
+						try {
+							blogManager.getBlog(b.getId());
+							subscribed = true;
+						} catch (NoSuchGroupException e) {
+							subscribed = false;
+						}
+						Collection<Contact> c =
+								blogSharingManager.getSharedBy(b.getId());
+						invitations.add(
+								new InvitationItem(b, subscribed, c));
+					}
+					long duration = System.currentTimeMillis() - now;
+					if (LOG.isLoggable(INFO))
+						LOG.info("Load took " + duration + " ms");
+					displayInvitations(invitations, clear);
+				} catch (DbException e) {
+					if (LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				}
+			}
+		});
+	}
+
+	private void displayInvitations(final Collection<InvitationItem> invitations,
+			final boolean clear) {
+		runOnUiThread(new Runnable() {
+			@Override
+			public void run() {
+				if (invitations.isEmpty()) {
+					LOG.info("No more invitations available, finishing");
+					finish();
+				} else {
+					if (clear) adapter.clear();
+					adapter.addAll(invitations);
+				}
+			}
+		});
+	}
+
+	@Override
+	public void eventOccurred(Event e) {
+		if (e instanceof ContactRemovedEvent) {
+			LOG.info("Contact removed, reloading");
+			loadShareables(true);
+		} else if (e instanceof GroupAddedEvent) {
+			GroupAddedEvent g = (GroupAddedEvent) e;
+			ClientId cId = g.getGroup().getClientId();
+			if (cId.equals(forumManager.getClientId()) && shareable == FORUM) {
+				LOG.info("Forum added, reloading");
+				loadShareables(false);
+			} else if (cId.equals(blogManager.getClientId()) &&
+					shareable == BLOG) {
+				LOG.info("Blog added, reloading");
+				loadShareables(true);
+			}
+		} else if (e instanceof GroupRemovedEvent) {
+			GroupRemovedEvent g = (GroupRemovedEvent) e;
+			ClientId cId = g.getGroup().getClientId();
+			if (cId.equals(forumManager.getClientId()) && shareable == FORUM) {
+				LOG.info("Forum removed, reloading");
+				loadShareables(true);
+			} else if (cId.equals(blogManager.getClientId()) &&
+					shareable == BLOG) {
+				LOG.info("Blog removed, reloading");
+				loadShareables(true);
+			}
+		} else if (e instanceof InvitationReceivedEvent) {
+			if (e instanceof ForumInvitationReceivedEvent &&
+					shareable == FORUM) {
+				LOG.info("Forum invitation received, reloading");
+				loadShareables(false);
+			} else if (e instanceof BlogInvitationReceivedEvent &&
+					shareable == BLOG) {
+				LOG.info("Blog invitation received, reloading");
+				loadShareables(false);
+			}
+		}
+	}
+
+	@Override
+	public void onItemClick(InvitationItem item, boolean accept) {
+		respondToInvitation(item, accept);
+
+		// show toast
+		int res;
+		if (shareable == FORUM) {
+			res = R.string.forum_declined_toast;
+			if (accept) res = R.string.forum_joined_toast;
+		} else {
+			res = R.string.blogs_sharing_declined_toast;
+			if (accept) res = R.string.blogs_sharing_joined_toast;
+		}
+		Toast.makeText(this, res, LENGTH_SHORT).show();
+
+		// remove item and finish if it was the last
+		adapter.remove(item);
+		if (adapter.getItemCount() == 0) {
+			supportFinishAfterTransition();
+		}
+	}
+
+	private void respondToInvitation(final InvitationItem item,
+			final boolean accept) {
+
+		if (shareable == FORUM) {
+			respondToForumInvitation(item, accept);
+		} else if (shareable == BLOG) {
+			respondToBlogInvitation(item, accept);
+		}
+	}
+
+	private void respondToForumInvitation(final InvitationItem item,
+			final boolean accept) {
+		runOnDbThread(new Runnable() {
+			@Override
+			public void run() {
+				try {
+					Forum f = (Forum) item.getShareable();
+					for (Contact c : item.getContacts()) {
+						forumSharingManager.respondToInvitation(f, c, accept);
+					}
+				} catch (DbException e) {
+					if (LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				}
+			}
+		});
+	}
+
+	private void respondToBlogInvitation(final InvitationItem item,
+			final boolean accept) {
+		runOnDbThread(new Runnable() {
+			@Override
+			public void run() {
+				try {
+					Blog b = (Blog) item.getShareable();
+					for (Contact c : item.getContacts()) {
+						blogSharingManager.respondToInvitation(b, c, accept);
+					}
+				} catch (DbException e) {
+					if (LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				}
+			}
+		});
+	}
+}
diff --git a/briar-android/src/org/briarproject/android/util/TextAvatarView.java b/briar-android/src/org/briarproject/android/util/TextAvatarView.java
index 6033021eb2..b48d0e7ad1 100644
--- a/briar-android/src/org/briarproject/android/util/TextAvatarView.java
+++ b/briar-android/src/org/briarproject/android/util/TextAvatarView.java
@@ -2,6 +2,7 @@ package org.briarproject.android.util;
 
 import android.content.Context;
 import android.graphics.Color;
+import android.graphics.drawable.Drawable;
 import android.support.v4.content.ContextCompat;
 import android.support.v7.widget.AppCompatTextView;
 import android.util.AttributeSet;
@@ -10,8 +11,10 @@ import android.widget.FrameLayout;
 import android.widget.TextView;
 
 import org.briarproject.R;
+import org.briarproject.api.identity.Author;
 
 import de.hdodenhof.circleimageview.CircleImageView;
+import im.delight.android.identicons.IdenticonDrawable;
 
 public class TextAvatarView extends FrameLayout {
 
@@ -83,4 +86,10 @@ public class TextAvatarView extends FrameLayout {
 		}
 	}
 
+	public void setAuthorAvatar(Author author) {
+		Drawable drawable = new IdenticonDrawable(author.getId().getBytes());
+		background.setImageDrawable(drawable);
+		character.setVisibility(GONE);
+	}
+
 }
-- 
GitLab