diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml index aad00c6d9e747d81de25845bbd0493c0f2060861..19b30172ceb158bc44e769e04a9b5bd9f5e474ae 100644 --- a/briar-android/AndroidManifest.xml +++ b/briar-android/AndroidManifest.xml @@ -122,6 +122,16 @@ /> </activity> + <activity + android:name=".android.privategroup.invitation.InvitationsGroupActivity" + android:label="@string/groups_invitations_title" + android:parentActivityName=".android.NavDrawerActivity"> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value=".android.NavDrawerActivity" + /> + </activity> + <activity android:name=".android.sharing.InvitationsForumActivity" android:label="@string/forum_invitations_title" diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml index 872800c9f05297d0fa40ae400567dfe9d37069da..2b04c8b8cf816d3560bd1e6247d65ae71e3232f7 100644 --- a/briar-android/res/values/strings.xml +++ b/briar-android/res/values/strings.xml @@ -168,6 +168,15 @@ <string name="groups_leave">Leave Group</string> <string name="groups_dissolve">Dissolve Group</string> + <!-- Private Group Invitations --> + <string name="groups_invitations_title">Group Invitations</string> + <string name="groups_invitations_joined">Joined Group</string> + <string name="groups_invitations_declined">Group Invitation Declined</string> + <plurals name="groups_invitations_open"> + <item quantity="one">%d open group invitation</item> + <item quantity="other">%d open group invitations</item> + </plurals> + <!-- Forums --> <string name="no_forums">You don\'t have any forums yet.\n\nWhy don\'t you create a new one yourself by tapping the + icon at the top?\n\nYou can also ask your contacts to share forums with you.</string> <string name="create_forum_title">New Forum</string> diff --git a/briar-android/src/org/briarproject/android/ActivityComponent.java b/briar-android/src/org/briarproject/android/ActivityComponent.java index 1179905b6932ced492c512debc964cc53d0ff76a..61889b113e2acf81fc90bee798213384ce069326 100644 --- a/briar-android/src/org/briarproject/android/ActivityComponent.java +++ b/briar-android/src/org/briarproject/android/ActivityComponent.java @@ -33,6 +33,7 @@ import org.briarproject.android.privategroup.creation.CreateGroupFragment; import org.briarproject.android.privategroup.conversation.GroupActivity; import org.briarproject.android.privategroup.creation.CreateGroupMessageFragment; import org.briarproject.android.privategroup.list.GroupListFragment; +import org.briarproject.android.privategroup.invitation.InvitationsGroupActivity; import org.briarproject.android.sharing.ContactSelectorFragment; import org.briarproject.android.sharing.InvitationsBlogActivity; import org.briarproject.android.sharing.InvitationsForumActivity; @@ -77,8 +78,8 @@ public interface ActivityComponent { void inject(InvitationsBlogActivity activity); void inject(CreateGroupActivity activity); - void inject(GroupActivity activity); + void inject(InvitationsGroupActivity activity); void inject(CreateForumActivity activity); diff --git a/briar-android/src/org/briarproject/android/ActivityModule.java b/briar-android/src/org/briarproject/android/ActivityModule.java index c5a1b275da416f4344c8867fe4f8b956f178fb0d..39d460151a71f0e9014128da6503aec8d29b09c3 100644 --- a/briar-android/src/org/briarproject/android/ActivityModule.java +++ b/briar-android/src/org/briarproject/android/ActivityModule.java @@ -25,6 +25,8 @@ import org.briarproject.android.privategroup.conversation.GroupController; import org.briarproject.android.privategroup.conversation.GroupControllerImpl; import org.briarproject.android.privategroup.creation.CreateGroupController; import org.briarproject.android.privategroup.creation.CreateGroupControllerImpl; +import org.briarproject.android.privategroup.invitation.InvitationsGroupController; +import org.briarproject.android.privategroup.invitation.InvitationsGroupControllerImpl; import org.briarproject.android.privategroup.list.GroupListController; import org.briarproject.android.privategroup.list.GroupListControllerImpl; import org.briarproject.android.sharing.InvitationsBlogController; @@ -121,6 +123,13 @@ public class ActivityModule { return groupController; } + @ActivityScope + @Provides + protected InvitationsGroupController provideInvitationsGroupController( + InvitationsGroupControllerImpl invitationsGroupController) { + return invitationsGroupController; + } + @ActivityScope @Provides protected ForumController provideForumController( diff --git a/briar-android/src/org/briarproject/android/AndroidComponent.java b/briar-android/src/org/briarproject/android/AndroidComponent.java index ceb3cd9e0b94be42eaa86acd8e2e24df8658b982..6081f1da9b5172bc1c1a6d2e7a74c47f37b6ad27 100644 --- a/briar-android/src/org/briarproject/android/AndroidComponent.java +++ b/briar-android/src/org/briarproject/android/AndroidComponent.java @@ -35,6 +35,7 @@ import org.briarproject.api.messaging.PrivateMessageFactory; import org.briarproject.api.plugins.ConnectionRegistry; import org.briarproject.api.plugins.PluginManager; import org.briarproject.api.privategroup.PrivateGroupManager; +import org.briarproject.api.privategroup.invitation.GroupInvitationManager; import org.briarproject.api.settings.SettingsManager; import org.briarproject.api.system.Clock; import org.briarproject.plugins.AndroidPluginsModule; @@ -96,6 +97,8 @@ public interface AndroidComponent extends CoreEagerSingletons { PrivateGroupManager privateGroupManager(); + GroupInvitationManager groupInvitationManager(); + ForumManager forumManager(); ForumSharingManager forumSharingManager(); diff --git a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java index f1faecd46d260908e5c7b7e89428df6b2aed2b8b..779fac49bbaaa86dbcf7a3d3d062c10308d193ff 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java +++ b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java @@ -220,10 +220,10 @@ public class ForumListFragment extends BaseEventFragment implements if (availableCount == 0) { snackbar.dismiss(); } else { - snackbar.show(); snackbar.setText(getResources().getQuantityString( R.plurals.forums_shared, availableCount, availableCount)); + if (!snackbar.isShownOrQueued()) snackbar.show(); } } }); diff --git a/briar-android/src/org/briarproject/android/privategroup/invitation/GroupInvitationViewHolder.java b/briar-android/src/org/briarproject/android/privategroup/invitation/GroupInvitationViewHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..25aef8ce8d2df68022ee0c7dd4e0286b85347c99 --- /dev/null +++ b/briar-android/src/org/briarproject/android/privategroup/invitation/GroupInvitationViewHolder.java @@ -0,0 +1,28 @@ +package org.briarproject.android.privategroup.invitation; + +import android.support.annotation.Nullable; +import android.view.View; + +import org.briarproject.R; +import org.briarproject.android.sharing.InvitationAdapter.InvitationClickListener; +import org.briarproject.android.sharing.InvitationViewHolder; +import org.briarproject.api.privategroup.invitation.GroupInvitationItem; + +public class GroupInvitationViewHolder extends InvitationViewHolder<GroupInvitationItem> { + + public GroupInvitationViewHolder(View v) { + super(v); + } + + @Override + public void onBind(@Nullable final GroupInvitationItem item, + final InvitationClickListener<GroupInvitationItem> listener) { + super.onBind(item, listener); + if (item == null) return; + + sharedBy.setText( + sharedBy.getContext().getString(R.string.groups_created_by, + item.getCreator().getAuthor().getName())); + } + +} \ No newline at end of file diff --git a/briar-android/src/org/briarproject/android/privategroup/invitation/InvitationGroupAdapter.java b/briar-android/src/org/briarproject/android/privategroup/invitation/InvitationGroupAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..7a53d48bd67a1a6206a8e4b4033dcdeb258b330f --- /dev/null +++ b/briar-android/src/org/briarproject/android/privategroup/invitation/InvitationGroupAdapter.java @@ -0,0 +1,28 @@ +package org.briarproject.android.privategroup.invitation; + +import android.content.Context; +import android.view.ViewGroup; + +import org.briarproject.android.sharing.InvitationAdapter; +import org.briarproject.api.privategroup.invitation.GroupInvitationItem; + +class InvitationGroupAdapter extends + InvitationAdapter<GroupInvitationItem, GroupInvitationViewHolder> { + + InvitationGroupAdapter(Context ctx, + InvitationClickListener<GroupInvitationItem> listener) { + super(ctx, GroupInvitationItem.class, listener); + } + + @Override + public GroupInvitationViewHolder onCreateViewHolder(ViewGroup parent, + int viewType) { + return new GroupInvitationViewHolder(getView(parent)); + } + + @Override + public boolean areContentsTheSame(GroupInvitationItem item1, + GroupInvitationItem item2) { + return item1.isSubscribed() == item2.isSubscribed(); + } +} diff --git a/briar-android/src/org/briarproject/android/privategroup/invitation/InvitationsGroupActivity.java b/briar-android/src/org/briarproject/android/privategroup/invitation/InvitationsGroupActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..2815e6fb391b509a3d85cccab7b92a80762e52ce --- /dev/null +++ b/briar-android/src/org/briarproject/android/privategroup/invitation/InvitationsGroupActivity.java @@ -0,0 +1,47 @@ +package org.briarproject.android.privategroup.invitation; + +import android.content.Context; + +import org.briarproject.R; +import org.briarproject.android.ActivityComponent; +import org.briarproject.android.sharing.InvitationAdapter; +import org.briarproject.android.sharing.InvitationsActivity; +import org.briarproject.api.privategroup.invitation.GroupInvitationItem; + +import javax.inject.Inject; + +import static org.briarproject.android.sharing.InvitationAdapter.InvitationClickListener; + +public class InvitationsGroupActivity + extends InvitationsActivity<GroupInvitationItem> { + + @Inject + protected InvitationsGroupController controller; + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + @Override + protected InvitationsGroupController getController() { + return controller; + } + + @Override + protected InvitationAdapter<GroupInvitationItem, ?> getAdapter(Context ctx, + InvitationClickListener listener) { + return new InvitationGroupAdapter(ctx, listener); + } + + @Override + protected int getAcceptRes() { + return R.string.groups_invitations_joined; + } + + @Override + protected int getDeclineRes() { + return R.string.groups_invitations_declined; + } + +} diff --git a/briar-android/src/org/briarproject/android/privategroup/invitation/InvitationsGroupController.java b/briar-android/src/org/briarproject/android/privategroup/invitation/InvitationsGroupController.java new file mode 100644 index 0000000000000000000000000000000000000000..f158064ba56b2c612c88af7eb782c076de277c23 --- /dev/null +++ b/briar-android/src/org/briarproject/android/privategroup/invitation/InvitationsGroupController.java @@ -0,0 +1,8 @@ +package org.briarproject.android.privategroup.invitation; + +import org.briarproject.android.sharing.InvitationsController; +import org.briarproject.api.privategroup.invitation.GroupInvitationItem; + +public interface InvitationsGroupController + extends InvitationsController<GroupInvitationItem> { +} diff --git a/briar-android/src/org/briarproject/android/privategroup/invitation/InvitationsGroupControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/invitation/InvitationsGroupControllerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..0bde16e851464ac74970f8f2cfcdd944e05c730c --- /dev/null +++ b/briar-android/src/org/briarproject/android/privategroup/invitation/InvitationsGroupControllerImpl.java @@ -0,0 +1,83 @@ +package org.briarproject.android.privategroup.invitation; + +import org.briarproject.android.controller.handler.ResultExceptionHandler; +import org.briarproject.android.sharing.InvitationsControllerImpl; +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.GroupInvitationReceivedEvent; +import org.briarproject.api.lifecycle.LifecycleManager; +import org.briarproject.api.privategroup.PrivateGroup; +import org.briarproject.api.privategroup.PrivateGroupManager; +import org.briarproject.api.privategroup.invitation.GroupInvitationItem; +import org.briarproject.api.privategroup.invitation.GroupInvitationManager; +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 InvitationsGroupControllerImpl + extends InvitationsControllerImpl<GroupInvitationItem> + implements InvitationsGroupController { + + private final PrivateGroupManager privateGroupManager; + private final GroupInvitationManager groupInvitationManager; + + @Inject + InvitationsGroupControllerImpl(@DatabaseExecutor Executor dbExecutor, + LifecycleManager lifecycleManager, EventBus eventBus, + PrivateGroupManager privateGroupManager, + GroupInvitationManager groupInvitationManager) { + super(dbExecutor, lifecycleManager, eventBus); + this.privateGroupManager = privateGroupManager; + this.groupInvitationManager = groupInvitationManager; + } + + @Override + public void eventOccurred(Event e) { + super.eventOccurred(e); + + if (e instanceof GroupInvitationReceivedEvent) { + LOG.info("Group invitation received, reloading"); + listener.loadInvitations(false); + } + } + + @Override + protected ClientId getClientId() { + return privateGroupManager.getClientId(); + } + + @Override + protected Collection<GroupInvitationItem> getInvitations() + throws DbException { + return groupInvitationManager.getInvitations(); + } + + @Override + public void respondToInvitation(final GroupInvitationItem item, + final boolean accept, + final ResultExceptionHandler<Void, DbException> handler) { + runOnDbThread(new Runnable() { + @Override + public void run() { + try { + PrivateGroup g = (PrivateGroup) item.getShareable(); + Contact c = item.getCreator(); + groupInvitationManager.respondToInvitation(g, 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/privategroup/list/GroupListController.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListController.java index fbe05315d01fcb316fb57ef3f74b95e27f5d107a..f102f82e49dd0641709f6b52a85f59e39a6aecb5 100644 --- a/briar-android/src/org/briarproject/android/privategroup/list/GroupListController.java +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListController.java @@ -30,6 +30,9 @@ public interface GroupListController extends DbController { void removeGroup(GroupId g, ResultExceptionHandler<Void, DbException> result); + void loadAvailableGroups( + ResultExceptionHandler<Integer, DbException> result); + interface GroupListListener extends DestroyableContext { @UiThread diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java index efa36fe5f2079e2bc818e0d0baad8b53e428d4c7..76856f244d627722d468826586a55b92dbc56bb6 100644 --- a/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java @@ -20,6 +20,7 @@ import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.privategroup.GroupMessageHeader; import org.briarproject.api.privategroup.PrivateGroup; import org.briarproject.api.privategroup.PrivateGroupManager; +import org.briarproject.api.privategroup.invitation.GroupInvitationManager; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.GroupId; @@ -41,6 +42,7 @@ public class GroupListControllerImpl extends DbControllerImpl Logger.getLogger(GroupListControllerImpl.class.getName()); private final PrivateGroupManager groupManager; + private final GroupInvitationManager groupInvitationManager; private final EventBus eventBus; private final AndroidNotificationManager notificationManager; private final IdentityManager identityManager; @@ -50,10 +52,12 @@ public class GroupListControllerImpl extends DbControllerImpl @Inject GroupListControllerImpl(@DatabaseExecutor Executor dbExecutor, LifecycleManager lifecycleManager, PrivateGroupManager groupManager, - EventBus eventBus, AndroidNotificationManager notificationManager, + GroupInvitationManager groupInvitationManager, EventBus eventBus, + AndroidNotificationManager notificationManager, IdentityManager identityManager) { super(dbExecutor, lifecycleManager); this.groupManager = groupManager; + this.groupInvitationManager = groupInvitationManager; this.eventBus = eventBus; this.notificationManager = notificationManager; this.identityManager = identityManager; @@ -187,4 +191,22 @@ public class GroupListControllerImpl extends DbControllerImpl }); } + @Override + public void loadAvailableGroups( + final ResultExceptionHandler<Integer, DbException> handler) { + runOnDbThread(new Runnable() { + @Override + public void run() { + try { + handler.onResult( + groupInvitationManager.getInvitations().size()); + } 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/privategroup/list/GroupListFragment.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java index 9e77003acedb1f79f17c76cac2df0309848ed2ad..597e8b120536128eee53d44fe65c0c65c197d045 100644 --- a/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java @@ -4,13 +4,16 @@ import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.annotation.UiThread; +import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityOptionsCompat; +import android.support.v4.content.ContextCompat; import android.support.v7.widget.LinearLayoutManager; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; +import android.view.View.OnClickListener; import android.view.ViewGroup; import org.briarproject.R; @@ -18,6 +21,7 @@ import org.briarproject.android.ActivityComponent; import org.briarproject.android.controller.handler.UiResultExceptionHandler; import org.briarproject.android.fragment.BaseFragment; import org.briarproject.android.privategroup.creation.CreateGroupActivity; +import org.briarproject.android.privategroup.invitation.InvitationsGroupActivity; import org.briarproject.android.privategroup.list.GroupListController.GroupListListener; import org.briarproject.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener; import org.briarproject.android.view.BriarRecyclerView; @@ -30,10 +34,11 @@ import java.util.logging.Logger; import javax.inject.Inject; +import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE; import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation; public class GroupListFragment extends BaseFragment implements - GroupListListener, OnGroupRemoveClickListener { + GroupListListener, OnGroupRemoveClickListener, OnClickListener { public final static String TAG = GroupListFragment.class.getName(); private static final Logger LOG = Logger.getLogger(TAG); @@ -47,6 +52,7 @@ public class GroupListFragment extends BaseFragment implements private BriarRecyclerView list; private GroupListAdapter adapter; + private Snackbar snackbar; @Nullable @Override @@ -61,6 +67,12 @@ public class GroupListFragment extends BaseFragment implements list.setLayoutManager(new LinearLayoutManager(getContext())); list.setAdapter(adapter); + snackbar = Snackbar.make(list, "", LENGTH_INDEFINITE); + snackbar.getView().setBackgroundResource(R.color.briar_primary); + snackbar.setAction(R.string.show, this); + snackbar.setActionTextColor(ContextCompat + .getColor(getContext(), R.color.briar_button_positive)); + return v; } @@ -76,6 +88,7 @@ public class GroupListFragment extends BaseFragment implements controller.onStart(); list.startPeriodicUpdate(); loadGroups(); + loadAvailableGroups(); } @Override @@ -180,4 +193,40 @@ public class GroupListFragment extends BaseFragment implements }); } + private void loadAvailableGroups() { + controller.loadAvailableGroups( + new UiResultExceptionHandler<Integer, DbException>(this) { + @Override + public void onResultUi(Integer num) { + if (num == 0) { + snackbar.dismiss(); + } else { + snackbar.setText(getResources().getQuantityString( + R.plurals.groups_invitations_open, num, + num)); + if (!snackbar.isShownOrQueued()) snackbar.show(); + } + } + + @Override + public void onExceptionUi(DbException exception) { + // TODO handle this error + finish(); + } + }); + } + + /** + * This method is handling the available groups snackbar action + */ + @Override + public void onClick(View v) { + Intent i = new Intent(getContext(), InvitationsGroupActivity.class); + ActivityOptionsCompat options = + makeCustomAnimation(getActivity(), + android.R.anim.slide_in_left, + android.R.anim.slide_out_right); + startActivity(i, options.toBundle()); + } + }