Merge branch 'contact-selector-controller' into 'master'

Add a controller for contact selection lists

See merge request !401
This commit is contained in:
akwizgran
2016-11-11 10:54:07 +00:00
38 changed files with 973 additions and 519 deletions

View File

@@ -33,17 +33,19 @@ import org.briarproject.android.privategroup.creation.CreateGroupActivity;
import org.briarproject.android.privategroup.creation.CreateGroupFragment; import org.briarproject.android.privategroup.creation.CreateGroupFragment;
import org.briarproject.android.privategroup.creation.CreateGroupMessageFragment; import org.briarproject.android.privategroup.creation.CreateGroupMessageFragment;
import org.briarproject.android.privategroup.creation.GroupInviteActivity; import org.briarproject.android.privategroup.creation.GroupInviteActivity;
import org.briarproject.android.privategroup.creation.GroupInviteFragment;
import org.briarproject.android.privategroup.invitation.GroupInvitationActivity; import org.briarproject.android.privategroup.invitation.GroupInvitationActivity;
import org.briarproject.android.privategroup.list.GroupListFragment; import org.briarproject.android.privategroup.list.GroupListFragment;
import org.briarproject.android.privategroup.memberlist.GroupMemberListActivity; import org.briarproject.android.privategroup.memberlist.GroupMemberListActivity;
import org.briarproject.android.sharing.BlogInvitationActivity; import org.briarproject.android.sharing.BlogInvitationActivity;
import org.briarproject.android.sharing.BlogSharingStatusActivity; import org.briarproject.android.sharing.BlogSharingStatusActivity;
import org.briarproject.android.sharing.ContactSelectorFragment;
import org.briarproject.android.sharing.ForumInvitationActivity; import org.briarproject.android.sharing.ForumInvitationActivity;
import org.briarproject.android.sharing.ForumSharingStatusActivity; import org.briarproject.android.sharing.ForumSharingStatusActivity;
import org.briarproject.android.sharing.ShareBlogActivity; import org.briarproject.android.sharing.ShareBlogActivity;
import org.briarproject.android.sharing.ShareBlogFragment;
import org.briarproject.android.sharing.ShareBlogMessageFragment; import org.briarproject.android.sharing.ShareBlogMessageFragment;
import org.briarproject.android.sharing.ShareForumActivity; import org.briarproject.android.sharing.ShareForumActivity;
import org.briarproject.android.sharing.ShareForumFragment;
import org.briarproject.android.sharing.ShareForumMessageFragment; import org.briarproject.android.sharing.ShareForumMessageFragment;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider; import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel; import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel;
@@ -142,6 +144,8 @@ public interface ActivityComponent {
void inject(GroupListFragment fragment); void inject(GroupListFragment fragment);
void inject(GroupInviteFragment fragment);
void inject(ForumListFragment fragment); void inject(ForumListFragment fragment);
void inject(FeedFragment fragment); void inject(FeedFragment fragment);
@@ -152,10 +156,12 @@ public interface ActivityComponent {
void inject(ContactChooserFragment fragment); void inject(ContactChooserFragment fragment);
void inject(ContactSelectorFragment fragment); void inject(ShareForumFragment fragment);
void inject(ShareForumMessageFragment fragment); void inject(ShareForumMessageFragment fragment);
void inject(ShareBlogFragment fragment);
void inject(ShareBlogMessageFragment fragment); void inject(ShareBlogMessageFragment fragment);
void inject(IntroductionMessageFragment fragment); void inject(IntroductionMessageFragment fragment);

View File

@@ -35,6 +35,10 @@ import org.briarproject.android.sharing.BlogInvitationController;
import org.briarproject.android.sharing.BlogInvitationControllerImpl; import org.briarproject.android.sharing.BlogInvitationControllerImpl;
import org.briarproject.android.sharing.ForumInvitationController; import org.briarproject.android.sharing.ForumInvitationController;
import org.briarproject.android.sharing.ForumInvitationControllerImpl; import org.briarproject.android.sharing.ForumInvitationControllerImpl;
import org.briarproject.android.sharing.ShareBlogController;
import org.briarproject.android.sharing.ShareBlogControllerImpl;
import org.briarproject.android.sharing.ShareForumController;
import org.briarproject.android.sharing.ShareForumControllerImpl;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
@@ -148,6 +152,13 @@ public class ActivityModule {
return forumController; return forumController;
} }
@ActivityScope
@Provides
ShareForumController provideShareForumController(
ShareForumControllerImpl shareForumController) {
return shareForumController;
}
@ActivityScope @ActivityScope
@Provides @Provides
protected ForumInvitationController provideInvitationForumController( protected ForumInvitationController provideInvitationForumController(
@@ -171,6 +182,13 @@ public class ActivityModule {
return blogController; return blogController;
} }
@ActivityScope
@Provides
ShareBlogController provideShareBlogController(
ShareBlogControllerImpl shareBlogController) {
return shareBlogController;
}
@ActivityScope @ActivityScope
@Provides @Provides
FeedController provideFeedController(FeedControllerImpl feedController) { FeedController provideFeedController(FeedControllerImpl feedController) {

View File

@@ -2,6 +2,7 @@ package org.briarproject.android;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.view.View; import android.view.View;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
@@ -31,7 +32,7 @@ public abstract class BaseActivity extends AppCompatActivity
} }
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE); if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);

View File

@@ -39,10 +39,8 @@ public abstract class BaseContactListAdapter<I extends ContactItem, VH extends C
} }
@Override @Override
public boolean areContentsTheSame(I c1, I c2) { public boolean areContentsTheSame(ContactItem c1, ContactItem c2) {
// check for all properties that influence visual return true;
// representation of contact
return c1.isConnected() == c2.isConnected();
} }
int findItemPosition(ContactId c) { int findItemPosition(ContactId c) {

View File

@@ -11,23 +11,12 @@ public class ContactItem {
private final Contact contact; private final Contact contact;
private boolean connected; public ContactItem(Contact contact) {
public ContactItem(Contact contact, boolean connected) {
this.contact = contact; this.contact = contact;
this.connected = connected;
} }
public Contact getContact() { public Contact getContact() {
return contact; return contact;
} }
boolean isConnected() {
return connected;
}
void setConnected(boolean connected) {
this.connected = connected;
}
} }

View File

@@ -34,7 +34,7 @@ public class ContactListAdapter extends
if (c1.getTimestamp() != c2.getTimestamp()) { if (c1.getTimestamp() != c2.getTimestamp()) {
return false; return false;
} }
return super.areContentsTheSame(c1, c2); return c1.isConnected() == c2.isConnected();
} }
@Override @Override

View File

@@ -10,13 +10,14 @@ import javax.annotation.concurrent.NotThreadSafe;
@NotNullByDefault @NotNullByDefault
public class ContactListItem extends ContactItem { public class ContactListItem extends ContactItem {
private boolean empty; private boolean connected, empty;
private long timestamp; private long timestamp;
private int unread; private int unread;
public ContactListItem(Contact contact, boolean connected, public ContactListItem(Contact contact, boolean connected,
GroupCount count) { GroupCount count) {
super(contact, connected); super(contact);
this.connected = connected;
this.empty = count.getMsgCount() == 0; this.empty = count.getMsgCount() == 0;
this.unread = count.getUnreadCount(); this.unread = count.getUnreadCount();
this.timestamp = count.getLatestMsgTime(); this.timestamp = count.getLatestMsgTime();
@@ -29,6 +30,14 @@ public class ContactListItem extends ContactItem {
unread++; unread++;
} }
boolean isConnected() {
return connected;
}
void setConnected(boolean connected) {
this.connected = connected;
}
boolean isEmpty() { boolean isEmpty() {
return empty; return empty;
} }

View File

@@ -0,0 +1,32 @@
package org.briarproject.android.contactselection;
import android.content.Context;
import org.briarproject.android.contact.BaseContactListAdapter;
import org.briarproject.android.contact.ContactItemViewHolder;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.nullsafety.NotNullByDefault;
import java.util.ArrayList;
import java.util.Collection;
@NotNullByDefault
public abstract class BaseContactSelectorAdapter<I extends SelectableContactItem, H extends ContactItemViewHolder<I>>
extends BaseContactListAdapter<I, H> {
BaseContactSelectorAdapter(Context context, Class<I> c,
OnContactClickListener<I> listener) {
super(context, c, listener);
}
Collection<ContactId> getSelectedContactIds() {
Collection<ContactId> selected = new ArrayList<>();
for (int i = 0; i < items.size(); i++) {
SelectableContactItem item = items.get(i);
if (item.isSelected()) selected.add(item.getContact().getId());
}
return selected;
}
}

View File

@@ -0,0 +1,143 @@
package org.briarproject.android.contactselection;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.transition.Fade;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.R;
import org.briarproject.android.contact.BaseContactListAdapter.OnContactClickListener;
import org.briarproject.android.contact.ContactItemViewHolder;
import org.briarproject.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.android.view.BriarRecyclerView;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.api.sync.GroupId;
import java.util.ArrayList;
import java.util.Collection;
import static org.briarproject.android.contactselection.ContactSelectorActivity.CONTACTS;
import static org.briarproject.android.contactselection.ContactSelectorActivity.getContactsFromIds;
import static org.briarproject.android.contactselection.ContactSelectorActivity.getContactsFromIntegers;
import static org.briarproject.api.sharing.SharingConstants.GROUP_ID;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public abstract class BaseContactSelectorFragment<I extends SelectableContactItem, H extends ContactItemViewHolder<I>>
extends BaseFragment
implements OnContactClickListener<I> {
protected BriarRecyclerView list;
protected BaseContactSelectorAdapter<I, H> adapter;
protected Collection<ContactId> selectedContacts = new ArrayList<>();
protected ContactSelectorListener<I> listener;
private GroupId groupId;
@Override
public void onAttach(Context context) {
super.onAttach(context);
listener = (ContactSelectorListener<I>) context;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
byte[] b = args.getByteArray(GROUP_ID);
if (b == null) throw new IllegalStateException("No GroupId");
groupId = new GroupId(b);
}
@Override
@CallSuper
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View contentView = inflater.inflate(R.layout.list, container, false);
if (Build.VERSION.SDK_INT >= 21) {
setExitTransition(new Fade());
}
list = (BriarRecyclerView) contentView.findViewById(R.id.list);
list.setLayoutManager(new LinearLayoutManager(getActivity()));
list.setEmptyText(getString(R.string.no_contacts_selector));
// restore selected contacts if available
if (savedInstanceState != null) {
ArrayList<Integer> intContacts =
savedInstanceState.getIntegerArrayList(CONTACTS);
if (intContacts != null) {
selectedContacts = getContactsFromIntegers(intContacts);
}
}
return contentView;
}
@Override
public void onStart() {
super.onStart();
loadContacts(selectedContacts);
}
@Override
public void onStop() {
super.onStop();
adapter.clear();
list.showProgressBar();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (adapter != null) {
selectedContacts = adapter.getSelectedContactIds();
outState.putIntegerArrayList(CONTACTS,
getContactsFromIds(selectedContacts));
}
}
@Override
public void onItemClick(View view, I item) {
item.toggleSelected();
adapter.notifyItemChanged(adapter.findItemPosition(item), item);
onSelectionChanged();
}
private void loadContacts(final Collection<ContactId> selection) {
getController().loadContacts(groupId, selection,
new UiResultExceptionHandler<Collection<I>, DbException>(
this) {
@Override
public void onResultUi(Collection<I> contacts) {
if (contacts.isEmpty()) list.showData();
else adapter.addAll(contacts);
onSelectionChanged();
}
@Override
public void onExceptionUi(DbException exception) {
// TODO error handling
finish();
}
});
}
protected abstract void onSelectionChanged();
protected abstract ContactSelectorController<I> getController();
}

View File

@@ -1,34 +1,45 @@
package org.briarproject.android.sharing; package org.briarproject.android.contactselection;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.CallSuper; import android.support.annotation.CallSuper;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.android.BriarActivity; import org.briarproject.android.BriarActivity;
import org.briarproject.android.fragment.BaseFragment.BaseFragmentListener; import org.briarproject.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
public abstract class ContactSelectorActivity extends BriarActivity implements @MethodsNotNullByDefault
BaseFragmentListener, ContactSelectorListener { @ParametersNotNullByDefault
public abstract class ContactSelectorActivity<I extends SelectableContactItem>
extends BriarActivity
implements BaseFragmentListener, ContactSelectorListener<I> {
final static String CONTACTS = "contacts"; final static String CONTACTS = "contacts";
// Subclasses may initialise the group ID in different places
protected GroupId groupId; protected GroupId groupId;
protected Collection<ContactId> contacts; protected Collection<ContactId> contacts;
@Override @Override
public void onCreate(Bundle bundle) { public void onCreate(@Nullable Bundle bundle) {
super.onCreate(bundle); super.onCreate(bundle);
setContentView(R.layout.activity_fragment_container); setContentView(R.layout.activity_fragment_container);
if (bundle != null) { if (bundle != null) {
// restore group ID if it was saved
byte[] groupBytes = bundle.getByteArray(GROUP_ID);
if (groupBytes != null) groupId = new GroupId(groupBytes);
// restore selected contacts if a selection was saved
ArrayList<Integer> intContacts = ArrayList<Integer> intContacts =
bundle.getIntegerArrayList(CONTACTS); bundle.getIntegerArrayList(CONTACTS);
if (intContacts != null) { if (intContacts != null) {
@@ -40,6 +51,10 @@ public abstract class ContactSelectorActivity extends BriarActivity implements
@Override @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
if (groupId != null) {
// save the group ID here regardless of how subclasses initialize it
outState.putByteArray(GROUP_ID, groupId.getBytes());
}
if (contacts != null) { if (contacts != null) {
outState.putIntegerArrayList(CONTACTS, outState.putIntegerArrayList(CONTACTS,
getContactsFromIds(contacts)); getContactsFromIds(contacts));
@@ -49,9 +64,7 @@ public abstract class ContactSelectorActivity extends BriarActivity implements
@CallSuper @CallSuper
@UiThread @UiThread
@Override @Override
public void contactsSelected(GroupId groupId, public void contactsSelected(Collection<ContactId> contacts) {
Collection<ContactId> contacts) {
this.groupId = groupId;
this.contacts = contacts; this.contacts = contacts;
} }

View File

@@ -0,0 +1,28 @@
package org.briarproject.android.contactselection;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.R;
import org.briarproject.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public class ContactSelectorAdapter extends
BaseContactSelectorAdapter<SelectableContactItem, SelectableContactHolder> {
ContactSelectorAdapter(Context context,
OnContactClickListener<SelectableContactItem> listener) {
super(context, SelectableContactItem.class, listener);
}
@Override
public SelectableContactHolder onCreateViewHolder(ViewGroup viewGroup,
int i) {
View v = LayoutInflater.from(ctx).inflate(
R.layout.list_item_selectable_contact, viewGroup, false);
return new SelectableContactHolder(v);
}
}

View File

@@ -0,0 +1,19 @@
package org.briarproject.android.contactselection;
import org.briarproject.android.controller.DbController;
import org.briarproject.android.controller.handler.ResultExceptionHandler;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
@NotNullByDefault
public interface ContactSelectorController<I extends SelectableContactItem>
extends DbController {
void loadContacts(GroupId g, Collection<ContactId> selection,
ResultExceptionHandler<Collection<I>, DbException> handler);
}

View File

@@ -0,0 +1,77 @@
package org.briarproject.android.contactselection;
import org.briarproject.android.controller.DbControllerImpl;
import org.briarproject.android.controller.handler.ResultExceptionHandler;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.sync.GroupId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import static java.util.logging.Level.WARNING;
@Immutable
@NotNullByDefault
public abstract class ContactSelectorControllerImpl<I extends SelectableContactItem>
extends DbControllerImpl
implements ContactSelectorController<I> {
private static final Logger LOG =
Logger.getLogger(ContactSelectorControllerImpl.class.getName());
private final ContactManager contactManager;
public ContactSelectorControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager) {
super(dbExecutor, lifecycleManager);
this.contactManager = contactManager;
}
@Override
public void loadContacts(final GroupId g,
final Collection<ContactId> selection,
final ResultExceptionHandler<Collection<I>, DbException> handler) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
Collection<I> contacts = new ArrayList<>();
for (Contact c : contactManager.getActiveContacts()) {
// was this contact already selected?
boolean selected =
isSelected(c, selection.contains(c.getId()));
// can this contact be selected?
boolean disabled = isDisabled(g, c);
contacts.add(getItem(c, selected, disabled));
}
handler.onResult(contacts);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
handler.onException(e);
}
}
});
}
@DatabaseExecutor
protected abstract boolean isSelected(Contact c, boolean wasSelected)
throws DbException;
@DatabaseExecutor
protected abstract boolean isDisabled(GroupId g, Contact c)
throws DbException;
protected abstract I getItem(Contact c, boolean selected, boolean disabled);
}

View File

@@ -0,0 +1,73 @@
package org.briarproject.android.contactselection;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.R;
import org.briarproject.android.contact.BaseContactListAdapter.OnContactClickListener;
import org.briarproject.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.api.nullsafety.ParametersNotNullByDefault;
import org.jetbrains.annotations.Nullable;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public abstract class ContactSelectorFragment extends
BaseContactSelectorFragment<SelectableContactItem, SelectableContactHolder>
implements OnContactClickListener<SelectableContactItem> {
public static final String TAG = ContactSelectorFragment.class.getName();
private Menu menu;
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View contentView =
super.onCreateView(inflater, container, savedInstanceState);
adapter = new ContactSelectorAdapter(getActivity(), this);
list.setAdapter(adapter);
return contentView;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.contact_selection_actions, menu);
super.onCreateOptionsMenu(menu, inflater);
this.menu = menu;
// hide sharing action initially, if no contact is selected
onSelectionChanged();
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.action_contacts_selected:
selectedContacts = adapter.getSelectedContactIds();
listener.contactsSelected(selectedContacts);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
protected void onSelectionChanged() {
if (menu == null) return;
MenuItem item = menu.findItem(R.id.action_contacts_selected);
if (item == null) return;
selectedContacts = adapter.getSelectedContactIds();
if (selectedContacts.size() > 0) {
item.setVisible(true);
} else {
item.setVisible(false);
}
}
}

View File

@@ -0,0 +1,18 @@
package org.briarproject.android.contactselection;
import android.support.annotation.UiThread;
import org.briarproject.android.DestroyableContext;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.nullsafety.NotNullByDefault;
import java.util.Collection;
@NotNullByDefault
interface ContactSelectorListener<I extends SelectableContactItem>
extends DestroyableContext {
@UiThread
void contactsSelected(Collection<ContactId> contacts);
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.android.sharing; package org.briarproject.android.contactselection;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.view.View; import android.view.View;
@@ -16,7 +16,7 @@ import static android.view.View.VISIBLE;
@UiThread @UiThread
@NotNullByDefault @NotNullByDefault
class SelectableContactHolder public class SelectableContactHolder
extends ContactItemViewHolder<SelectableContactItem> { extends ContactItemViewHolder<SelectableContactItem> {
private final CheckBox checkBox; private final CheckBox checkBox;

View File

@@ -1,4 +1,4 @@
package org.briarproject.android.sharing; package org.briarproject.android.contactselection;
import org.briarproject.android.contact.ContactItem; import org.briarproject.android.contact.ContactItem;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
@@ -8,13 +8,13 @@ import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe @NotThreadSafe
@NotNullByDefault @NotNullByDefault
class SelectableContactItem extends ContactItem { public class SelectableContactItem extends ContactItem {
private boolean selected, disabled; private boolean selected, disabled;
SelectableContactItem(Contact contact, boolean connected, public SelectableContactItem(Contact contact, boolean selected,
boolean selected, boolean disabled) { boolean disabled) {
super(contact, connected); super(contact);
this.selected = selected; this.selected = selected;
this.disabled = disabled; this.disabled = disabled;
} }

View File

@@ -1,14 +1,15 @@
package org.briarproject.android.privategroup.creation; package org.briarproject.android.privategroup.creation;
import android.os.Bundle;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.android.contactselection.ContactSelectorActivity;
import org.briarproject.android.contactselection.ContactSelectorController;
import org.briarproject.android.contactselection.SelectableContactItem;
import org.briarproject.android.controller.handler.UiResultExceptionHandler; import org.briarproject.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.android.sharing.BaseMessageFragment.MessageFragmentListener; import org.briarproject.android.sharing.BaseMessageFragment.MessageFragmentListener;
import org.briarproject.android.sharing.ContactSelectorActivity;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.api.nullsafety.ParametersNotNullByDefault;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Collection; import java.util.Collection;
@@ -17,37 +18,18 @@ import javax.inject.Inject;
import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_MSG_LENGTH; import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_MSG_LENGTH;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public abstract class BaseGroupInviteActivity public abstract class BaseGroupInviteActivity
extends ContactSelectorActivity extends ContactSelectorActivity<SelectableContactItem>
implements MessageFragmentListener { implements MessageFragmentListener {
@Inject @Inject
CreateGroupController controller; CreateGroupController controller;
@Override @Override
public void onCreate(Bundle bundle) { public void contactsSelected(Collection<ContactId> contacts) {
super.onCreate(bundle); super.contactsSelected(contacts);
// Subclasses may initialise the group ID in different places,
// restore it if it was saved
if (bundle != null) {
byte[] groupBytes = bundle.getByteArray(GROUP_ID);
if (groupBytes != null) groupId = new GroupId(groupBytes);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (groupId != null) {
outState.putByteArray(GROUP_ID, groupId.getBytes());
}
}
@Override
public void contactsSelected(GroupId groupId,
Collection<ContactId> contacts) {
super.contactsSelected(groupId, contacts);
CreateGroupMessageFragment fragment = new CreateGroupMessageFragment(); CreateGroupMessageFragment fragment = new CreateGroupMessageFragment();
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
@@ -62,6 +44,8 @@ public abstract class BaseGroupInviteActivity
@Override @Override
public boolean onButtonClick(@NotNull String message) { public boolean onButtonClick(@NotNull String message) {
if (groupId == null)
throw new IllegalStateException("GroupId was not initialized");
controller.sendInvitation(groupId, contacts, message, controller.sendInvitation(groupId, contacts, message,
new UiResultExceptionHandler<Void, DbException>(this) { new UiResultExceptionHandler<Void, DbException>(this) {
@Override @Override

View File

@@ -7,12 +7,10 @@ import android.support.v4.app.ActivityOptionsCompat;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.android.ActivityComponent; import org.briarproject.android.ActivityComponent;
import org.briarproject.android.contactselection.ContactSelectorFragment;
import org.briarproject.android.controller.handler.UiResultExceptionHandler; import org.briarproject.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.android.privategroup.conversation.GroupActivity; import org.briarproject.android.privategroup.conversation.GroupActivity;
import org.briarproject.android.sharing.BaseMessageFragment.MessageFragmentListener; import org.briarproject.android.sharing.BaseMessageFragment.MessageFragmentListener;
import org.briarproject.android.sharing.ContactSelectorFragment;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
@@ -38,13 +36,6 @@ public class CreateGroupActivity extends BaseGroupInviteActivity implements
} }
} }
@Override
@DatabaseExecutor
public boolean isDisabled(GroupId groupId, Contact c) throws DbException {
// contacts can always be invited into a new group
return false;
}
@Override @Override
public void onBackPressed() { public void onBackPressed() {
if (getSupportFragmentManager().getBackStackEntryCount() == 1) { if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
@@ -76,8 +67,8 @@ public class CreateGroupActivity extends BaseGroupInviteActivity implements
private void switchToContactSelectorFragment(GroupId g) { private void switchToContactSelectorFragment(GroupId g) {
setTitle(R.string.groups_invite_members); setTitle(R.string.groups_invite_members);
ContactSelectorFragment fragment = GroupInviteFragment fragment =
ContactSelectorFragment.newInstance(g); GroupInviteFragment.newInstance(g);
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.setCustomAnimations(android.R.anim.fade_in, .setCustomAnimations(android.R.anim.fade_in,
android.R.anim.fade_out, android.R.anim.fade_out,

View File

@@ -1,6 +1,7 @@
package org.briarproject.android.privategroup.creation; package org.briarproject.android.privategroup.creation;
import org.briarproject.android.controller.DbController; import org.briarproject.android.contactselection.ContactSelectorController;
import org.briarproject.android.contactselection.SelectableContactItem;
import org.briarproject.android.controller.handler.ResultExceptionHandler; import org.briarproject.android.controller.handler.ResultExceptionHandler;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
@@ -10,12 +11,13 @@ import org.briarproject.api.sync.GroupId;
import java.util.Collection; import java.util.Collection;
@NotNullByDefault @NotNullByDefault
public interface CreateGroupController extends DbController { public interface CreateGroupController
extends ContactSelectorController<SelectableContactItem> {
void createGroup(String name, void createGroup(String name,
ResultExceptionHandler<GroupId, DbException> result); ResultExceptionHandler<GroupId, DbException> result);
void sendInvitation(GroupId groupId, Collection<ContactId> contacts, void sendInvitation(GroupId g, Collection<ContactId> contacts,
String message, ResultExceptionHandler<Void, DbException> result); String message, ResultExceptionHandler<Void, DbException> result);
} }

View File

@@ -1,6 +1,7 @@
package org.briarproject.android.privategroup.creation; package org.briarproject.android.privategroup.creation;
import org.briarproject.android.controller.DbControllerImpl; import org.briarproject.android.contactselection.ContactSelectorControllerImpl;
import org.briarproject.android.contactselection.SelectableContactItem;
import org.briarproject.android.controller.handler.ResultExceptionHandler; import org.briarproject.android.controller.handler.ResultExceptionHandler;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
@@ -36,7 +37,8 @@ import static java.util.logging.Level.WARNING;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class CreateGroupControllerImpl extends DbControllerImpl public class CreateGroupControllerImpl
extends ContactSelectorControllerImpl<SelectableContactItem>
implements CreateGroupController { implements CreateGroupController {
private static final Logger LOG = private static final Logger LOG =
@@ -61,7 +63,7 @@ public class CreateGroupControllerImpl extends DbControllerImpl
PrivateGroupManager groupManager, PrivateGroupManager groupManager,
GroupInvitationFactory groupInvitationFactory, GroupInvitationFactory groupInvitationFactory,
GroupInvitationManager groupInvitationManager, Clock clock) { GroupInvitationManager groupInvitationManager, Clock clock) {
super(dbExecutor, lifecycleManager); super(dbExecutor, lifecycleManager, contactManager);
this.cryptoExecutor = cryptoExecutor; this.cryptoExecutor = cryptoExecutor;
this.contactManager = contactManager; this.contactManager = contactManager;
this.identityManager = identityManager; this.identityManager = identityManager;
@@ -128,6 +130,23 @@ public class CreateGroupControllerImpl extends DbControllerImpl
}); });
} }
@Override
protected boolean isSelected(Contact c, boolean wasSelected)
throws DbException {
return wasSelected;
}
@Override
protected boolean isDisabled(GroupId g, Contact c) throws DbException {
return !groupInvitationManager.isInvitationAllowed(c, g);
}
@Override
protected SelectableContactItem getItem(Contact c, boolean selected,
boolean disabled) {
return new SelectableContactItem(c, selected, disabled);
}
@Override @Override
public void sendInvitation(final GroupId g, public void sendInvitation(final GroupId g,
final Collection<ContactId> contactIds, final String message, final Collection<ContactId> contactIds, final String message,
@@ -191,6 +210,7 @@ public class CreateGroupControllerImpl extends DbControllerImpl
// Continue // Continue
} }
} }
//noinspection ConstantConditions
handler.onResult(null); handler.onResult(null);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))

View File

@@ -5,22 +5,13 @@ import android.os.Bundle;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.android.ActivityComponent; import org.briarproject.android.ActivityComponent;
import org.briarproject.android.contactselection.ContactSelectorFragment;
import org.briarproject.android.sharing.BaseMessageFragment.MessageFragmentListener; import org.briarproject.android.sharing.BaseMessageFragment.MessageFragmentListener;
import org.briarproject.android.sharing.ContactSelectorFragment;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException;
import org.briarproject.api.privategroup.invitation.GroupInvitationManager;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import javax.inject.Inject;
public class GroupInviteActivity extends BaseGroupInviteActivity public class GroupInviteActivity extends BaseGroupInviteActivity
implements MessageFragmentListener { implements MessageFragmentListener {
@Inject
GroupInvitationManager groupInvitationManager;
@Override @Override
public void injectActivity(ActivityComponent component) { public void injectActivity(ActivityComponent component) {
component.inject(this); component.inject(this);
@@ -30,26 +21,18 @@ public class GroupInviteActivity extends BaseGroupInviteActivity
public void onCreate(Bundle bundle) { public void onCreate(Bundle bundle) {
super.onCreate(bundle); super.onCreate(bundle);
// Initialise the group ID,
// it will be saved and restored by the superclass
Intent i = getIntent(); Intent i = getIntent();
byte[] g = i.getByteArrayExtra(GROUP_ID); byte[] g = i.getByteArrayExtra(GROUP_ID);
if (g == null) throw new IllegalStateException("No GroupId in intent."); if (g == null) throw new IllegalStateException("No GroupId in intent.");
groupId = new GroupId(g); groupId = new GroupId(g);
if (bundle == null) { if (bundle == null) {
ContactSelectorFragment fragment = GroupInviteFragment fragment =
ContactSelectorFragment.newInstance(groupId); GroupInviteFragment.newInstance(groupId);
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, fragment) .replace(R.id.fragmentContainer, fragment)
.commit(); .commit();
} }
} }
@Override
@DatabaseExecutor
public boolean isDisabled(GroupId g, Contact c) throws DbException {
return !groupInvitationManager.isInvitationAllowed(c, g);
}
} }

View File

@@ -0,0 +1,49 @@
package org.briarproject.android.privategroup.creation;
import android.os.Bundle;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.contactselection.ContactSelectorController;
import org.briarproject.android.contactselection.ContactSelectorFragment;
import org.briarproject.android.contactselection.SelectableContactItem;
import org.briarproject.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.api.sync.GroupId;
import javax.inject.Inject;
import static org.briarproject.api.sharing.SharingConstants.GROUP_ID;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class GroupInviteFragment extends ContactSelectorFragment {
public static final String TAG = GroupInviteFragment.class.getName();
@Inject
CreateGroupController controller;
public static GroupInviteFragment newInstance(GroupId groupId) {
Bundle args = new Bundle();
args.putByteArray(GROUP_ID, groupId.getBytes());
GroupInviteFragment fragment = new GroupInviteFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
protected ContactSelectorController<SelectableContactItem> getController() {
return controller;
}
@Override
public String getUniqueTag() {
return TAG;
}
}

View File

@@ -2,7 +2,6 @@ package org.briarproject.android.sharing;
import org.briarproject.android.controller.handler.ResultExceptionHandler; import org.briarproject.android.controller.handler.ResultExceptionHandler;
import org.briarproject.api.blogs.Blog; import org.briarproject.api.blogs.Blog;
import org.briarproject.api.blogs.BlogManager;
import org.briarproject.api.blogs.BlogSharingManager; import org.briarproject.api.blogs.BlogSharingManager;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.db.DatabaseExecutor;
@@ -22,19 +21,18 @@ import javax.inject.Inject;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.api.blogs.BlogManager.CLIENT_ID; import static org.briarproject.api.blogs.BlogManager.CLIENT_ID;
public class BlogInvitationControllerImpl public class BlogInvitationControllerImpl
extends InvitationControllerImpl<SharingInvitationItem> extends InvitationControllerImpl<SharingInvitationItem>
implements BlogInvitationController { implements BlogInvitationController {
private final BlogManager blogManager;
private final BlogSharingManager blogSharingManager; private final BlogSharingManager blogSharingManager;
@Inject @Inject
BlogInvitationControllerImpl(@DatabaseExecutor Executor dbExecutor, BlogInvitationControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, EventBus eventBus, LifecycleManager lifecycleManager, EventBus eventBus,
BlogManager blogManager, BlogSharingManager blogSharingManager) { BlogSharingManager blogSharingManager) {
super(dbExecutor, lifecycleManager, eventBus); super(dbExecutor, lifecycleManager, eventBus);
this.blogManager = blogManager;
this.blogSharingManager = blogSharingManager; this.blogSharingManager = blogSharingManager;
} }

View File

@@ -1,41 +0,0 @@
package org.briarproject.android.sharing;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.R;
import org.briarproject.android.contact.BaseContactListAdapter;
import org.briarproject.api.contact.ContactId;
import java.util.ArrayList;
import java.util.Collection;
class ContactSelectorAdapter extends
BaseContactListAdapter<SelectableContactItem, SelectableContactHolder> {
ContactSelectorAdapter(Context context,
OnContactClickListener<SelectableContactItem> listener) {
super(context, SelectableContactItem.class, listener);
}
@Override
public SelectableContactHolder onCreateViewHolder(ViewGroup viewGroup,
int i) {
View v = LayoutInflater.from(ctx).inflate(
R.layout.list_item_selectable_contact, viewGroup, false);
return new SelectableContactHolder(v);
}
Collection<ContactId> getSelectedContactIds() {
Collection<ContactId> selected = new ArrayList<>();
for (int i = 0; i < items.size(); i++) {
SelectableContactItem item = items.get(i);
if (item.isSelected()) selected.add(item.getContact().getId());
}
return selected;
}
}

View File

@@ -1,233 +0,0 @@
package org.briarproject.android.sharing;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager;
import android.transition.Fade;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.contact.BaseContactListAdapter.OnContactClickListener;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.android.view.BriarRecyclerView;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.db.DbException;
import org.briarproject.api.plugins.ConnectionRegistry;
import org.briarproject.api.sync.GroupId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.sharing.ShareActivity.CONTACTS;
import static org.briarproject.android.sharing.ShareActivity.getContactsFromIds;
import static org.briarproject.android.sharing.ShareActivity.getContactsFromIntegers;
import static org.briarproject.api.sharing.SharingConstants.GROUP_ID;
public class ContactSelectorFragment extends BaseFragment
implements OnContactClickListener<SelectableContactItem> {
public static final String TAG = ContactSelectorFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
private Menu menu;
private BriarRecyclerView list;
private ContactSelectorAdapter adapter;
private Collection<ContactId> selectedContacts;
// Fields that are accessed from background threads must be volatile
@Inject
volatile ContactManager contactManager;
@Inject
volatile ConnectionRegistry connectionRegistry;
private volatile GroupId groupId;
private volatile ContactSelectorListener listener;
public static ContactSelectorFragment newInstance(GroupId groupId) {
Bundle args = new Bundle();
args.putByteArray(GROUP_ID, groupId.getBytes());
ContactSelectorFragment fragment = new ContactSelectorFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
listener = (ContactSelectorListener) context;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
byte[] b = args.getByteArray(GROUP_ID);
if (b == null) throw new IllegalStateException("No GroupId");
groupId = new GroupId(b);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View contentView = inflater.inflate(R.layout.list, container, false);
if (Build.VERSION.SDK_INT >= 21) {
setExitTransition(new Fade());
}
adapter = new ContactSelectorAdapter(getActivity(), this);
list = (BriarRecyclerView) contentView.findViewById(R.id.list);
list.setLayoutManager(new LinearLayoutManager(getActivity()));
list.setAdapter(adapter);
list.setEmptyText(getString(R.string.no_contacts_selector));
// restore selected contacts if available
if (savedInstanceState != null) {
ArrayList<Integer> intContacts =
savedInstanceState.getIntegerArrayList(CONTACTS);
if (intContacts != null) {
selectedContacts = getContactsFromIntegers(intContacts);
}
}
return contentView;
}
@Override
public void onStart() {
super.onStart();
loadContacts(selectedContacts);
}
@Override
public void onStop() {
super.onStop();
adapter.clear();
list.showProgressBar();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (adapter != null) {
selectedContacts = adapter.getSelectedContactIds();
outState.putIntegerArrayList(CONTACTS,
getContactsFromIds(selectedContacts));
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.contact_selection_actions, menu);
super.onCreateOptionsMenu(menu, inflater);
this.menu = menu;
// hide sharing action initially, if no contact is selected
updateMenuItem();
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_contacts_selected:
selectedContacts = adapter.getSelectedContactIds();
listener.contactsSelected(groupId, selectedContacts);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void onItemClick(View view, SelectableContactItem item) {
item.toggleSelected();
adapter.notifyItemChanged(adapter.findItemPosition(item), item);
updateMenuItem();
}
private void loadContacts(@Nullable final Collection<ContactId> selection) {
listener.runOnDbThread(new Runnable() {
@Override
public void run() {
try {
long now = System.currentTimeMillis();
List<SelectableContactItem> contacts =
new ArrayList<>();
for (Contact c : contactManager.getActiveContacts()) {
// is this contact online?
boolean connected =
connectionRegistry.isConnected(c.getId());
// was this contact already selected?
boolean selected = selection != null &&
selection.contains(c.getId());
// do we have already some sharing with that contact?
boolean disabled = listener.isDisabled(groupId, c);
contacts.add(new SelectableContactItem(c, connected,
selected, disabled));
}
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Load took " + duration + " ms");
displayContacts(contacts);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void displayContacts(
final List<SelectableContactItem> contacts) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override
public void run() {
if (contacts.isEmpty()) list.showData();
else adapter.addAll(contacts);
updateMenuItem();
}
});
}
private void updateMenuItem() {
if (menu == null) return;
MenuItem item = menu.findItem(R.id.action_contacts_selected);
if (item == null) return;
selectedContacts = adapter.getSelectedContactIds();
if (selectedContacts.size() > 0) {
item.setVisible(true);
} else {
item.setVisible(false);
}
}
}

View File

@@ -1,28 +0,0 @@
package org.briarproject.android.sharing;
import android.support.annotation.UiThread;
import org.briarproject.android.DestroyableContext;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
interface ContactSelectorListener extends DestroyableContext {
@Deprecated
void runOnDbThread(Runnable runnable);
@DatabaseExecutor
boolean isDisabled(GroupId groupId, Contact c) throws DbException;
@UiThread
void contactsSelected(GroupId groupId, Collection<ContactId> contacts);
@UiThread
void onBackPressed();
}

View File

@@ -8,7 +8,6 @@ import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.ForumInvitationReceivedEvent; import org.briarproject.api.event.ForumInvitationReceivedEvent;
import org.briarproject.api.forum.Forum; import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumSharingManager; import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.sharing.SharingInvitationItem; import org.briarproject.api.sharing.SharingInvitationItem;
@@ -26,16 +25,13 @@ public class ForumInvitationControllerImpl
extends InvitationControllerImpl<SharingInvitationItem> extends InvitationControllerImpl<SharingInvitationItem>
implements ForumInvitationController { implements ForumInvitationController {
private final ForumManager forumManager;
private final ForumSharingManager forumSharingManager; private final ForumSharingManager forumSharingManager;
@Inject @Inject
ForumInvitationControllerImpl(@DatabaseExecutor Executor dbExecutor, ForumInvitationControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, EventBus eventBus, LifecycleManager lifecycleManager, EventBus eventBus,
ForumManager forumManager,
ForumSharingManager forumSharingManager) { ForumSharingManager forumSharingManager) {
super(dbExecutor, lifecycleManager, eventBus); super(dbExecutor, lifecycleManager, eventBus);
this.forumManager = forumManager;
this.forumSharingManager = forumSharingManager; this.forumSharingManager = forumSharingManager;
} }

View File

@@ -2,53 +2,42 @@ package org.briarproject.android.sharing;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.StringRes;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.widget.Toast;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.android.contactselection.ContactSelectorActivity;
import org.briarproject.android.contactselection.ContactSelectorFragment;
import org.briarproject.android.contactselection.SelectableContactItem;
import org.briarproject.android.sharing.BaseMessageFragment.MessageFragmentListener; import org.briarproject.android.sharing.BaseMessageFragment.MessageFragmentListener;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.api.db.DbException; import org.briarproject.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection; import java.util.Collection;
import java.util.logging.Logger;
import static android.widget.Toast.LENGTH_SHORT; @MethodsNotNullByDefault
import static java.util.logging.Level.WARNING; @ParametersNotNullByDefault
public abstract class ShareActivity
public abstract class ShareActivity extends ContactSelectorActivity implements extends ContactSelectorActivity<SelectableContactItem>
MessageFragmentListener { implements MessageFragmentListener {
private final static Logger LOG =
Logger.getLogger(ShareActivity.class.getName());
@Override @Override
public void onCreate(Bundle bundle) { public void onCreate(@Nullable Bundle bundle) {
super.onCreate(bundle); super.onCreate(bundle);
Intent i = getIntent(); Intent i = getIntent();
byte[] b = i.getByteArrayExtra(GROUP_ID); byte[] b = i.getByteArrayExtra(GROUP_ID);
if (b == null) throw new IllegalStateException("No GroupId"); if (b == null) throw new IllegalStateException("No GroupId");
groupId = new GroupId(b); groupId = new GroupId(b);
if (bundle == null) {
ContactSelectorFragment contactSelectorFragment =
ContactSelectorFragment.newInstance(groupId);
getSupportFragmentManager().beginTransaction()
.add(R.id.fragmentContainer, contactSelectorFragment)
.commit();
}
} }
@UiThread @UiThread
@Override @Override
public void contactsSelected(GroupId groupId, public void contactsSelected(Collection<ContactId> contacts) {
Collection<ContactId> contacts) { super.contactsSelected(contacts);
super.contactsSelected(groupId, contacts);
BaseMessageFragment messageFragment = getMessageFragment(); BaseMessageFragment messageFragment = getMessageFragment();
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
@@ -67,45 +56,12 @@ public abstract class ShareActivity extends ContactSelectorActivity implements
@UiThread @UiThread
@Override @Override
public boolean onButtonClick(@NotNull String message) { public boolean onButtonClick(@NotNull String message) {
share(groupId, contacts, message); share(contacts, message);
setResult(RESULT_OK); setResult(RESULT_OK);
supportFinishAfterTransition(); supportFinishAfterTransition();
return true; return true;
} }
private void share(final GroupId g, final Collection<ContactId> contacts, abstract void share(Collection<ContactId> contacts, String msg);
final String msg) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
for (ContactId c : contacts) {
share(g, c, msg);
}
} catch (DbException e) {
// TODO proper error handling
sharingError();
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
@DatabaseExecutor
protected abstract void share(GroupId g, ContactId c, String msg)
throws DbException;
private void sharingError() {
runOnUiThreadUnlessDestroyed(new Runnable() {
@Override
public void run() {
int res = getSharingError();
Toast.makeText(ShareActivity.this, res, LENGTH_SHORT).show();
}
});
}
protected abstract @StringRes int getSharingError();
} }

View File

@@ -1,22 +1,30 @@
package org.briarproject.android.sharing; package org.briarproject.android.sharing;
import android.os.Bundle;
import android.widget.Toast;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.android.ActivityComponent; import org.briarproject.android.ActivityComponent;
import org.briarproject.api.blogs.BlogSharingManager; import org.briarproject.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.api.nullsafety.ParametersNotNullByDefault;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import javax.inject.Inject; import javax.inject.Inject;
import static android.widget.Toast.LENGTH_SHORT;
import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ShareBlogActivity extends ShareActivity { public class ShareBlogActivity extends ShareActivity {
// Fields that are accessed from background threads must be volatile
@Inject @Inject
volatile BlogSharingManager blogSharingManager; ShareBlogController controller;
@Override @Override
BaseMessageFragment getMessageFragment() { BaseMessageFragment getMessageFragment() {
@@ -29,23 +37,40 @@ public class ShareBlogActivity extends ShareActivity {
} }
@Override @Override
public boolean isDisabled(GroupId groupId, Contact c) throws DbException { public void onCreate(@Nullable Bundle bundle) {
return !blogSharingManager.canBeShared(groupId, c); super.onCreate(bundle);
}
@Override if (bundle == null) {
protected void share(GroupId g, ContactId c, String msg) ShareBlogFragment fragment = ShareBlogFragment.newInstance(groupId);
throws DbException { getSupportFragmentManager().beginTransaction()
blogSharingManager.sendInvitation(g, c, msg); .add(R.id.fragmentContainer, fragment)
} .commit();
}
@Override
protected int getSharingError() {
return R.string.blogs_sharing_error;
} }
@Override @Override
public int getMaximumMessageLength() { public int getMaximumMessageLength() {
return MAX_MESSAGE_BODY_LENGTH; return MAX_MESSAGE_BODY_LENGTH;
} }
@Override
void share(Collection<ContactId> contacts, String msg) {
controller.share(groupId, contacts, msg,
new UiResultExceptionHandler<Void, DbException>(this) {
@Override
public void onResultUi(Void result) {
}
@Override
public void onExceptionUi(DbException exception) {
// TODO proper error handling
Toast.makeText(ShareBlogActivity.this,
R.string.blogs_sharing_error, LENGTH_SHORT)
.show();
}
});
}
} }

View File

@@ -0,0 +1,18 @@
package org.briarproject.android.sharing;
import org.briarproject.android.contactselection.ContactSelectorController;
import org.briarproject.android.contactselection.SelectableContactItem;
import org.briarproject.android.controller.handler.ResultExceptionHandler;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
public interface ShareBlogController
extends ContactSelectorController<SelectableContactItem> {
void share(GroupId g, Collection<ContactId> contacts, String msg,
ResultExceptionHandler<Void, DbException> handler);
}

View File

@@ -0,0 +1,90 @@
package org.briarproject.android.sharing;
import org.briarproject.android.contactselection.ContactSelectorControllerImpl;
import org.briarproject.android.contactselection.SelectableContactItem;
import org.briarproject.android.controller.handler.ResultExceptionHandler;
import org.briarproject.api.blogs.BlogSharingManager;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchContactException;
import org.briarproject.api.db.NoSuchGroupException;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
@Immutable
@NotNullByDefault
public class ShareBlogControllerImpl
extends ContactSelectorControllerImpl<SelectableContactItem>
implements ShareBlogController {
private final static Logger LOG =
Logger.getLogger(ShareBlogControllerImpl.class.getName());
private final BlogSharingManager blogSharingManager;
@Inject
public ShareBlogControllerImpl(
@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
ContactManager contactManager,
BlogSharingManager blogSharingManager) {
super(dbExecutor, lifecycleManager, contactManager);
this.blogSharingManager = blogSharingManager;
}
@Override
protected boolean isSelected(Contact c, boolean wasSelected)
throws DbException {
return wasSelected;
}
@Override
protected boolean isDisabled(GroupId g, Contact c) throws DbException {
return !blogSharingManager.canBeShared(g, c);
}
@Override
protected SelectableContactItem getItem(Contact c, boolean selected,
boolean disabled) {
return new SelectableContactItem(c, selected, disabled);
}
@Override
public void share(final GroupId g, final Collection<ContactId> contacts,
final String msg,
final ResultExceptionHandler<Void, DbException> handler) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
for (ContactId c : contacts) {
try {
blogSharingManager.sendInvitation(g, c, msg);
} catch (NoSuchContactException | NoSuchGroupException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
handler.onException(e);
}
}
});
}
}

View File

@@ -0,0 +1,49 @@
package org.briarproject.android.sharing;
import android.os.Bundle;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.contactselection.ContactSelectorController;
import org.briarproject.android.contactselection.ContactSelectorFragment;
import org.briarproject.android.contactselection.SelectableContactItem;
import org.briarproject.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.api.sync.GroupId;
import javax.inject.Inject;
import static org.briarproject.api.sharing.SharingConstants.GROUP_ID;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ShareBlogFragment extends ContactSelectorFragment {
public static final String TAG = ShareBlogFragment.class.getName();
@Inject
ShareBlogController controller;
public static ShareBlogFragment newInstance(GroupId groupId) {
Bundle args = new Bundle();
args.putByteArray(GROUP_ID, groupId.getBytes());
ShareBlogFragment fragment = new ShareBlogFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
protected ContactSelectorController<SelectableContactItem> getController() {
return controller;
}
@Override
public String getUniqueTag() {
return TAG;
}
}

View File

@@ -1,22 +1,30 @@
package org.briarproject.android.sharing; package org.briarproject.android.sharing;
import android.os.Bundle;
import android.widget.Toast;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.android.ActivityComponent; import org.briarproject.android.ActivityComponent;
import org.briarproject.api.contact.Contact; import org.briarproject.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.forum.ForumSharingManager; import org.briarproject.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.nullsafety.ParametersNotNullByDefault;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import javax.inject.Inject; import javax.inject.Inject;
import static android.widget.Toast.LENGTH_SHORT;
import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ShareForumActivity extends ShareActivity { public class ShareForumActivity extends ShareActivity {
// Fields that are accessed from background threads must be volatile
@Inject @Inject
volatile ForumSharingManager forumSharingManager; ShareForumController controller;
@Override @Override
BaseMessageFragment getMessageFragment() { BaseMessageFragment getMessageFragment() {
@@ -29,23 +37,40 @@ public class ShareForumActivity extends ShareActivity {
} }
@Override @Override
public boolean isDisabled(GroupId groupId, Contact c) throws DbException { public void onCreate(@Nullable Bundle bundle) {
return !forumSharingManager.canBeShared(groupId, c); super.onCreate(bundle);
}
@Override if (bundle == null) {
protected void share(GroupId g, ContactId c, String msg) ShareForumFragment fragment =
throws DbException { ShareForumFragment.newInstance(groupId);
forumSharingManager.sendInvitation(g, c, msg); getSupportFragmentManager().beginTransaction()
} .add(R.id.fragmentContainer, fragment)
.commit();
@Override }
protected int getSharingError() {
return R.string.forum_share_error;
} }
@Override @Override
public int getMaximumMessageLength() { public int getMaximumMessageLength() {
return MAX_MESSAGE_BODY_LENGTH; return MAX_MESSAGE_BODY_LENGTH;
} }
@Override
void share(Collection<ContactId> contacts, String msg) {
controller.share(groupId, contacts, msg,
new UiResultExceptionHandler<Void, DbException>(this) {
@Override
public void onResultUi(Void result) {
}
@Override
public void onExceptionUi(DbException exception) {
// TODO proper error handling
Toast.makeText(ShareForumActivity.this,
R.string.forum_share_error, LENGTH_SHORT)
.show();
}
});
}
} }

View File

@@ -0,0 +1,18 @@
package org.briarproject.android.sharing;
import org.briarproject.android.contactselection.ContactSelectorController;
import org.briarproject.android.contactselection.SelectableContactItem;
import org.briarproject.android.controller.handler.ResultExceptionHandler;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
public interface ShareForumController
extends ContactSelectorController<SelectableContactItem> {
void share(GroupId g, Collection<ContactId> contacts, String msg,
ResultExceptionHandler<Void, DbException> handler);
}

View File

@@ -0,0 +1,90 @@
package org.briarproject.android.sharing;
import org.briarproject.android.contactselection.ContactSelectorControllerImpl;
import org.briarproject.android.contactselection.SelectableContactItem;
import org.briarproject.android.controller.handler.ResultExceptionHandler;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchContactException;
import org.briarproject.api.db.NoSuchGroupException;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
@Immutable
@NotNullByDefault
public class ShareForumControllerImpl
extends ContactSelectorControllerImpl<SelectableContactItem>
implements ShareForumController {
private final static Logger LOG =
Logger.getLogger(ShareForumControllerImpl.class.getName());
private final ForumSharingManager forumSharingManager;
@Inject
public ShareForumControllerImpl(
@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
ContactManager contactManager,
ForumSharingManager forumSharingManager) {
super(dbExecutor, lifecycleManager, contactManager);
this.forumSharingManager = forumSharingManager;
}
@Override
protected boolean isSelected(Contact c, boolean wasSelected)
throws DbException {
return wasSelected;
}
@Override
protected boolean isDisabled(GroupId g, Contact c) throws DbException {
return !forumSharingManager.canBeShared(g, c);
}
@Override
protected SelectableContactItem getItem(Contact c, boolean selected,
boolean disabled) {
return new SelectableContactItem(c, selected, disabled);
}
@Override
public void share(final GroupId g, final Collection<ContactId> contacts,
final String msg,
final ResultExceptionHandler<Void, DbException> handler) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
for (ContactId c : contacts) {
try {
forumSharingManager.sendInvitation(g, c, msg);
} catch (NoSuchContactException | NoSuchGroupException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
handler.onException(e);
}
}
});
}
}

View File

@@ -0,0 +1,49 @@
package org.briarproject.android.sharing;
import android.os.Bundle;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.contactselection.ContactSelectorController;
import org.briarproject.android.contactselection.ContactSelectorFragment;
import org.briarproject.android.contactselection.SelectableContactItem;
import org.briarproject.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.api.sync.GroupId;
import javax.inject.Inject;
import static org.briarproject.api.sharing.SharingConstants.GROUP_ID;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ShareForumFragment extends ContactSelectorFragment {
public static final String TAG = ShareForumFragment.class.getName();
@Inject
ShareForumController controller;
public static ShareForumFragment newInstance(GroupId groupId) {
Bundle args = new Bundle();
args.putByteArray(GROUP_ID, groupId.getBytes());
ShareForumFragment fragment = new ShareForumFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
protected ContactSelectorController<SelectableContactItem> getController() {
return controller;
}
@Override
public String getUniqueTag() {
return TAG;
}
}

View File

@@ -11,7 +11,6 @@ import org.briarproject.android.contact.ContactItem;
import org.briarproject.android.view.BriarRecyclerView; import org.briarproject.android.view.BriarRecyclerView;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.plugins.ConnectionRegistry;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import java.util.ArrayList; import java.util.ArrayList;
@@ -19,8 +18,6 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
abstract class SharingStatusActivity extends BriarActivity { abstract class SharingStatusActivity extends BriarActivity {
@@ -32,10 +29,6 @@ abstract class SharingStatusActivity extends BriarActivity {
private BriarRecyclerView sharedByList, sharedWithList; private BriarRecyclerView sharedByList, sharedWithList;
private SharingStatusAdapter sharedByAdapter, sharedWithAdapter; private SharingStatusAdapter sharedByAdapter, sharedWithAdapter;
// Fields that are accessed from background threads must be volatile
@Inject
volatile ConnectionRegistry connectionRegistry;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -109,9 +102,7 @@ abstract class SharingStatusActivity extends BriarActivity {
try { try {
List<ContactItem> contactItems = new ArrayList<>(); List<ContactItem> contactItems = new ArrayList<>();
for (Contact c : getSharedBy()) { for (Contact c : getSharedBy()) {
boolean isConnected = ContactItem item = new ContactItem(c);
connectionRegistry.isConnected(c.getId());
ContactItem item = new ContactItem(c, isConnected);
contactItems.add(item); contactItems.add(item);
} }
displaySharedBy(contactItems); displaySharedBy(contactItems);
@@ -140,9 +131,7 @@ abstract class SharingStatusActivity extends BriarActivity {
try { try {
List<ContactItem> contactItems = new ArrayList<>(); List<ContactItem> contactItems = new ArrayList<>();
for (Contact c : getSharedWith()) { for (Contact c : getSharedWith()) {
boolean isConnected = ContactItem item = new ContactItem(c);
connectionRegistry.isConnected(c.getId());
ContactItem item = new ContactItem(c, isConnected);
contactItems.add(item); contactItems.add(item);
} }
displaySharedWith(contactItems); displaySharedWith(contactItems);