Merge branch '661-implement-ux-for-creating-a-private-group' into 'master'

Implement UX for creating a private group

This MR allows the user to create a new private group and select contacts to be invited into the group.

There are currently 6 commits starting with some small refactoring for code reuse and making more functionality available in the backend. Each commit could be split up into a dedicated MR if desired.

![create-groups](/uploads/5229f102ee2611c05d5e9d1e2aac510d/create-groups.gif)

Closes #661

See merge request !353
This commit is contained in:
akwizgran
2016-10-26 09:32:34 +00:00
45 changed files with 922 additions and 411 deletions

View File

@@ -100,6 +100,17 @@
/>
</activity>
<activity
android:name=".android.privategroup.creation.CreateGroupActivity"
android:label="@string/groups_create_group_title"
android:parentActivityName=".android.NavDrawerActivity"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.NavDrawerActivity"
/>
</activity>
<activity
android:name=".android.privategroup.conversation.GroupActivity"
android:label="@string/app_name"

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
android:id="@+id/shareContainer"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="@dimen/margin_medium">
<EditText
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="bottom"
android:hint="@string/groups_create_group_hint"/>
<Button
android:id="@+id/button"
style="@style/BriarButton"
android:enabled="false"
android:text="@string/groups_create_group_button"/>
</LinearLayout>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<org.briarproject.android.view.LargeTextInputView
android:id="@+id/messageView"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:buttonText="@string/forum_share_button"
app:fillHeight="true"
app:hint="@string/forum_share_message"/>

View File

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/introductionText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/margin_activity_horizontal"
android:text="@string/forum_share_message"
android:textColor="@color/briar_text_primary"
android:textSize="@dimen/text_size_medium"/>
<org.briarproject.android.view.LargeTextInputView
android:id="@+id/invitationMessageView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="bottom"
app:buttonText="@string/forum_share_button"
app:fillHeight="true"
app:hint="@string/introduction_message_hint"/>
</LinearLayout>

View File

@@ -4,9 +4,9 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_share_forum"
android:id="@+id/action_contacts_selected"
android:icon="@drawable/ic_check_white"
android:title="@string/forum_share_action"
android:title="@string/contacts_selected"
app:showAsAction="always"/>
</menu>

View File

@@ -6,7 +6,7 @@
<item
android:id="@+id/action_add_group"
android:icon="@drawable/ic_add_white"
android:title="@string/groups_add_group_title"
android:title="@string/groups_create_group_title"
app:showAsAction="ifRoom"/>
</menu>

View File

@@ -171,7 +171,7 @@
<string name="forum_left_toast">Forum wurde verlassen</string>
<!--Forum Sharing-->
<string name="forum_share_button">Forum teilen</string>
<string name="forum_share_action">Teile dieses Forum mit den gewählten Kontakten</string>
<string name="contacts_selected">Teile dieses Forum mit den gewählten Kontakten</string>
<string name="activity_share_toolbar_header">Kontakte auswählen</string>
<string name="no_contacts_selector">Du scheinst hier neu zu sein und noch keine Kontakte zu haben.\n\nBitte komm zurück, wenn du deinen ersten Kontakt hinzugefügt hast.</string>
<string name="forum_shared_snackbar">Forum mit gewählten Kontakten geteilt</string>

View File

@@ -159,7 +159,7 @@
<string name="forum_left_toast">Foro abandonado</string>
<!--Forum Sharing-->
<string name="forum_share_button">Compartir foro</string>
<string name="forum_share_action">Compartir este foro con los contactos seleccionados</string>
<string name="contacts_selected">Compartir este foro con los contactos seleccionados</string>
<string name="activity_share_toolbar_header">Elige contactos</string>
<string name="no_contacts_selector">Parece que eres nuevo aquí y no tienes contactos aún.\n\nPor favor, vuelve cuando hayas añadido tu primer contacto.</string>
<string name="forum_shared_snackbar">Foro compartido con los contactos seleccionados</string>

View File

@@ -136,7 +136,7 @@
<string name="forum_left_toast">Forum lasciato</string>
<!--Forum Sharing-->
<string name="forum_share_button">Condividi Forum</string>
<string name="forum_share_action">Condividi questo forum con i contatti scelti</string>
<string name="contacts_selected">Condividi questo forum con i contatti scelti</string>
<string name="activity_share_toolbar_header">Scegli Contatti</string>
<string name="forum_shared_snackbar">Forum condiviso con i contatti scelti</string>
<string name="forum_share_error">C\'è stato un errore nella condivisione di questo forum.</string>

View File

@@ -171,7 +171,7 @@ Se sentido sozinho aqui? Compartilhe esse fórum com seus contatos!</string>
<string name="forum_left_toast">Saiu do fórum</string>
<!--Forum Sharing-->
<string name="forum_share_button">Compartilhar fórum</string>
<string name="forum_share_action">Compartilhar este fórum com os contatos escolhidos</string>
<string name="contacts_selected">Compartilhar este fórum com os contatos escolhidos</string>
<string name="activity_share_toolbar_header">Escolher contatos</string>
<string name="no_contacts_selector">Parece que você é novo aqui e não tem nenhum contato ainda.
Por favor volte aqui depois de adicionar um contato.</string>

View File

@@ -155,8 +155,11 @@
<string name="groups_group_is_empty">This group is empty</string>
<string name="groups_group_is_dissolved">This group is dissolved</string>
<string name="groups_remove">Remove</string>
<string name="groups_add_group_title">Add Private Group</string>
<string name="groups_create_group_title">Create Private Group</string>
<string name="groups_no_messages">This group is empty.\n\nYou can use the pen icon at the top to compose the first message.</string>
<string name="groups_create_group_button">Create Group</string>
<string name="groups_create_group_invitation_button">Send Invitation</string>
<string name="groups_create_group_hint">Add a name for your private group</string>
<string name="groups_compose_message">Compose Message</string>
<string name="groups_message_sent">Message sent</string>
<string name="groups_message_received">Message received</string>
@@ -195,7 +198,7 @@
<!-- Forum Sharing -->
<string name="forum_share_button">Share Forum</string>
<string name="forum_share_action">Share this forum with chosen contacts</string>
<string name="contacts_selected">Contacts selected</string>
<string name="activity_share_toolbar_header">Choose Contacts</string>
<string name="no_contacts_selector">It seems that you are new here and have no contacts yet.\n\nPlease come back here after you added your first contact.</string>
<string name="forum_shared_snackbar">Forum shared with chosen contacts</string>

View File

@@ -28,7 +28,10 @@ import org.briarproject.android.keyagreement.KeyAgreementActivity;
import org.briarproject.android.keyagreement.ShowQrCodeFragment;
import org.briarproject.android.panic.PanicPreferencesActivity;
import org.briarproject.android.panic.PanicResponderActivity;
import org.briarproject.android.privategroup.creation.CreateGroupActivity;
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.sharing.ContactSelectorFragment;
import org.briarproject.android.sharing.InvitationsBlogActivity;
@@ -73,6 +76,8 @@ public interface ActivityComponent {
void inject(InvitationsBlogActivity activity);
void inject(CreateGroupActivity activity);
void inject(GroupActivity activity);
void inject(CreateForumActivity activity);
@@ -118,6 +123,8 @@ public interface ActivityComponent {
// Fragments
void inject(ContactListFragment fragment);
void inject(CreateGroupFragment fragment);
void inject(CreateGroupMessageFragment fragment);
void inject(GroupListFragment fragment);
void inject(ForumListFragment fragment);
void inject(FeedFragment fragment);

View File

@@ -23,6 +23,8 @@ import org.briarproject.android.forum.ForumController;
import org.briarproject.android.forum.ForumControllerImpl;
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.list.GroupListController;
import org.briarproject.android.privategroup.list.GroupListControllerImpl;
@@ -101,6 +103,13 @@ public class ActivityModule {
return groupListController;
}
@ActivityScope
@Provides
protected CreateGroupController provideCreateGroupController(
CreateGroupControllerImpl createGroupController) {
return createGroupController;
}
@ActivityScope
@Provides
protected GroupController provideGroupController(

View File

@@ -5,7 +5,6 @@ import android.support.annotation.CallSuper;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
@@ -36,8 +35,6 @@ abstract class BasePostFragment extends BaseFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setHasOptionsMenu(true);
view = inflater.inflate(R.layout.fragment_blog_post, container,
false);
progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
@@ -60,17 +57,6 @@ abstract class BasePostFragment extends BaseFragment {
stopPeriodicUpdate();
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
getActivity().onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@UiThread
protected void onBlogPostLoaded(BlogPostItem post) {
progressBar.setVisibility(INVISIBLE);

View File

@@ -80,9 +80,6 @@ public class BlogFragment extends BaseFragment implements
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setHasOptionsMenu(true);
Bundle args = getArguments();
byte[] b = args.getByteArray(GROUP_ID);
if (b == null) throw new IllegalStateException("No group ID in args");
@@ -151,9 +148,6 @@ public class BlogFragment extends BaseFragment implements
android.R.anim.slide_in_left,
android.R.anim.slide_out_right);
switch (item.getItemId()) {
case android.R.id.home:
getActivity().onBackPressed();
return true;
case R.id.action_write_blog_post:
Intent i = new Intent(getActivity(),
WriteBlogPostActivity.class);

View File

@@ -66,7 +66,6 @@ public class FeedFragment extends BaseFragment implements
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setHasOptionsMenu(true);
View v = inflater.inflate(R.layout.fragment_blog, container, false);
adapter = new BlogPostAdapter(getActivity(), this);

View File

@@ -72,8 +72,6 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setHasOptionsMenu(true);
Bundle args = getArguments();
blogId = new GroupId(args.getByteArray(GROUP_ID));
postId = new MessageId(args.getByteArray(POST_ID));

View File

@@ -106,8 +106,6 @@ public class ContactListFragment extends BaseFragment implements EventListener {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setHasOptionsMenu(true);
View contentView =
inflater.inflate(R.layout.list, container,
false);

View File

@@ -623,6 +623,7 @@ public class ConversationActivity extends BriarActivity
long timestamp = System.currentTimeMillis();
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
createMessage(StringUtils.toUtf8(text), timestamp);
textInputView.setText("");
}
private long getMinTimestampForNewMessage() {

View File

@@ -78,8 +78,6 @@ public class ForumListFragment extends BaseEventFragment implements
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setHasOptionsMenu(true);
View contentView =
inflater.inflate(R.layout.fragment_forum_list, container,
false);

View File

@@ -5,6 +5,7 @@ import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.v4.app.Fragment;
import android.view.MenuItem;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.DestroyableContext;
@@ -27,6 +28,9 @@ public abstract class BaseFragment extends Fragment
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// allow for "up" button to act as back button
setHasOptionsMenu(true);
}
@@ -37,6 +41,17 @@ public abstract class BaseFragment extends Fragment
listener.onFragmentCreated(getUniqueTag());
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
listener.onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@UiThread
protected void finish() {
getActivity().supportFinishAfterTransition();
@@ -47,6 +62,9 @@ public abstract class BaseFragment extends Fragment
@Deprecated
void runOnDbThread(Runnable runnable);
@UiThread
void onBackPressed();
@UiThread
ActivityComponent getActivityComponent();

View File

@@ -14,6 +14,7 @@ import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.android.view.TextInputView;
import org.briarproject.android.view.TextInputView.TextInputListener;
import org.briarproject.api.FormatException;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
@@ -37,7 +38,7 @@ import static java.util.logging.Level.WARNING;
import static org.briarproject.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
public class IntroductionMessageFragment extends BaseFragment
implements TextInputView.TextInputListener {
implements TextInputListener {
public static final String TAG =
IntroductionMessageFragment.class.getName();
@@ -56,7 +57,8 @@ public class IntroductionMessageFragment extends BaseFragment
@Inject
protected volatile IntroductionManager introductionManager;
public static IntroductionMessageFragment newInstance(int contactId1, int contactId2) {
public static IntroductionMessageFragment newInstance(int contactId1,
int contactId2) {
Bundle args = new Bundle();
args.putInt(CONTACT_ID_1, contactId1);
args.putInt(CONTACT_ID_2, contactId2);

View File

@@ -0,0 +1,165 @@
package org.briarproject.android.privategroup.creation;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.android.privategroup.conversation.GroupActivity;
import org.briarproject.android.sharing.BaseMessageFragment.MessageFragmentListener;
import org.briarproject.android.sharing.ContactSelectorActivity;
import org.briarproject.android.sharing.ContactSelectorFragment;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.sync.GroupId;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import javax.inject.Inject;
import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
import static android.widget.Toast.LENGTH_SHORT;
import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_MSG_LENGTH;
public class CreateGroupActivity extends ContactSelectorActivity implements
CreateGroupListener, MessageFragmentListener {
@Inject
CreateGroupController controller;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_fragment_container);
if (bundle == null) {
CreateGroupFragment fragment = new CreateGroupFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.fragmentContainer, fragment)
.commit();
} else {
byte[] groupBytes = bundle.getByteArray(GROUP_ID);
if (groupBytes != null) groupId = new GroupId(groupBytes);
}
}
@Override
public void onBackPressed() {
if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
// At this point, the group had been created already,
// so don't allow to create it again.
openNewGroup();
} else {
super.onBackPressed();
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (groupId != null) {
outState.putByteArray(GROUP_ID, groupId.getBytes());
}
}
@Override
public void onGroupNameChosen(String name) {
controller.createGroup(name,
new UiResultExceptionHandler<GroupId, DbException>(this) {
@Override
public void onResultUi(GroupId g) {
switchToContactSelectorFragment(g);
}
@Override
public void onExceptionUi(DbException exception) {
// TODO proper error handling
finish();
}
});
}
private void switchToContactSelectorFragment(GroupId g) {
ContactSelectorFragment fragment =
ContactSelectorFragment.newInstance(g);
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(android.R.anim.fade_in,
android.R.anim.fade_out,
android.R.anim.slide_in_left,
android.R.anim.slide_out_right)
.replace(R.id.fragmentContainer, fragment)
.addToBackStack(fragment.getUniqueTag())
.commit();
}
@Override
public boolean isDisabled(GroupId groupId, Contact c) throws DbException {
return false;
}
@Override
public void contactsSelected(GroupId groupId,
Collection<ContactId> contacts) {
super.contactsSelected(groupId, contacts);
CreateGroupMessageFragment fragment = new CreateGroupMessageFragment();
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(android.R.anim.fade_in,
android.R.anim.fade_out,
android.R.anim.slide_in_left,
android.R.anim.slide_out_right)
.replace(R.id.fragmentContainer, fragment)
.addToBackStack(fragment.getUniqueTag())
.commit();
}
@Override
public boolean onButtonClick(@NotNull String message) {
controller.sendInvitation(groupId, contacts, message,
new UiResultExceptionHandler<Void, DbException>(this) {
@Override
public void onResultUi(Void result) {
Toast.makeText(CreateGroupActivity.this,
"Inviting members is not yet implemented",
LENGTH_SHORT).show();
openNewGroup();
}
@Override
public void onExceptionUi(DbException exception) {
// TODO proper error handling
finish();
}
});
return true;
}
@Override
public int getMaximumMessageLength() {
return MAX_GROUP_INVITATION_MSG_LENGTH;
}
private void openNewGroup() {
Intent i = new Intent(this, GroupActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes());
ActivityOptionsCompat options =
makeCustomAnimation(this, android.R.anim.fade_in,
android.R.anim.fade_out);
ActivityCompat.startActivity(this, i, options.toBundle());
// finish this activity, so we can't come back to it
finish();
}
}

View File

@@ -0,0 +1,19 @@
package org.briarproject.android.privategroup.creation;
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.sync.GroupId;
import java.util.Collection;
public interface CreateGroupController extends DbController {
void createGroup(String name,
ResultExceptionHandler<GroupId, DbException> result);
void sendInvitation(GroupId groupId, Collection<ContactId> contacts,
String message, ResultExceptionHandler<Void, DbException> result);
}

View File

@@ -0,0 +1,68 @@
package org.briarproject.android.privategroup.creation;
import org.briarproject.android.controller.DbControllerImpl;
import org.briarproject.android.controller.handler.ResultExceptionHandler;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.privategroup.PrivateGroupManager;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
public class CreateGroupControllerImpl extends DbControllerImpl
implements CreateGroupController {
private static final Logger LOG =
Logger.getLogger(CreateGroupControllerImpl.class.getName());
private final PrivateGroupManager groupManager;
@Inject
CreateGroupControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
PrivateGroupManager groupManager) {
super(dbExecutor, lifecycleManager);
this.groupManager = groupManager;
}
@Override
public void createGroup(final String name,
final ResultExceptionHandler<GroupId, DbException> handler) {
runOnDbThread(new Runnable() {
@Override
public void run() {
LOG.info("Adding group to database...");
try {
handler.onResult(groupManager.addPrivateGroup(name));
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
handler.onException(e);
}
}
});
}
@Override
public void sendInvitation(final GroupId groupId,
final Collection<ContactId> contacts, final String message,
final ResultExceptionHandler<Void, DbException> result) {
runOnDbThread(new Runnable() {
@Override
public void run() {
// TODO actually send invitation
//noinspection ConstantConditions
result.onResult(null);
}
});
}
}

View File

@@ -0,0 +1,93 @@
package org.briarproject.android.privategroup.creation;
import android.content.Context;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.fragment.BaseFragment;
import static org.briarproject.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
public class CreateGroupFragment extends BaseFragment {
public final static String TAG = CreateGroupFragment.class.getName();
private CreateGroupListener listener;
private EditText name;
private Button button;
@Override
public void onAttach(Context context) {
super.onAttach(context);
listener = (CreateGroupListener) context;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// inflate view
View v = inflater.inflate(R.layout.fragment_create_group, container,
false);
name = (EditText) v.findViewById(R.id.name);
name.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
validateName();
}
@Override
public void afterTextChanged(Editable s) {
}
});
button = (Button) v.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.hideSoftKeyboard(name);
listener.onGroupNameChosen(name.getText().toString());
}
});
return v;
}
@Override
public void onStart() {
super.onStart();
listener.showSoftKeyboard(name);
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
public String getUniqueTag() {
return TAG;
}
private void validateName() {
String name = this.name.getText().toString();
if (name.length() < 1 || name.length() > MAX_GROUP_NAME_LENGTH)
button.setEnabled(false);
else if(!button.isEnabled())
button.setEnabled(true);
}
}

View File

@@ -0,0 +1,15 @@
package org.briarproject.android.privategroup.creation;
import android.view.View;
import org.briarproject.android.fragment.BaseFragment.BaseFragmentListener;
interface CreateGroupListener extends BaseFragmentListener {
void onGroupNameChosen(String name);
void showSoftKeyboard(View view);
void hideSoftKeyboard(View view);
}

View File

@@ -0,0 +1,36 @@
package org.briarproject.android.privategroup.creation;
import android.support.annotation.StringRes;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.sharing.BaseMessageFragment;
public class CreateGroupMessageFragment extends BaseMessageFragment {
private final static String TAG =
CreateGroupMessageFragment.class.getName();
@Override
@StringRes
protected int getButtonText() {
return R.string.groups_create_group_invitation_button;
}
@Override
@StringRes
protected int getHintText() {
return R.string.forum_share_message;
}
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
}

View File

@@ -1,8 +1,10 @@
package org.briarproject.android.privategroup.list;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -15,6 +17,7 @@ import org.briarproject.R;
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.list.GroupListController.GroupListListener;
import org.briarproject.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener;
import org.briarproject.android.view.BriarRecyclerView;
@@ -27,6 +30,8 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
public class GroupListFragment extends BaseFragment implements
GroupListListener, OnGroupRemoveClickListener {
@@ -48,8 +53,6 @@ public class GroupListFragment extends BaseFragment implements
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setHasOptionsMenu(true);
View v = inflater.inflate(R.layout.list, container, false);
adapter = new GroupListAdapter(getContext(), this);
@@ -94,7 +97,12 @@ public class GroupListFragment extends BaseFragment implements
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.action_add_group:
// TODO
Intent i = new Intent(getContext(), CreateGroupActivity.class);
ActivityOptionsCompat options =
makeCustomAnimation(getActivity(),
android.R.anim.slide_in_left,
android.R.anim.slide_out_right);
startActivity(i, options.toBundle());
return true;
default:
return super.onOptionsItemSelected(item);

View File

@@ -0,0 +1,98 @@
package org.briarproject.android.sharing;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.StringRes;
import android.support.annotation.UiThread;
import android.support.design.widget.Snackbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.R;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.android.view.LargeTextInputView;
import org.briarproject.android.view.TextInputView.TextInputListener;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.util.StringUtils;
import static android.support.design.widget.Snackbar.LENGTH_SHORT;
import static org.briarproject.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH;
import static org.briarproject.util.StringUtils.truncateUtf8;
public abstract class BaseMessageFragment extends BaseFragment
implements TextInputListener {
protected LargeTextInputView message;
private MessageFragmentListener listener;
@Override
public void onAttach(Context context) {
super.onAttach(context);
listener = (MessageFragmentListener) context;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// inflate view
View v = inflater.inflate(R.layout.fragment_message, container,
false);
message = (LargeTextInputView) v.findViewById(R.id.messageView);
message.setButtonText(getString(getButtonText()));
message.setHint(getHintText());
message.setListener(this);
return v;
}
protected void setTitle(int res) {
listener.setTitle(res);
}
@StringRes
protected abstract int getButtonText();
@StringRes
protected abstract int getHintText();
@Override
public void onStart() {
super.onStart();
message.showSoftKeyboard();
}
@Override
public void onSendClick(String msg) {
if (StringUtils.isTooLong(msg, listener.getMaximumMessageLength())) {
Snackbar.make(message, R.string.text_too_long, LENGTH_SHORT).show();
return;
}
// disable button to prevent accidental double actions
message.setSendButtonEnabled(false);
message.hideSoftKeyboard();
msg = truncateUtf8(msg, MAX_INVITATION_MESSAGE_LENGTH);
if(!listener.onButtonClick(msg)) {
message.setSendButtonEnabled(true);
message.showSoftKeyboard();
}
}
@UiThread
@NotNullByDefault
public interface MessageFragmentListener {
void onBackPressed();
void setTitle(@StringRes int titleRes);
/** Returns true when the button click has been consumed. */
boolean onButtonClick(String message);
int getMaximumMessageLength();
}
}

View File

@@ -0,0 +1,90 @@
package org.briarproject.android.sharing;
import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import org.briarproject.R;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.fragment.BaseFragment.BaseFragmentListener;
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.ArrayList;
import java.util.Collection;
import java.util.List;
public abstract class ContactSelectorActivity extends BriarActivity implements
BaseFragmentListener, ContactSelectorListener {
final static String CONTACTS = "contacts";
protected GroupId groupId;
protected Collection<ContactId> contacts;
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_fragment_container);
if (bundle != null) {
ArrayList<Integer> intContacts =
bundle.getIntegerArrayList(CONTACTS);
if (intContacts != null) {
contacts = getContactsFromIntegers(intContacts);
}
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (contacts != null) {
outState.putIntegerArrayList(CONTACTS,
getContactsFromIds(contacts));
}
}
@CallSuper
@UiThread
@Override
public void contactsSelected(GroupId groupId,
Collection<ContactId> contacts) {
this.groupId = groupId;
this.contacts = contacts;
}
@DatabaseExecutor
public abstract boolean isDisabled(GroupId groupId, Contact c)
throws DbException;
static ArrayList<Integer> getContactsFromIds(
Collection<ContactId> contacts) {
// transform ContactIds to Integers so they can be added to a bundle
ArrayList<Integer> intContacts = new ArrayList<>(contacts.size());
for (ContactId contactId : contacts) {
intContacts.add(contactId.getInt());
}
return intContacts;
}
static Collection<ContactId> getContactsFromIntegers(
ArrayList<Integer> intContacts) {
// turn contact integers from a bundle back to ContactIds
List<ContactId> contacts = new ArrayList<>(intContacts.size());
for (Integer c : intContacts) {
contacts.add(new ContactId(c));
}
return contacts;
}
@Override
public void onFragmentCreated(String tag) {
}
}

View File

@@ -23,7 +23,6 @@ import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.db.DbException;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.GroupId;
@@ -48,7 +47,6 @@ public class ContactSelectorFragment extends BaseFragment implements
public static final String TAG = ContactSelectorFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
private ShareActivity shareActivity;
private Menu menu;
private BriarRecyclerView list;
private ContactSelectorAdapter adapter;
@@ -59,13 +57,11 @@ public class ContactSelectorFragment extends BaseFragment implements
volatile ContactManager contactManager;
@Inject
volatile IdentityManager identityManager;
@Inject
volatile ForumSharingManager forumSharingManager;
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();
@@ -81,14 +77,13 @@ public class ContactSelectorFragment extends BaseFragment implements
@Override
public void onAttach(Context context) {
super.onAttach(context);
shareActivity = (ShareActivity) context;
listener = (ContactSelectorListener) context;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
Bundle args = getArguments();
byte[] b = args.getByteArray(GROUP_ID);
if (b == null) throw new IllegalStateException("No GroupId");
@@ -139,6 +134,7 @@ public class ContactSelectorFragment extends BaseFragment implements
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (adapter != null) {
selectedContacts = adapter.getSelectedContactIds();
outState.putIntegerArrayList(CONTACTS,
@@ -148,7 +144,7 @@ public class ContactSelectorFragment extends BaseFragment implements
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.forum_share_actions, menu);
inflater.inflate(R.menu.contact_selection_actions, menu);
super.onCreateOptionsMenu(menu, inflater);
this.menu = menu;
// hide sharing action initially, if no contact is selected
@@ -159,12 +155,9 @@ public class ContactSelectorFragment extends BaseFragment implements
public boolean onOptionsItemSelected(final MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case android.R.id.home:
shareActivity.onBackPressed();
return true;
case R.id.action_share_forum:
case R.id.action_contacts_selected:
selectedContacts = adapter.getSelectedContactIds();
shareActivity.showMessageScreen(groupId, selectedContacts);
listener.contactsSelected(groupId, selectedContacts);
return true;
default:
return super.onOptionsItemSelected(item);
@@ -185,7 +178,7 @@ public class ContactSelectorFragment extends BaseFragment implements
}
private void loadContacts(@Nullable final Collection<ContactId> selection) {
shareActivity.runOnDbThread(new Runnable() {
listener.runOnDbThread(new Runnable() {
@Override
public void run() {
try {
@@ -199,7 +192,7 @@ public class ContactSelectorFragment extends BaseFragment implements
boolean selected = selection != null &&
selection.contains(c.getId());
// do we have already some sharing with that contact?
boolean disabled = shareActivity.isDisabled(groupId, c);
boolean disabled = listener.isDisabled(groupId, c);
contacts.add(new SelectableContactListItem(c,
localAuthor, groupId, selected, disabled));
}
@@ -216,7 +209,7 @@ public class ContactSelectorFragment extends BaseFragment implements
}
private void displayContacts(final List<ContactListItem> contacts) {
shareActivity.runOnUiThreadUnlessDestroyed(new Runnable() {
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override
public void run() {
if (contacts.isEmpty()) list.showData();
@@ -228,7 +221,7 @@ public class ContactSelectorFragment extends BaseFragment implements
private void updateMenuItem() {
if (menu == null) return;
MenuItem item = menu.findItem(R.id.action_share_forum);
MenuItem item = menu.findItem(R.id.action_contacts_selected);
if (item == null) return;
selectedContacts = adapter.getSelectedContactIds();

View File

@@ -0,0 +1,28 @@
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;
public 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

@@ -2,96 +2,110 @@ package org.briarproject.android.sharing;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.support.annotation.StringRes;
import android.support.annotation.UiThread;
import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.api.contact.Contact;
import org.briarproject.android.sharing.BaseMessageFragment.MessageFragmentListener;
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 org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
public abstract class ShareActivity extends BriarActivity implements
BaseFragment.BaseFragmentListener {
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.logging.Level.WARNING;
final static String CONTACTS = "contacts";
public abstract class ShareActivity extends ContactSelectorActivity implements
MessageFragmentListener {
private final static Logger LOG =
Logger.getLogger(ShareActivity.class.getName());
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_share);
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
Intent i = getIntent();
byte[] b = i.getByteArrayExtra(GROUP_ID);
if (b == null) throw new IllegalStateException("No GroupId");
GroupId groupId = new GroupId(b);
groupId = new GroupId(b);
if (savedInstanceState == null) {
if (bundle == null) {
ContactSelectorFragment contactSelectorFragment =
ContactSelectorFragment.newInstance(groupId);
getSupportFragmentManager().beginTransaction()
.add(R.id.shareContainer, contactSelectorFragment)
.add(R.id.fragmentContainer, contactSelectorFragment)
.commit();
}
}
abstract ShareMessageFragment getMessageFragment(GroupId groupId,
Collection<ContactId> contacts);
abstract boolean isDisabled(GroupId groupId, Contact c) throws DbException;
void showMessageScreen(GroupId groupId, Collection<ContactId> contacts) {
ShareMessageFragment messageFragment =
getMessageFragment(groupId, contacts);
@UiThread
@Override
public void contactsSelected(GroupId groupId,
Collection<ContactId> contacts) {
super.contactsSelected(groupId, contacts);
BaseMessageFragment messageFragment = getMessageFragment();
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(android.R.anim.fade_in,
android.R.anim.fade_out,
android.R.anim.slide_in_left,
android.R.anim.slide_out_right)
.replace(R.id.shareContainer, messageFragment,
.replace(R.id.fragmentContainer, messageFragment,
ContactSelectorFragment.TAG)
.addToBackStack(null)
.commit();
}
static ArrayList<Integer> getContactsFromIds(
Collection<ContactId> contacts) {
// transform ContactIds to Integers so they can be added to a bundle
ArrayList<Integer> intContacts = new ArrayList<>(contacts.size());
for (ContactId contactId : contacts) {
intContacts.add(contactId.getInt());
}
return intContacts;
}
void sharingSuccessful(View v) {
setResult(RESULT_OK);
hideSoftKeyboard(v);
supportFinishAfterTransition();
}
static Collection<ContactId> getContactsFromIntegers(
ArrayList<Integer> intContacts) {
// turn contact integers from a bundle back to ContactIds
List<ContactId> contacts = new ArrayList<>(intContacts.size());
for (Integer c : intContacts) {
contacts.add(new ContactId(c));
}
return contacts;
}
abstract BaseMessageFragment getMessageFragment();
@UiThread
@Override
public void onFragmentCreated(String tag) {
public boolean onButtonClick(@NotNull String message) {
share(groupId, contacts, message);
setResult(RESULT_OK);
supportFinishAfterTransition();
return true;
}
private void share(final GroupId g, final Collection<ContactId> contacts,
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,5 +1,6 @@
package org.briarproject.android.sharing;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.api.blogs.BlogSharingManager;
import org.briarproject.api.contact.Contact;
@@ -7,18 +8,19 @@ import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
import javax.inject.Inject;
import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
public class ShareBlogActivity extends ShareActivity {
// Fields that are accessed from background threads must be volatile
@Inject
volatile BlogSharingManager blogSharingManager;
ShareMessageFragment getMessageFragment(GroupId groupId,
Collection<ContactId> contacts) {
return ShareBlogMessageFragment.newInstance(groupId, contacts);
@Override
BaseMessageFragment getMessageFragment() {
return ShareBlogMessageFragment.newInstance();
}
@Override
@@ -26,10 +28,24 @@ public class ShareBlogActivity extends ShareActivity {
component.inject(this);
}
/**
* This must only be called from a DbThread
*/
boolean isDisabled(GroupId groupId, Contact c) throws DbException {
@Override
public boolean isDisabled(GroupId groupId, Contact c) throws DbException {
return !blogSharingManager.canBeShared(groupId, c);
}
@Override
protected void share(GroupId g, ContactId c, String msg)
throws DbException {
blogSharingManager.sendInvitation(g, c, msg);
}
@Override
protected int getSharingError() {
return R.string.blogs_sharing_error;
}
@Override
public int getMaximumMessageLength() {
return MAX_MESSAGE_BODY_LENGTH;
}
}

View File

@@ -1,41 +1,20 @@
package org.briarproject.android.sharing;
import android.os.Bundle;
import android.support.annotation.StringRes;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.api.blogs.BlogSharingManager;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
import java.util.logging.Logger;
import javax.inject.Inject;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.logging.Level.WARNING;
public class ShareBlogMessageFragment extends ShareMessageFragment {
public class ShareBlogMessageFragment extends BaseMessageFragment {
public final static String TAG = ShareBlogMessageFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile BlogSharingManager blogSharingManager;
public static ShareBlogMessageFragment newInstance(GroupId groupId,
Collection<ContactId> contacts) {
ShareBlogMessageFragment fragment = new ShareBlogMessageFragment();
fragment.setArguments(getArguments(groupId, contacts));
return fragment;
public static ShareBlogMessageFragment newInstance() {
return new ShareBlogMessageFragment();
}
@Override
@@ -43,10 +22,19 @@ public class ShareBlogMessageFragment extends ShareMessageFragment {
Bundle savedInstanceState) {
setTitle(R.string.blogs_sharing_share);
return super.onCreateView(inflater, container, savedInstanceState);
}
View v = super.onCreateView(inflater, container, savedInstanceState);
ui.message.setButtonText(getString(R.string.blogs_sharing_button));
return v;
@Override
@StringRes
protected int getButtonText() {
return R.string.blogs_sharing_button;
}
@Override
@StringRes
protected int getHintText() {
return R.string.forum_share_message;
}
@Override
@@ -59,32 +47,4 @@ public class ShareBlogMessageFragment extends ShareMessageFragment {
return TAG;
}
@Override
protected void share(final String msg) {
listener.runOnDbThread(new Runnable() {
@Override
public void run() {
try {
for (ContactId c : getContacts()) {
blogSharingManager.sendInvitation(getGroupId(), c, msg);
}
} catch (DbException e) {
sharingError();
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
@Override
protected void sharingError() {
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override
public void run() {
int res = R.string.blogs_sharing_error;
Toast.makeText(getContext(), res, LENGTH_SHORT).show();
}
});
}
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.android.sharing;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
@@ -7,17 +8,19 @@ import org.briarproject.api.db.DbException;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
import javax.inject.Inject;
import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
public class ShareForumActivity extends ShareActivity {
// Fields that are accessed from background threads must be volatile
@Inject
volatile ForumSharingManager forumSharingManager;
ShareMessageFragment getMessageFragment(GroupId groupId,
Collection<ContactId> contacts) {
return ShareForumMessageFragment.newInstance(groupId, contacts);
@Override
BaseMessageFragment getMessageFragment() {
return ShareForumMessageFragment.newInstance();
}
@Override
@@ -25,10 +28,24 @@ public class ShareForumActivity extends ShareActivity {
component.inject(this);
}
/**
* This must only be called from a DbThread
*/
boolean isDisabled(GroupId groupId, Contact c) throws DbException {
@Override
public boolean isDisabled(GroupId groupId, Contact c) throws DbException {
return !forumSharingManager.canBeShared(groupId, c);
}
@Override
protected void share(GroupId g, ContactId c, String msg)
throws DbException {
forumSharingManager.sendInvitation(g, c, msg);
}
@Override
protected int getSharingError() {
return R.string.forum_share_error;
}
@Override
public int getMaximumMessageLength() {
return MAX_MESSAGE_BODY_LENGTH;
}
}

View File

@@ -1,41 +1,20 @@
package org.briarproject.android.sharing;
import android.os.Bundle;
import android.support.annotation.StringRes;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
import java.util.logging.Logger;
import javax.inject.Inject;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.logging.Level.WARNING;
public class ShareForumMessageFragment extends ShareMessageFragment {
public class ShareForumMessageFragment extends BaseMessageFragment {
public final static String TAG = ShareForumMessageFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile ForumSharingManager forumSharingManager;
public static ShareForumMessageFragment newInstance(GroupId groupId,
Collection<ContactId> contacts) {
ShareForumMessageFragment fragment = new ShareForumMessageFragment();
fragment.setArguments(getArguments(groupId, contacts));
return fragment;
public static ShareForumMessageFragment newInstance() {
return new ShareForumMessageFragment();
}
@Override
@@ -46,6 +25,18 @@ public class ShareForumMessageFragment extends ShareMessageFragment {
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
@StringRes
protected int getButtonText() {
return R.string.forum_share_button;
}
@Override
@StringRes
protected int getHintText() {
return R.string.forum_share_message;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
@@ -56,33 +47,4 @@ public class ShareForumMessageFragment extends ShareMessageFragment {
return TAG;
}
@Override
protected void share(final String msg) {
listener.runOnDbThread(new Runnable() {
@Override
public void run() {
try {
for (ContactId c : getContacts()) {
forumSharingManager.
sendInvitation(getGroupId(), c, msg);
}
} catch (DbException e) {
sharingError();
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
@Override
protected void sharingError() {
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override
public void run() {
int res = R.string.forum_share_error;
Toast.makeText(getContext(), res, LENGTH_SHORT).show();
}
});
}
}

View File

@@ -1,135 +0,0 @@
package org.briarproject.android.sharing;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.R;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.android.view.LargeTextInputView;
import org.briarproject.android.view.TextInputView.TextInputListener;
import org.briarproject.api.blogs.BlogSharingManager;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.sync.GroupId;
import org.briarproject.util.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import javax.inject.Inject;
import static org.briarproject.android.sharing.ShareActivity.CONTACTS;
import static org.briarproject.android.sharing.ShareActivity.getContactsFromIds;
import static org.briarproject.api.sharing.SharingConstants.GROUP_ID;
import static org.briarproject.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH;
abstract class ShareMessageFragment extends BaseFragment
implements TextInputListener {
protected ViewHolder ui;
private ShareActivity shareActivity;
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile ForumSharingManager forumSharingManager;
@Inject
protected volatile BlogSharingManager blogSharingManager;
private volatile GroupId groupId;
private volatile Collection<ContactId> contacts;
protected static Bundle getArguments(GroupId groupId,
Collection<ContactId> contacts) {
Bundle args = new Bundle();
args.putByteArray(GROUP_ID, groupId.getBytes());
args.putIntegerArrayList(CONTACTS, getContactsFromIds(contacts));
return args;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
shareActivity = (ShareActivity) context;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// allow for "up" button to act as back button
setHasOptionsMenu(true);
// get groupID and contactIDs from fragment arguments
groupId = new GroupId(getArguments().getByteArray(GROUP_ID));
ArrayList<Integer> intContacts =
getArguments().getIntegerArrayList(CONTACTS);
if (intContacts == null) throw new IllegalArgumentException();
contacts = ShareActivity.getContactsFromIntegers(intContacts);
// inflate view
View v = inflater.inflate(R.layout.fragment_share_message, container,
false);
ui = new ViewHolder(v);
ui.message.setListener(this);
return v;
}
@Override
public void onStart() {
super.onStart();
ui.message.showSoftKeyboard();
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
shareActivity.onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
protected void setTitle(int res) {
shareActivity.setTitle(res);
}
@Override
public void onSendClick(String msg) {
// disable button to prevent accidental double invitations
ui.message.setSendButtonEnabled(false);
msg = StringUtils.truncateUtf8(msg, MAX_INVITATION_MESSAGE_LENGTH);
share(msg);
// don't wait for the invitation to be made before finishing activity
shareActivity.sharingSuccessful(ui.message);
}
abstract void share(final String msg);
abstract void sharingError();
protected Collection<ContactId> getContacts() {
return contacts;
}
protected GroupId getGroupId() {
return groupId;
}
protected static class ViewHolder {
protected final LargeTextInputView message;
private ViewHolder(View v) {
message = (LargeTextInputView) v
.findViewById(R.id.invitationMessageView);
}
}
}

View File

@@ -253,6 +253,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
replyItem != null ? replyItem.getId() : null, handler);
textInput.hideSoftKeyboard();
textInput.setVisibility(GONE);
textInput.setText("");
adapter.setReplyItem(null);
}

View File

@@ -101,7 +101,6 @@ public class TextInputView extends KeyboardAwareLinearLayout
public void onClick(View v) {
if (listener != null) {
listener.onSendClick(ui.editText.getText().toString());
ui.editText.setText("");
}
}
});

View File

@@ -19,4 +19,9 @@ public interface PrivateGroupConstants {
*/
int MAX_GROUP_POST_BODY_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
/**
* The maximum length of a group invitation message in bytes.
*/
int MAX_GROUP_INVITATION_MSG_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
}

View File

@@ -18,6 +18,9 @@ public interface PrivateGroupManager extends MessageTracker {
@NotNull
ClientId getClientId();
/** Adds a new private group. */
GroupId addPrivateGroup(String name) throws DbException;
/** Removes a dissolved private group. */
void removePrivateGroup(GroupId g) throws DbException;

View File

@@ -28,6 +28,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.logging.Logger;
@@ -70,6 +71,21 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
return CLIENT_ID;
}
@Override
public GroupId addPrivateGroup(String name) throws DbException {
PrivateGroup group;
Transaction txn = db.startTransaction(false);
try {
LocalAuthor a = identityManager.getLocalAuthor(txn);
group = privateGroupFactory.createPrivateGroup(name, a);
db.addGroup(txn, group.getGroup());
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return group.getId();
}
@Override
public void removePrivateGroup(GroupId g) throws DbException {
@@ -137,7 +153,24 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
@NotNull
@Override
public Collection<PrivateGroup> getPrivateGroups() throws DbException {
return Collections.emptyList();
Collection<Group> groups;
Transaction txn = db.startTransaction(true);
try {
groups = db.getGroups(txn, getClientId());
txn.setComplete();
} finally {
db.endTransaction(txn);
}
try {
Collection<PrivateGroup> privateGroups =
new ArrayList<PrivateGroup>(groups.size());
for (Group g : groups) {
privateGroups.add(privateGroupFactory.parsePrivateGroup(g));
}
return privateGroups;
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override