mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-14 03:39:05 +01:00
UI for introducing two contacts to each other
Show system notification for successful introductions
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
package org.briarproject.android.introduction;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.transition.ChangeBounds;
|
||||
import android.transition.Fade;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.AndroidComponent;
|
||||
import org.briarproject.android.contact.ContactListAdapter;
|
||||
import org.briarproject.android.contact.ContactListItem;
|
||||
import org.briarproject.android.contact.ConversationItem;
|
||||
import org.briarproject.android.contact.ConversationMessageItem;
|
||||
import org.briarproject.android.fragment.BaseFragment;
|
||||
import org.briarproject.android.util.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.identity.AuthorId;
|
||||
import org.briarproject.api.identity.IdentityManager;
|
||||
import org.briarproject.api.identity.LocalAuthor;
|
||||
import org.briarproject.api.introduction.IntroductionManager;
|
||||
import org.briarproject.api.introduction.IntroductionMessage;
|
||||
import org.briarproject.api.messaging.MessagingManager;
|
||||
import org.briarproject.api.messaging.PrivateMessageHeader;
|
||||
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;
|
||||
|
||||
public class ContactChooserFragment extends BaseFragment {
|
||||
|
||||
public final static String TAG = "ContactChooserFragment";
|
||||
private IntroductionActivity introductionActivity;
|
||||
private BriarRecyclerView list;
|
||||
private ContactListAdapter adapter;
|
||||
private int contactId;
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ContactChooserFragment.class.getName());
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
protected volatile Contact c1;
|
||||
@Inject
|
||||
protected volatile ContactManager contactManager;
|
||||
@Inject
|
||||
protected volatile IdentityManager identityManager;
|
||||
@Inject
|
||||
protected volatile MessagingManager messagingManager;
|
||||
@Inject
|
||||
protected volatile IntroductionManager introductionManager;
|
||||
@Inject
|
||||
protected volatile ConnectionRegistry connectionRegistry;
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
try {
|
||||
introductionActivity = (IntroductionActivity) context;
|
||||
} catch (ClassCastException e) {
|
||||
throw new java.lang.InstantiationError(
|
||||
"This fragment is only meant to be attached to the IntroductionActivity");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(AndroidComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
View contentView =
|
||||
inflater.inflate(R.layout.introduction_contact_chooser,
|
||||
container, false);
|
||||
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
setExitTransition(new Fade());
|
||||
}
|
||||
|
||||
ContactListAdapter.OnItemClickListener onItemClickListener =
|
||||
new ContactListAdapter.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(View view, ContactListItem item) {
|
||||
if (c1 == null) {
|
||||
Toast.makeText(getActivity(),
|
||||
R.string.introduction_error,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
Contact c2 = item.getContact();
|
||||
if (!c1.getLocalAuthorId()
|
||||
.equals(c2.getLocalAuthorId())) {
|
||||
warnAboutDifferentIdentities(view, c1, c2);
|
||||
} else {
|
||||
introductionActivity.showMessageScreen(view, c1, c2);
|
||||
}
|
||||
}
|
||||
};
|
||||
adapter =
|
||||
new ContactListAdapter(getActivity(), onItemClickListener, true);
|
||||
|
||||
list = (BriarRecyclerView) contentView.findViewById(R.id.contactList);
|
||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
list.setAdapter(adapter);
|
||||
list.setEmptyText(getString(R.string.no_contacts));
|
||||
|
||||
contactId = introductionActivity.getContactId();
|
||||
|
||||
return contentView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
loadContacts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
private void loadContacts() {
|
||||
introductionActivity.runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
List<ContactListItem> contacts =
|
||||
new ArrayList<ContactListItem>();
|
||||
AuthorId localAuthorId= null;
|
||||
for (Contact c : contactManager.getActiveContacts()) {
|
||||
if (c.getId().getInt() == contactId) {
|
||||
c1 = c;
|
||||
localAuthorId = c1.getLocalAuthorId();
|
||||
} else {
|
||||
ContactId id = c.getId();
|
||||
GroupId groupId =
|
||||
messagingManager.getConversationId(id);
|
||||
Collection<ConversationItem> messages =
|
||||
getMessages(id);
|
||||
boolean connected =
|
||||
connectionRegistry.isConnected(c.getId());
|
||||
LocalAuthor localAuthor = identityManager
|
||||
.getLocalAuthor(c.getLocalAuthorId());
|
||||
contacts.add(new ContactListItem(c, localAuthor,
|
||||
connected, groupId, messages));
|
||||
}
|
||||
}
|
||||
displayContacts(localAuthorId, contacts);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayContacts(final AuthorId localAuthorId,
|
||||
final List<ContactListItem> contacts) {
|
||||
introductionActivity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
adapter.setLocalAuthor(localAuthorId);
|
||||
adapter.clear();
|
||||
if (contacts.size() == 0) list.showData();
|
||||
else adapter.addAll(contacts);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void warnAboutDifferentIdentities(final View view, final Contact c1,
|
||||
final Contact c2) {
|
||||
|
||||
DialogInterface.OnClickListener okListener =
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
introductionActivity.showMessageScreen(view, c1, c2);
|
||||
}
|
||||
};
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(),
|
||||
R.style.BriarDialogTheme);
|
||||
builder.setTitle(getString(
|
||||
R.string.introduction_warn_different_identities_title));
|
||||
builder.setMessage(getString(
|
||||
R.string.introduction_warn_different_identities_text));
|
||||
builder.setPositiveButton(R.string.dialog_button_introduce, okListener);
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
/** This needs to be called from the DbThread */
|
||||
private Collection<ConversationItem> getMessages(ContactId id)
|
||||
throws DbException {
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
Collection<ConversationItem> messages =
|
||||
new ArrayList<ConversationItem>();
|
||||
|
||||
Collection<PrivateMessageHeader> headers =
|
||||
messagingManager.getMessageHeaders(id);
|
||||
for (PrivateMessageHeader h : headers) {
|
||||
messages.add(new ConversationMessageItem(h));
|
||||
}
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading message headers took " + duration + " ms");
|
||||
|
||||
Collection<IntroductionMessage> introductions =
|
||||
introductionManager
|
||||
.getIntroductionMessages(id);
|
||||
for (IntroductionMessage m : introductions) {
|
||||
messages.add(ConversationItem.from(m));
|
||||
}
|
||||
duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading introduction messages took " + duration + " ms");
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.briarproject.android.introduction;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.transition.ChangeBounds;
|
||||
import android.transition.Fade;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.AndroidComponent;
|
||||
import org.briarproject.android.BriarActivity;
|
||||
import org.briarproject.android.fragment.BaseFragment;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
|
||||
public class IntroductionActivity extends BriarActivity implements
|
||||
BaseFragment.BaseFragmentListener {
|
||||
|
||||
public static final String CONTACT_ID = "briar.CONTACT_ID";
|
||||
private int contactId;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent intent = getIntent();
|
||||
contactId = intent.getIntExtra(CONTACT_ID, -1);
|
||||
|
||||
setContentView(R.layout.activity_introduction);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
ContactChooserFragment chooserFragment =
|
||||
new ContactChooserFragment();
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.introductionContainer, chooserFragment).commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(AndroidComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showLoadingScreen(boolean isBlocking, int stringId) {
|
||||
// this is handled by the recycler view in ContactChooserFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideLoadingScreen() {
|
||||
// this is handled by the recycler view in ContactChooserFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
// Handle presses on the action bar items
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
if (fm.getBackStackEntryCount() == 1) {
|
||||
fm.popBackStack();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
public int getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
public void showMessageScreen(final View view, final Contact c1,
|
||||
final Contact c2) {
|
||||
|
||||
IntroductionMessageFragment messageFragment =
|
||||
IntroductionMessageFragment
|
||||
.newInstance(c1.getId().getInt(), c2.getId().getInt());
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
messageFragment.setSharedElementEnterTransition(new ChangeBounds());
|
||||
messageFragment.setEnterTransition(new Fade());
|
||||
messageFragment.setSharedElementReturnTransition(new ChangeBounds());
|
||||
}
|
||||
|
||||
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)
|
||||
.addSharedElement(view, "avatar")
|
||||
.replace(R.id.introductionContainer, messageFragment,
|
||||
ContactChooserFragment.TAG)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package org.briarproject.android.introduction;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.AndroidComponent;
|
||||
import org.briarproject.android.fragment.BaseFragment;
|
||||
import org.briarproject.api.FormatException;
|
||||
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.introduction.IntroductionManager;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import de.hdodenhof.circleimageview.CircleImageView;
|
||||
import im.delight.android.identicons.IdenticonDrawable;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
public class IntroductionMessageFragment extends BaseFragment {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(IntroductionMessageFragment.class.getName());
|
||||
|
||||
public final static String TAG = "ContactChooserFragment";
|
||||
private IntroductionActivity introductionActivity;
|
||||
private ViewHolder ui;
|
||||
|
||||
private final static String CONTACT_ID_1 = "contact1";
|
||||
private final static String CONTACT_ID_2 = "contact2";
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
private volatile boolean introductionWasMade = false;
|
||||
@Inject
|
||||
protected volatile ContactManager contactManager;
|
||||
@Inject
|
||||
protected volatile IntroductionManager introductionManager;
|
||||
|
||||
public static IntroductionMessageFragment newInstance(int contactId1,
|
||||
int contactId2) {
|
||||
IntroductionMessageFragment f = new IntroductionMessageFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(CONTACT_ID_1, contactId1);
|
||||
args.putInt(CONTACT_ID_2, contactId2);
|
||||
f.setArguments(args);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
try {
|
||||
introductionActivity = (IntroductionActivity) context;
|
||||
} catch (ClassCastException e) {
|
||||
throw new java.lang.InstantiationError(
|
||||
"This fragment is only meant to be attached to the IntroductionActivity");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(AndroidComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
// change toolbar text
|
||||
ActionBar actionBar = introductionActivity.getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setTitle(R.string.introduction_message_title);
|
||||
}
|
||||
|
||||
// inflate view
|
||||
View v =
|
||||
inflater.inflate(R.layout.introduction_message, container,
|
||||
false);
|
||||
|
||||
// show progress bar until contacts have been loaded
|
||||
ui = new ViewHolder(v);
|
||||
ui.text.setVisibility(View.GONE);
|
||||
ui.button.setEnabled(false);
|
||||
|
||||
// get contact IDs from fragment arguments
|
||||
int contactId1 = getArguments().getInt(CONTACT_ID_1, -1);
|
||||
int contactId2 = getArguments().getInt(CONTACT_ID_2, -1);
|
||||
if (contactId1 == -1 || contactId2 == -1) {
|
||||
throw new java.lang.InstantiationError(
|
||||
"You need to use newInstance() to instantiate");
|
||||
}
|
||||
|
||||
// get contacts and then show view
|
||||
prepareToSetUpViews(contactId1, contactId2);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
private void prepareToSetUpViews(final int contactId1,
|
||||
final int contactId2) {
|
||||
introductionActivity.runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
Contact c1 = contactManager
|
||||
.getContact(new ContactId(contactId1));
|
||||
Contact c2 = contactManager
|
||||
.getContact(new ContactId(contactId2));
|
||||
setUpViews(c1, c2);
|
||||
} catch (DbException e) {
|
||||
// TODO
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setUpViews(final Contact c1, final Contact c2) {
|
||||
introductionActivity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
// set avatars
|
||||
ui.avatar1.setImageDrawable(new IdenticonDrawable(
|
||||
c1.getAuthor().getId().getBytes()));
|
||||
ui.avatar2.setImageDrawable(new IdenticonDrawable(
|
||||
c2.getAuthor().getId().getBytes()));
|
||||
|
||||
// set introduction text
|
||||
ui.text.setText(String.format(
|
||||
getString(R.string.introduction_message_text),
|
||||
c1.getAuthor().getName(), c2.getAuthor().getName()));
|
||||
|
||||
// set button action
|
||||
ui.button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
onButtonClick(c1, c2);
|
||||
}
|
||||
});
|
||||
|
||||
// hide progress bar and show views
|
||||
ui.progressBar.setVisibility(View.GONE);
|
||||
ui.text.setVisibility(View.VISIBLE);
|
||||
ui.button.setEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onButtonClick(final Contact c1, final Contact c2) {
|
||||
String msg = ui.message.getText().toString();
|
||||
makeIntroduction(c1, c2, msg);
|
||||
}
|
||||
|
||||
private void makeIntroduction(final Contact c1, final Contact c2,
|
||||
final String msg) {
|
||||
introductionActivity.runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
// prevent double introductions
|
||||
if (introductionWasMade) return;
|
||||
|
||||
// actually make the introduction
|
||||
try {
|
||||
introductionManager.makeIntroduction(c1, c2, msg);
|
||||
introductionWasMade = true;
|
||||
postIntroduction(false);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
postIntroduction(true);
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
postIntroduction(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void postIntroduction(final boolean error) {
|
||||
introductionActivity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
introductionActivity.hideSoftKeyboard(ui.message);
|
||||
if (error) {
|
||||
Toast.makeText(introductionActivity,
|
||||
R.string.introduction_error, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
introductionActivity.setResult(Activity.RESULT_CANCELED);
|
||||
} else {
|
||||
introductionActivity.setResult(Activity.RESULT_OK);
|
||||
}
|
||||
introductionActivity.finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class ViewHolder {
|
||||
ProgressBar progressBar;
|
||||
ViewGroup header;
|
||||
CircleImageView avatar1;
|
||||
CircleImageView avatar2;
|
||||
TextView text;
|
||||
EditText message;
|
||||
Button button;
|
||||
|
||||
ViewHolder(View v) {
|
||||
progressBar = (ProgressBar) v.findViewById(R.id.progressBar);
|
||||
header = (ViewGroup) v.findViewById(R.id.introductionHeader);
|
||||
avatar1 = (CircleImageView) v.findViewById(R.id.avatarContact1);
|
||||
avatar2 = (CircleImageView) v.findViewById(R.id.avatarContact2);
|
||||
text = (TextView) v.findViewById(R.id.introductionText);
|
||||
message = (EditText) v.findViewById(R.id.introductionMessageView);
|
||||
button = (Button) v.findViewById(R.id.makeIntroductionButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user