diff --git a/briar-android/src/org/briarproject/android/ActivityModule.java b/briar-android/src/org/briarproject/android/ActivityModule.java index 69505d32f3ea0d2f7bbef96cd5e21e97470dc57b..c5a1b275da416f4344c8867fe4f8b956f178fb0d 100644 --- a/briar-android/src/org/briarproject/android/ActivityModule.java +++ b/briar-android/src/org/briarproject/android/ActivityModule.java @@ -27,6 +27,10 @@ import org.briarproject.android.privategroup.creation.CreateGroupController; import org.briarproject.android.privategroup.creation.CreateGroupControllerImpl; import org.briarproject.android.privategroup.list.GroupListController; import org.briarproject.android.privategroup.list.GroupListControllerImpl; +import org.briarproject.android.sharing.InvitationsBlogController; +import org.briarproject.android.sharing.InvitationsBlogControllerImpl; +import org.briarproject.android.sharing.InvitationsForumController; +import org.briarproject.android.sharing.InvitationsForumControllerImpl; import dagger.Module; import dagger.Provides; @@ -125,6 +129,22 @@ public class ActivityModule { return forumController; } + @ActivityScope + @Provides + protected InvitationsForumController provideInvitationsForumController( + InvitationsForumControllerImpl invitationsForumController) { + activity.addLifecycleController(invitationsForumController); + return invitationsForumController; + } + + @ActivityScope + @Provides + protected InvitationsBlogController provideInvitationsBlogController( + InvitationsBlogControllerImpl invitationsBlogController) { + activity.addLifecycleController(invitationsBlogController); + return invitationsBlogController; + } + @ActivityScope @Provides BlogController provideBlogController(BlogControllerImpl blogController) { diff --git a/briar-android/src/org/briarproject/android/sharing/BlogInvitationAdapter.java b/briar-android/src/org/briarproject/android/sharing/BlogInvitationAdapter.java deleted file mode 100644 index 7b0e7dfa6e108067aed9470e96f88921216518de..0000000000000000000000000000000000000000 --- a/briar-android/src/org/briarproject/android/sharing/BlogInvitationAdapter.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.briarproject.android.sharing; - -import android.content.Context; - -import org.briarproject.R; -import org.briarproject.api.blogs.Blog; -import org.briarproject.api.sharing.InvitationItem; - -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 = getItemAt(position); - if (item == null) return; - - 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)); - } - } - - @Override - public int compare(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 deleted file mode 100644 index cfe914a9c2949f7b74b42dc508232f2aae2f865a..0000000000000000000000000000000000000000 --- a/briar-android/src/org/briarproject/android/sharing/ForumInvitationAdapter.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.briarproject.android.sharing; - -import android.content.Context; - -import org.briarproject.api.forum.Forum; -import org.briarproject.api.sharing.InvitationItem; - -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 = getItemAt(position); - if (item == null) return; - - 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()); - } - - @Override - public int compare(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/sharing/InvitationAdapter.java b/briar-android/src/org/briarproject/android/sharing/InvitationAdapter.java index db35fd55bc4dbd0ee3a416d6112167eee42514f1..5afd24c07c66b2750810dc195a6505dcc44e234e 100644 --- a/briar-android/src/org/briarproject/android/sharing/InvitationAdapter.java +++ b/briar-android/src/org/briarproject/android/sharing/InvitationAdapter.java @@ -1,111 +1,51 @@ package org.briarproject.android.sharing; import android.content.Context; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; -import android.widget.TextView; import org.briarproject.R; import org.briarproject.android.util.BriarAdapter; -import org.briarproject.android.view.TextAvatarView; -import org.briarproject.api.contact.Contact; import org.briarproject.api.sharing.InvitationItem; -import org.briarproject.util.StringUtils; -import java.util.ArrayList; -import java.util.Collection; +public abstract class InvitationAdapter<I extends InvitationItem, VH extends InvitationViewHolder<I>> + extends BriarAdapter<I, VH> { -import static android.view.View.GONE; -import static android.view.View.VISIBLE; + private final InvitationClickListener<I> listener; -abstract class InvitationAdapter extends - BriarAdapter<InvitationItem, InvitationAdapter.InvitationsViewHolder> { - - private final AvailableForumClickListener listener; - - InvitationAdapter(Context ctx, AvailableForumClickListener listener) { - super(ctx, InvitationItem.class); + public InvitationAdapter(Context ctx, Class<I> c, + InvitationClickListener<I> listener) { + super(ctx, c); this.listener = listener; } - @Override - public InvitationsViewHolder onCreateViewHolder(ViewGroup parent, - int viewType) { - - View v = LayoutInflater.from(ctx) - .inflate(R.layout.list_item_invitations, parent, false); - return new InvitationsViewHolder(v); + protected View getView(ViewGroup parent) { + return LayoutInflater.from(ctx) + .inflate(R.layout.list_item_invitations, parent, false); } @Override - public void onBindViewHolder(InvitationsViewHolder ui, int position) { - final InvitationItem item = getItemAt(position); + public void onBindViewHolder(VH ui, int position) { + final I item = getItemAt(position); if (item == null) return; - - Collection<String> names = new ArrayList<>(); - for (Contact c : item.getNewSharers()) - names.add(c.getAuthor().getName()); - ui.sharedBy.setText(ctx.getString(R.string.shared_by_format, - StringUtils.join(names, ", "))); - - if (item.isSubscribed()) { - ui.subscribed.setVisibility(VISIBLE); - } else { - ui.subscribed.setVisibility(GONE); - } - - ui.accept.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - listener.onItemClick(item, true); - } - }); - ui.decline.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - listener.onItemClick(item, false); - } - }); + ui.onBind(item, listener); } @Override - public boolean areContentsTheSame(InvitationItem oldItem, - InvitationItem newItem) { - return oldItem.isSubscribed() == newItem.isSubscribed() && - oldItem.getNewSharers().equals(newItem.getNewSharers()); + public boolean areItemsTheSame(I oldItem, I newItem) { + return oldItem.getShareable().equals(newItem.getShareable()); } @Override - public boolean areItemsTheSame(InvitationItem oldItem, - InvitationItem newItem) { - return oldItem.getShareable().equals(newItem.getShareable()); + public int compare(I o1, I o2) { + return String.CASE_INSENSITIVE_ORDER + .compare((o1.getShareable()).getName(), + (o2.getShareable()).getName()); } - static class InvitationsViewHolder extends RecyclerView.ViewHolder { - - final TextAvatarView avatar; - final TextView name; - private final TextView sharedBy; - final TextView subscribed; - private final Button accept; - private final Button decline; - - private InvitationsViewHolder(View v) { - super(v); - - avatar = (TextAvatarView) v.findViewById(R.id.avatarView); - name = (TextView) v.findViewById(R.id.forumNameView); - sharedBy = (TextView) v.findViewById(R.id.sharedByView); - subscribed = (TextView) v.findViewById(R.id.forumSubscribedView); - accept = (Button) v.findViewById(R.id.acceptButton); - decline = (Button) v.findViewById(R.id.declineButton); - } + public interface InvitationClickListener<I> { + void onItemClick(I item, boolean accept); } - interface AvailableForumClickListener { - void onItemClick(InvitationItem item, boolean accept); - } } diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationViewHolder.java b/briar-android/src/org/briarproject/android/sharing/InvitationViewHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..2c8337c13c3271ac3eb95d601f6bb45739c55e97 --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/InvitationViewHolder.java @@ -0,0 +1,69 @@ +package org.briarproject.android.sharing; + +import android.support.annotation.CallSuper; +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import org.briarproject.R; +import org.briarproject.android.sharing.InvitationAdapter.InvitationClickListener; +import org.briarproject.android.view.TextAvatarView; +import org.briarproject.api.sharing.InvitationItem; + +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + +public class InvitationViewHolder<I extends InvitationItem> + extends RecyclerView.ViewHolder { + + private final TextAvatarView avatar; + private final TextView name; + protected final TextView sharedBy; + private final TextView subscribed; + private final Button accept; + private final Button decline; + + public InvitationViewHolder(View v) { + super(v); + + avatar = (TextAvatarView) v.findViewById(R.id.avatarView); + name = (TextView) v.findViewById(R.id.forumNameView); + sharedBy = (TextView) v.findViewById(R.id.sharedByView); + subscribed = (TextView) v.findViewById(R.id.forumSubscribedView); + accept = (Button) v.findViewById(R.id.acceptButton); + decline = (Button) v.findViewById(R.id.declineButton); + } + + @CallSuper + public void onBind(@Nullable final I item, + final InvitationClickListener<I> listener) { + if (item == null) return; + + avatar.setText(item.getShareable().getName().substring(0, 1)); + avatar.setBackgroundBytes(item.getShareable().getId().getBytes()); + + name.setText(item.getShareable().getName()); + + if (item.isSubscribed()) { + subscribed.setVisibility(VISIBLE); + } else { + subscribed.setVisibility(GONE); + } + + accept.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.onItemClick(item, true); + } + }); + decline.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.onItemClick(item, false); + } + }); + } + +} \ No newline at end of file diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java b/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java index 772be9bf605e7dd472ffb152a2f395b360147988..a8313f56d5aaf8789535be176fe8b335c99963ce 100644 --- a/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java +++ b/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java @@ -2,39 +2,34 @@ package org.briarproject.android.sharing; import android.content.Context; import android.os.Bundle; -import android.support.annotation.CallSuper; +import android.support.annotation.StringRes; import android.support.v7.widget.LinearLayoutManager; import android.widget.Toast; import org.briarproject.R; import org.briarproject.android.BriarActivity; +import org.briarproject.android.controller.handler.UiResultExceptionHandler; +import org.briarproject.android.sharing.InvitationsController.InvitationListener; import org.briarproject.android.view.BriarRecyclerView; -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.db.DbException; import org.briarproject.api.sharing.InvitationItem; import java.util.Collection; import java.util.logging.Logger; -import javax.inject.Inject; - import static android.widget.Toast.LENGTH_SHORT; -import static org.briarproject.android.sharing.InvitationAdapter.AvailableForumClickListener; +import static org.briarproject.android.sharing.InvitationAdapter.InvitationClickListener; -abstract class InvitationsActivity extends BriarActivity - implements EventListener, AvailableForumClickListener { +public abstract class InvitationsActivity<I extends InvitationItem> + extends BriarActivity + implements InvitationListener, InvitationClickListener<I> { protected static final Logger LOG = Logger.getLogger(InvitationsActivity.class.getName()); - protected InvitationAdapter adapter; + private InvitationAdapter<I, ?> adapter; private BriarRecyclerView list; - @Inject - EventBus eventBus; - @Override public void onCreate(Bundle state) { super.onCreate(state); @@ -42,7 +37,6 @@ abstract class InvitationsActivity extends BriarActivity setContentView(R.layout.list); adapter = getAdapter(this, this); - list = (BriarRecyclerView) findViewById(R.id.list); if (list != null) { list.setLayoutManager(new LinearLayoutManager(this)); @@ -50,32 +44,24 @@ abstract class InvitationsActivity extends BriarActivity } } + abstract protected InvitationAdapter<I, ?> getAdapter(Context ctx, + InvitationClickListener listener); + @Override public void onStart() { super.onStart(); - eventBus.addListener(this); loadInvitations(false); } @Override public void onStop() { super.onStop(); - eventBus.removeListener(this); adapter.clear(); list.showProgressBar(); } @Override - @CallSuper - public void eventOccurred(Event e) { - if (e instanceof ContactRemovedEvent) { - LOG.info("Contact removed, reloading..."); - loadInvitations(true); - } - } - - @Override - public void onItemClick(InvitationItem item, boolean accept) { + public void onItemClick(I item, boolean accept) { respondToInvitation(item, accept); // show toast @@ -91,26 +77,58 @@ abstract class InvitationsActivity extends BriarActivity } } - abstract protected InvitationAdapter getAdapter(Context ctx, - AvailableForumClickListener listener); + @Override + public void loadInvitations(final boolean clear) { + final int revision = adapter.getRevision(); + getController().loadInvitations(clear, + new UiResultExceptionHandler<Collection<I>, DbException>( + this) { + @Override + public void onResultUi(Collection<I> items) { + displayInvitations(revision, items, clear); + } + + @Override + public void onExceptionUi(DbException exception) { + // TODO proper error handling + finish(); + } + }); + } + + abstract protected InvitationsController<I> getController(); + + protected void respondToInvitation(final I item, + final boolean accept) { + getController().respondToInvitation(item, accept, + new UiResultExceptionHandler<Void, DbException>(this) { + @Override + public void onResultUi(Void result) { - abstract protected void loadInvitations(boolean clear); + } - abstract protected void respondToInvitation(final InvitationItem item, - final boolean accept); + @Override + public void onExceptionUi(DbException exception) { + // TODO proper error handling + finish(); + } + }); + } + @StringRes abstract protected int getAcceptRes(); + @StringRes abstract protected int getDeclineRes(); protected void displayInvitations(final int revision, - final Collection<InvitationItem> invitations, final boolean clear) { + final Collection<I> invitations, final boolean clear) { runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { if (invitations.isEmpty()) { LOG.info("No more invitations available, finishing"); - finish(); + supportFinishAfterTransition(); } else if (revision == adapter.getRevision()) { adapter.incrementRevision(); if (clear) adapter.setItems(invitations); diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsBlogActivity.java b/briar-android/src/org/briarproject/android/sharing/InvitationsBlogActivity.java index 1b0e5f3bd7be5ce10c72991abb40792584d66bd0..da7d014e0811ad1d2687a0f9070c64f6b42e71ca 100644 --- a/briar-android/src/org/briarproject/android/sharing/InvitationsBlogActivity.java +++ b/briar-android/src/org/briarproject/android/sharing/InvitationsBlogActivity.java @@ -4,34 +4,17 @@ import android.content.Context; import org.briarproject.R; import org.briarproject.android.ActivityComponent; -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.event.BlogInvitationReceivedEvent; -import org.briarproject.api.event.Event; -import org.briarproject.api.event.GroupAddedEvent; -import org.briarproject.api.event.GroupRemovedEvent; -import org.briarproject.api.sharing.InvitationItem; -import org.briarproject.api.sync.ClientId; - -import java.util.ArrayList; -import java.util.Collection; +import org.briarproject.api.sharing.SharingInvitationItem; import javax.inject.Inject; -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.InvitationAdapter.InvitationClickListener; -public class InvitationsBlogActivity extends InvitationsActivity { +public class InvitationsBlogActivity + extends InvitationsActivity<SharingInvitationItem> { - // Fields that are accessed from background threads must be volatile - @Inject - volatile BlogManager blogManager; @Inject - volatile BlogSharingManager blogSharingManager; + InvitationsBlogController controller; @Override public void injectActivity(ActivityComponent component) { @@ -39,75 +22,14 @@ public class InvitationsBlogActivity extends InvitationsActivity { } @Override - public void eventOccurred(Event e) { - super.eventOccurred(e); - - if (e instanceof GroupAddedEvent) { - GroupAddedEvent g = (GroupAddedEvent) e; - ClientId cId = g.getGroup().getClientId(); - if (cId.equals(blogManager.getClientId())) { - LOG.info("Blog added, reloading"); - loadInvitations(false); - } - } else if (e instanceof GroupRemovedEvent) { - GroupRemovedEvent g = (GroupRemovedEvent) e; - ClientId cId = g.getGroup().getClientId(); - if (cId.equals(blogManager.getClientId())) { - LOG.info("Blog removed, reloading"); - loadInvitations(false); - } - } else if (e instanceof BlogInvitationReceivedEvent) { - LOG.info("Blog invitation received, reloading"); - loadInvitations(false); - } - } - - @Override - protected InvitationAdapter getAdapter(Context ctx, - AvailableForumClickListener listener) { - return new BlogInvitationAdapter(ctx, listener); + protected InvitationsController<SharingInvitationItem> getController() { + return controller; } @Override - protected void loadInvitations(final boolean clear) { - final int revision = adapter.getRevision(); - runOnDbThread(new Runnable() { - @Override - public void run() { - try { - Collection<InvitationItem> invitations = new ArrayList<>(); - long now = System.currentTimeMillis(); - invitations.addAll(blogSharingManager.getInvitations()); - long duration = System.currentTimeMillis() - now; - if (LOG.isLoggable(INFO)) - LOG.info("Load took " + duration + " ms"); - displayInvitations(revision, invitations, clear); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - } - } - }); - } - - @Override - protected void respondToInvitation(final InvitationItem item, - final boolean accept) { - runOnDbThread(new Runnable() { - @Override - public void run() { - try { - Blog b = (Blog) item.getShareable(); - for (Contact c : item.getNewSharers()) { - // TODO: What happens if a contact has been removed? - blogSharingManager.respondToInvitation(b, c, accept); - } - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - } - } - }); + protected InvitationAdapter<SharingInvitationItem, ?> getAdapter( + Context ctx, InvitationClickListener listener) { + return new SharingInvitationAdapter(ctx, listener); } @Override @@ -119,4 +41,5 @@ public class InvitationsBlogActivity extends InvitationsActivity { protected int getDeclineRes() { return R.string.blogs_sharing_declined_toast; } + } diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsBlogController.java b/briar-android/src/org/briarproject/android/sharing/InvitationsBlogController.java new file mode 100644 index 0000000000000000000000000000000000000000..4efcff5cf39b305d146e1400973fdeecd345d16b --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/InvitationsBlogController.java @@ -0,0 +1,7 @@ +package org.briarproject.android.sharing; + +import org.briarproject.api.sharing.SharingInvitationItem; + +public interface InvitationsBlogController + extends InvitationsController<SharingInvitationItem> { +} diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsBlogControllerImpl.java b/briar-android/src/org/briarproject/android/sharing/InvitationsBlogControllerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..024109f7edffdf7891d0f52caa913dfc06ec9d21 --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/InvitationsBlogControllerImpl.java @@ -0,0 +1,82 @@ +package org.briarproject.android.sharing; + +import org.briarproject.android.controller.handler.ResultExceptionHandler; +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.DatabaseExecutor; +import org.briarproject.api.db.DbException; +import org.briarproject.api.event.BlogInvitationReceivedEvent; +import org.briarproject.api.event.Event; +import org.briarproject.api.event.EventBus; +import org.briarproject.api.lifecycle.LifecycleManager; +import org.briarproject.api.sharing.SharingInvitationItem; +import org.briarproject.api.sync.ClientId; + +import java.util.Collection; +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +import static java.util.logging.Level.WARNING; + +public class InvitationsBlogControllerImpl + extends InvitationsControllerImpl<SharingInvitationItem> + implements InvitationsBlogController { + + private final BlogManager blogManager; + private final BlogSharingManager blogSharingManager; + + @Inject + InvitationsBlogControllerImpl(@DatabaseExecutor Executor dbExecutor, + LifecycleManager lifecycleManager, EventBus eventBus, + BlogManager blogManager, BlogSharingManager blogSharingManager) { + super(dbExecutor, lifecycleManager, eventBus); + this.blogManager = blogManager; + this.blogSharingManager = blogSharingManager; + } + + @Override + public void eventOccurred(Event e) { + super.eventOccurred(e); + + if (e instanceof BlogInvitationReceivedEvent) { + LOG.info("Blog invitation received, reloading"); + listener.loadInvitations(false); + } + } + + @Override + protected ClientId getClientId() { + return blogManager.getClientId(); + } + + @Override + protected Collection<SharingInvitationItem> getInvitations() throws DbException { + return blogSharingManager.getInvitations(); + } + + @Override + public void respondToInvitation(final SharingInvitationItem item, + final boolean accept, + final ResultExceptionHandler<Void, DbException> handler) { + runOnDbThread(new Runnable() { + @Override + public void run() { + try { + Blog f = (Blog) item.getShareable(); + for (Contact c : item.getNewSharers()) { + // TODO: What happens if a contact has been removed? + blogSharingManager.respondToInvitation(f, c, accept); + } + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + handler.onException(e); + } + } + }); + } + +} diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsController.java b/briar-android/src/org/briarproject/android/sharing/InvitationsController.java new file mode 100644 index 0000000000000000000000000000000000000000..91576dfdf814ffa886fe575500ead2305d93413a --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/InvitationsController.java @@ -0,0 +1,25 @@ +package org.briarproject.android.sharing; + +import org.briarproject.android.controller.ActivityLifecycleController; +import org.briarproject.android.controller.handler.ResultExceptionHandler; +import org.briarproject.api.db.DbException; +import org.briarproject.api.sharing.InvitationItem; + +import java.util.Collection; + +public interface InvitationsController<I extends InvitationItem> + extends ActivityLifecycleController { + + void loadInvitations(boolean clear, + ResultExceptionHandler<Collection<I>, DbException> handler); + + void respondToInvitation(I item, boolean accept, + ResultExceptionHandler<Void, DbException> handler); + + interface InvitationListener { + + void loadInvitations(boolean clear); + + } + +} diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsControllerImpl.java b/briar-android/src/org/briarproject/android/sharing/InvitationsControllerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..5fcace519adf4ecaa3376f8e82eb3054ec751109 --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/InvitationsControllerImpl.java @@ -0,0 +1,116 @@ +package org.briarproject.android.sharing; + +import android.app.Activity; +import android.support.annotation.CallSuper; + +import org.briarproject.android.controller.DbControllerImpl; +import org.briarproject.android.controller.handler.ResultExceptionHandler; +import org.briarproject.api.db.DatabaseExecutor; +import org.briarproject.api.db.DbException; +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.GroupAddedEvent; +import org.briarproject.api.event.GroupRemovedEvent; +import org.briarproject.api.lifecycle.LifecycleManager; +import org.briarproject.api.sharing.InvitationItem; +import org.briarproject.api.sync.ClientId; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; + +public abstract class InvitationsControllerImpl<I extends InvitationItem> + extends DbControllerImpl + implements InvitationsController<I>, EventListener { + + protected static final Logger LOG = + Logger.getLogger(InvitationsControllerImpl.class.getName()); + + private final EventBus eventBus; + protected InvitationListener listener; + + public InvitationsControllerImpl(@DatabaseExecutor Executor dbExecutor, + LifecycleManager lifecycleManager, EventBus eventBus) { + super(dbExecutor, lifecycleManager); + this.eventBus = eventBus; + } + + @Override + public void onActivityCreate(Activity activity) { + listener = (InvitationListener) activity; + } + + @Override + public void onActivityStart() { + eventBus.addListener(this); + } + + @Override + public void onActivityStop() { + eventBus.removeListener(this); + } + + @Override + public void onActivityDestroy() { + + } + + @CallSuper + @Override + public void eventOccurred(Event e) { + if (e instanceof ContactRemovedEvent) { + LOG.info("Contact removed, reloading..."); + listener.loadInvitations(true); + } else if (e instanceof GroupAddedEvent) { + GroupAddedEvent g = (GroupAddedEvent) e; + ClientId cId = g.getGroup().getClientId(); + if (cId.equals(getClientId())) { + LOG.info("Group added, reloading"); + listener.loadInvitations(false); + } + } else if (e instanceof GroupRemovedEvent) { + GroupRemovedEvent g = (GroupRemovedEvent) e; + ClientId cId = g.getGroup().getClientId(); + if (cId.equals(getClientId())) { + LOG.info("Group removed, reloading"); + listener.loadInvitations(true); + } + } + } + + protected abstract ClientId getClientId(); + + @Override + public void loadInvitations(final boolean clear, + final ResultExceptionHandler<Collection<I>, DbException> handler) { + runOnDbThread(new Runnable() { + @Override + public void run() { + Collection<I> invitations = new ArrayList<>(); + try { + long now = System.currentTimeMillis(); + invitations.addAll(getInvitations()); + long duration = System.currentTimeMillis() - now; + if (LOG.isLoggable(INFO)) + LOG.info( + "Loading invitations took " + duration + " ms"); + handler.onResult(invitations); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + handler.onException(e); + } + } + }); + } + + @DatabaseExecutor + protected abstract Collection<I> getInvitations() throws DbException; + +} diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsForumActivity.java b/briar-android/src/org/briarproject/android/sharing/InvitationsForumActivity.java index d64dab69f04af496aa5469aa79464c8eaa82d5a9..1c32ee1125e43d2fcb18001a1c755aedb74a8303 100644 --- a/briar-android/src/org/briarproject/android/sharing/InvitationsForumActivity.java +++ b/briar-android/src/org/briarproject/android/sharing/InvitationsForumActivity.java @@ -4,34 +4,17 @@ import android.content.Context; import org.briarproject.R; import org.briarproject.android.ActivityComponent; -import org.briarproject.api.contact.Contact; -import org.briarproject.api.db.DbException; -import org.briarproject.api.event.Event; -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 org.briarproject.api.sharing.InvitationItem; -import org.briarproject.api.sync.ClientId; - -import java.util.ArrayList; -import java.util.Collection; +import org.briarproject.api.sharing.SharingInvitationItem; import javax.inject.Inject; -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.InvitationAdapter.InvitationClickListener; -public class InvitationsForumActivity extends InvitationsActivity { +public class InvitationsForumActivity + extends InvitationsActivity<SharingInvitationItem> { - // Fields that are accessed from background threads must be volatile - @Inject - volatile ForumManager forumManager; @Inject - volatile ForumSharingManager forumSharingManager; + InvitationsForumController controller; @Override public void injectActivity(ActivityComponent component) { @@ -39,75 +22,14 @@ public class InvitationsForumActivity extends InvitationsActivity { } @Override - public void eventOccurred(Event e) { - super.eventOccurred(e); - - if (e instanceof GroupAddedEvent) { - GroupAddedEvent g = (GroupAddedEvent) e; - ClientId cId = g.getGroup().getClientId(); - if (cId.equals(forumManager.getClientId())) { - LOG.info("Forum added, reloading"); - loadInvitations(false); - } - } else if (e instanceof GroupRemovedEvent) { - GroupRemovedEvent g = (GroupRemovedEvent) e; - ClientId cId = g.getGroup().getClientId(); - if (cId.equals(forumManager.getClientId())) { - LOG.info("Forum removed, reloading"); - loadInvitations(false); - } - } else if (e instanceof ForumInvitationReceivedEvent) { - LOG.info("Forum invitation received, reloading"); - loadInvitations(false); - } - } - - @Override - protected InvitationAdapter getAdapter(Context ctx, - AvailableForumClickListener listener) { - return new ForumInvitationAdapter(ctx, listener); + protected InvitationsController<SharingInvitationItem> getController() { + return controller; } @Override - protected void loadInvitations(final boolean clear) { - final int revision = adapter.getRevision(); - runOnDbThread(new Runnable() { - @Override - public void run() { - try { - Collection<InvitationItem> invitations = new ArrayList<>(); - long now = System.currentTimeMillis(); - invitations.addAll(forumSharingManager.getInvitations()); - long duration = System.currentTimeMillis() - now; - if (LOG.isLoggable(INFO)) - LOG.info("Load took " + duration + " ms"); - displayInvitations(revision, invitations, clear); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - } - } - }); - } - - @Override - protected void respondToInvitation(final InvitationItem item, - final boolean accept) { - runOnDbThread(new Runnable() { - @Override - public void run() { - try { - Forum f = (Forum) item.getShareable(); - for (Contact c : item.getNewSharers()) { - // TODO: What happens if a contact has been removed? - forumSharingManager.respondToInvitation(f, c, accept); - } - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - } - } - }); + protected InvitationAdapter<SharingInvitationItem, ?> getAdapter( + Context ctx, InvitationClickListener listener) { + return new SharingInvitationAdapter(ctx, listener); } @Override @@ -119,4 +41,5 @@ public class InvitationsForumActivity extends InvitationsActivity { protected int getDeclineRes() { return R.string.forum_declined_toast; } + } diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsForumController.java b/briar-android/src/org/briarproject/android/sharing/InvitationsForumController.java new file mode 100644 index 0000000000000000000000000000000000000000..e58c4a41c75bb38d3aa2b40013a1f65733499c53 --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/InvitationsForumController.java @@ -0,0 +1,7 @@ +package org.briarproject.android.sharing; + +import org.briarproject.api.sharing.SharingInvitationItem; + +public interface InvitationsForumController + extends InvitationsController<SharingInvitationItem> { +} diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsForumControllerImpl.java b/briar-android/src/org/briarproject/android/sharing/InvitationsForumControllerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..25f672460a4752760ab3a97a47c60464aadfced8 --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/InvitationsForumControllerImpl.java @@ -0,0 +1,83 @@ +package org.briarproject.android.sharing; + +import org.briarproject.android.controller.handler.ResultExceptionHandler; +import org.briarproject.api.contact.Contact; +import org.briarproject.api.db.DatabaseExecutor; +import org.briarproject.api.db.DbException; +import org.briarproject.api.event.Event; +import org.briarproject.api.event.EventBus; +import org.briarproject.api.event.ForumInvitationReceivedEvent; +import org.briarproject.api.forum.Forum; +import org.briarproject.api.forum.ForumManager; +import org.briarproject.api.forum.ForumSharingManager; +import org.briarproject.api.lifecycle.LifecycleManager; +import org.briarproject.api.sharing.SharingInvitationItem; +import org.briarproject.api.sync.ClientId; + +import java.util.Collection; +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +import static java.util.logging.Level.WARNING; + +public class InvitationsForumControllerImpl + extends InvitationsControllerImpl<SharingInvitationItem> + implements InvitationsForumController { + + private final ForumManager forumManager; + private final ForumSharingManager forumSharingManager; + + @Inject + InvitationsForumControllerImpl(@DatabaseExecutor Executor dbExecutor, + LifecycleManager lifecycleManager, EventBus eventBus, + ForumManager forumManager, + ForumSharingManager forumSharingManager) { + super(dbExecutor, lifecycleManager, eventBus); + this.forumManager = forumManager; + this.forumSharingManager = forumSharingManager; + } + + @Override + public void eventOccurred(Event e) { + super.eventOccurred(e); + + if (e instanceof ForumInvitationReceivedEvent) { + LOG.info("Forum invitation received, reloading"); + listener.loadInvitations(false); + } + } + + @Override + protected ClientId getClientId() { + return forumManager.getClientId(); + } + + @Override + protected Collection<SharingInvitationItem> getInvitations() throws DbException { + return forumSharingManager.getInvitations(); + } + + @Override + public void respondToInvitation(final SharingInvitationItem item, + final boolean accept, + final ResultExceptionHandler<Void, DbException> handler) { + runOnDbThread(new Runnable() { + @Override + public void run() { + try { + Forum f = (Forum) item.getShareable(); + for (Contact c : item.getNewSharers()) { + // TODO: What happens if a contact has been removed? + forumSharingManager.respondToInvitation(f, c, accept); + } + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + handler.onException(e); + } + } + }); + } + +} diff --git a/briar-android/src/org/briarproject/android/sharing/SharingInvitationAdapter.java b/briar-android/src/org/briarproject/android/sharing/SharingInvitationAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..2964c9b930db4f376bd028d2d5d3b0db98d8796d --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/SharingInvitationAdapter.java @@ -0,0 +1,29 @@ +package org.briarproject.android.sharing; + +import android.content.Context; +import android.view.ViewGroup; + +import org.briarproject.api.sharing.SharingInvitationItem; + +class SharingInvitationAdapter extends + InvitationAdapter<SharingInvitationItem, SharingInvitationViewHolder> { + + SharingInvitationAdapter(Context ctx, InvitationClickListener listener) { + super(ctx, SharingInvitationItem.class, listener); + } + + @Override + public SharingInvitationViewHolder onCreateViewHolder( + ViewGroup parent, + int viewType) { + return new SharingInvitationViewHolder(getView(parent)); + } + + @Override + public boolean areContentsTheSame(SharingInvitationItem oldItem, + SharingInvitationItem newItem) { + return oldItem.isSubscribed() == newItem.isSubscribed() && + oldItem.getNewSharers().equals(newItem.getNewSharers()); + } + +} diff --git a/briar-android/src/org/briarproject/android/sharing/SharingInvitationViewHolder.java b/briar-android/src/org/briarproject/android/sharing/SharingInvitationViewHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..b10672468f1ada6f65c482c3c84576f4020527af --- /dev/null +++ b/briar-android/src/org/briarproject/android/sharing/SharingInvitationViewHolder.java @@ -0,0 +1,35 @@ +package org.briarproject.android.sharing; + +import android.support.annotation.Nullable; +import android.view.View; + +import org.briarproject.R; +import org.briarproject.api.contact.Contact; +import org.briarproject.api.sharing.SharingInvitationItem; +import org.briarproject.util.StringUtils; + +import java.util.ArrayList; +import java.util.Collection; + +public class SharingInvitationViewHolder + extends InvitationViewHolder<SharingInvitationItem> { + + public SharingInvitationViewHolder(View v) { + super(v); + } + + @Override + public void onBind(@Nullable final SharingInvitationItem item, + final InvitationAdapter.InvitationClickListener<SharingInvitationItem> listener) { + super.onBind(item, listener); + if (item == null) return; + + Collection<String> names = new ArrayList<>(); + for (Contact c : item.getNewSharers()) + names.add(c.getAuthor().getName()); + sharedBy.setText( + sharedBy.getContext().getString(R.string.shared_by_format, + StringUtils.join(names, ", "))); + } + +}