[android] rough sketch of UI for adding contacts remotely

This commit is contained in:
Torsten Grote
2018-09-24 18:33:15 -03:00
parent df5ac59fc9
commit 58ffc6e761
37 changed files with 1818 additions and 40 deletions

View File

@@ -15,8 +15,12 @@ import org.briarproject.briar.android.blog.ReblogFragment;
import org.briarproject.briar.android.blog.RssFeedImportActivity;
import org.briarproject.briar.android.blog.RssFeedManageActivity;
import org.briarproject.briar.android.blog.WriteBlogPostActivity;
import org.briarproject.briar.android.contact.add.remote.AddContactActivity;
import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment;
import org.briarproject.briar.android.contact.ContactListFragment;
import org.briarproject.briar.android.contact.ContactModule;
import org.briarproject.briar.android.contact.add.remote.NicknameFragment;
import org.briarproject.briar.android.contact.add.remote.PendingRequestsActivity;
import org.briarproject.briar.android.conversation.AliasDialogFragment;
import org.briarproject.briar.android.conversation.ConversationActivity;
import org.briarproject.briar.android.conversation.ImageActivity;
@@ -168,6 +172,10 @@ public interface ActivityComponent {
void inject(UnlockActivity activity);
void inject(AddContactActivity activity);
void inject(PendingRequestsActivity activity);
// Fragments
void inject(AuthorNameFragment fragment);
@@ -191,6 +199,10 @@ public interface ActivityComponent {
void inject(KeyAgreementFragment fragment);
void inject(LinkExchangeFragment fragment);
void inject(NicknameFragment fragment);
void inject(ContactChooserFragment fragment);
void inject(ShareForumFragment fragment);

View File

@@ -3,22 +3,24 @@ package org.briarproject.briar.android.contact;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.UiThread;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.util.Pair;
import android.support.v7.widget.LinearLayoutManager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.event.Event;
@@ -32,6 +34,8 @@ import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import org.briarproject.briar.android.contact.add.remote.AddContactActivity;
import org.briarproject.briar.android.contact.add.remote.PendingRequestsActivity;
import org.briarproject.briar.android.conversation.ConversationActivity;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.keyagreement.ContactExchangeActivity;
@@ -49,11 +53,16 @@ import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
import static android.os.Build.VERSION.SDK_INT;
import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE;
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
import static android.support.v4.view.ViewCompat.getTransitionName;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
@@ -62,7 +71,8 @@ import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ContactListFragment extends BaseFragment implements EventListener {
public class ContactListFragment extends BaseFragment implements EventListener,
OnMenuItemClickListener {
public static final String TAG = ContactListFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
@@ -76,6 +86,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
private ContactListAdapter adapter;
private BriarRecyclerView list;
private Snackbar snackbar;
// Fields that are accessed from background threads must be volatile
@Inject
@@ -107,7 +118,12 @@ public class ContactListFragment extends BaseFragment implements EventListener {
@Nullable Bundle savedInstanceState) {
requireNonNull(getActivity()).setTitle(R.string.contact_list_button);
View contentView = inflater.inflate(R.layout.list, container, false);
View contentView =
inflater.inflate(R.layout.fragment_contact_list, container,
false);
FabSpeedDial speedDialView = contentView.findViewById(R.id.speedDial);
speedDialView.addOnMenuItemClickListener(this);
OnContactClickListener<ContactListItem> onContactClickListener =
(view, item) -> {
@@ -146,26 +162,30 @@ public class ContactListFragment extends BaseFragment implements EventListener {
list.setEmptyText(getString(R.string.no_contacts));
list.setEmptyAction(getString(R.string.no_contacts_action));
// TODO UiUtils helper method?
snackbar = Snackbar.make(contentView,
R.string.pending_contact_requests_snackbar, LENGTH_INDEFINITE);
snackbar.getView().setBackgroundResource(R.color.briar_primary);
snackbar.setAction(R.string.show, v -> startActivity(
new Intent(getContext(), PendingRequestsActivity.class)));
snackbar.setActionTextColor(ContextCompat
.getColor(getContext(), R.color.briar_button_text_positive));
return contentView;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.contact_list_actions, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_add_contact:
public void onMenuItemClick(FloatingActionButton fab, @Nullable TextView v,
int itemId) {
switch (itemId) {
case R.id.action_add_contact_nearby:
Intent intent =
new Intent(getContext(), ContactExchangeActivity.class);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
return;
case R.id.action_add_contact_remotely:
startActivity(
new Intent(getContext(), AddContactActivity.class));
}
}
@@ -176,9 +196,24 @@ public class ContactListFragment extends BaseFragment implements EventListener {
notificationManager.clearAllContactNotifications();
notificationManager.clearAllIntroductionNotifications();
loadContacts();
checkForPendingContacts();
list.startPeriodicUpdate();
}
private void checkForPendingContacts() {
listener.runOnDbThread(() -> {
try {
if (contactManager.getPendingContacts().isEmpty()) {
runOnUiThreadUnlessDestroyed(() -> snackbar.dismiss());
} else {
runOnUiThreadUnlessDestroyed(() -> snackbar.show());
}
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
@Override
public void onStop() {
super.onStop();
@@ -232,6 +267,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
if (e instanceof ContactAddedEvent) {
LOG.info("Contact added, reloading");
loadContacts();
checkForPendingContacts();
} else if (e instanceof ContactConnectedEvent) {
setConnected(((ContactConnectedEvent) e).getContactId(), true);
} else if (e instanceof ContactDisconnectedEvent) {
@@ -245,6 +281,13 @@ public class ContactListFragment extends BaseFragment implements EventListener {
(ConversationMessageReceivedEvent) e;
ConversationMessageHeader h = p.getMessageHeader();
updateItem(p.getContactId(), h);
} else if (e instanceof PendingContactStateChangedEvent) {
PendingContactStateChangedEvent pe =
(PendingContactStateChangedEvent) e;
// only re-check pending contacts for initial state
if (pe.getPendingContactState() == WAITING_FOR_CONNECTION) {
checkForPendingContacts();
}
}
}

View File

@@ -0,0 +1,99 @@
package org.briarproject.briar.android.contact.add.remote;
import android.arch.lifecycle.ViewModelProvider;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.view.MenuItem;
import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.content.Intent.ACTION_SEND;
import static android.content.Intent.ACTION_VIEW;
import static android.content.Intent.EXTRA_TEXT;
import static android.widget.Toast.LENGTH_LONG;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class AddContactActivity extends BriarActivity implements
BaseFragmentListener {
@Inject
ViewModelProvider.Factory viewModelFactory;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onCreate(@Nullable Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_fragment_container);
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setDisplayHomeAsUpEnabled(true);
}
AddContactViewModel viewModel =
ViewModelProviders.of(this, viewModelFactory)
.get(AddContactViewModel.class);
viewModel.getRemoteLinkEntered().observe(this, entered -> {
if (entered != null && entered) {
NicknameFragment f = new NicknameFragment();
showNextFragment(f);
}
});
Intent i = getIntent();
if (i != null) {
String action = i.getAction();
if (ACTION_SEND.equals(action) || ACTION_VIEW.equals(action)) {
String text = i.getStringExtra(EXTRA_TEXT);
if (text != null) {
if (viewModel.isValidRemoteContactLink(text)) {
viewModel.setRemoteContactLink(text);
} else {
Toast.makeText(this, R.string.invalid_link, LENGTH_LONG)
.show();
}
}
String uri = i.getDataString();
if (uri != null) {
if (viewModel.isValidRemoteContactLink(uri)) {
viewModel.setRemoteContactLink(uri);
} else {
Toast.makeText(this, R.string.invalid_link, LENGTH_LONG)
.show();
}
}
}
}
if (state == null) {
showInitialFragment(new LinkExchangeFragment());
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

View File

@@ -0,0 +1,89 @@
package org.briarproject.briar.android.contact.add.remote;
import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Logger.getLogger;
@NotNullByDefault
public class AddContactViewModel extends AndroidViewModel {
private static Logger LOG = getLogger(AddContactViewModel.class.getName());
private final ContactManager contactManager;
@DatabaseExecutor
private final Executor dbExecutor;
private final MutableLiveData<String> ourLink = new MutableLiveData<>();
private final MutableLiveData<Boolean> remoteLinkEntered =
new MutableLiveData<>();
@Nullable
private String remoteContactLink;
@Inject
public AddContactViewModel(@NonNull Application application,
ContactManager contactManager,
@DatabaseExecutor Executor dbExecutor) {
super(application);
this.contactManager = contactManager;
this.dbExecutor = dbExecutor;
loadOurLink();
}
private void loadOurLink() {
dbExecutor.execute(() -> {
try {
ourLink.postValue(contactManager.getRemoteContactLink());
} catch (DbException e) {
throw new AssertionError(e);
}
});
}
LiveData<String> getOurLink() {
return ourLink;
}
void setRemoteContactLink(String link) {
remoteContactLink = link;
}
@Nullable
String getRemoteContactLink() {
return remoteContactLink;
}
boolean isValidRemoteContactLink(@Nullable CharSequence link) {
return link != null &&
contactManager.isValidRemoteContactLink(link.toString());
}
LiveData<Boolean> getRemoteLinkEntered() {
return remoteLinkEntered;
}
void onRemoteLinkEntered() {
if (remoteContactLink == null) throw new IllegalStateException();
remoteLinkEntered.setValue(true);
}
void addContact(String nickname) {
if (remoteContactLink == null) throw new IllegalStateException();
contactManager.addRemoteContactRequest(remoteContactLink, nickname);
}
}

View File

@@ -0,0 +1,168 @@
package org.briarproject.briar.android.contact.add.remote;
import android.arch.lifecycle.ViewModelProvider;
import android.arch.lifecycle.ViewModelProviders;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.os.Bundle;
import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.ShareCompat.IntentBuilder;
import android.text.Editable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import java.util.regex.Matcher;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.content.Context.CLIPBOARD_SERVICE;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Objects.requireNonNull;
import static org.briarproject.bramble.api.contact.ContactManager.LINK_REGEX;
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class LinkExchangeFragment extends BaseFragment {
private static final String TAG = LinkExchangeFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private AddContactViewModel viewModel;
private ClipboardManager clipboard;
private TextInputLayout linkInputLayout;
private TextInputEditText linkInput;
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (getActivity() == null || getContext() == null) return null;
viewModel = ViewModelProviders.of(getActivity(), viewModelFactory)
.get(AddContactViewModel.class);
View v = inflater.inflate(R.layout.fragment_link_exchange,
container, false);
linkInputLayout = v.findViewById(R.id.linkInputLayout);
linkInput = v.findViewById(R.id.linkInput);
if (viewModel.getRemoteContactLink() != null) {
linkInput.setText(viewModel.getRemoteContactLink());
}
clipboard = (ClipboardManager) requireNonNull(
getContext().getSystemService(CLIPBOARD_SERVICE));
Button pasteButton = v.findViewById(R.id.pasteButton);
pasteButton.setOnClickListener(view -> {
ClipData clipData = clipboard.getPrimaryClip();
if (clipData != null)
linkInput.setText(clipData.getItemAt(0).getText());
});
observeOnce(viewModel.getOurLink(), this, this::onOwnLinkLoaded);
return v;
}
private void onOwnLinkLoaded(String link) {
View v = requireNonNull(getView());
TextView linkView = v.findViewById(R.id.linkView);
linkView.setText(link);
Button copyButton = v.findViewById(R.id.copyButton);
ClipData clip = ClipData.newPlainText(
getString(R.string.link_clip_label), link);
copyButton.setOnClickListener(view -> {
clipboard.setPrimaryClip(clip);
Toast.makeText(getContext(), R.string.link_copied_toast,
LENGTH_SHORT).show();
});
copyButton.setEnabled(true);
Button shareButton = v.findViewById(R.id.shareButton);
shareButton.setOnClickListener(view ->
IntentBuilder.from(requireNonNull(getActivity()))
.setText(link)
.setType("text/plain")
.startChooser());
shareButton.setEnabled(true);
Button continueButton = v.findViewById(R.id.addButton);
continueButton.setOnClickListener(view -> onContinueButtonClicked());
continueButton.setEnabled(true);
}
private boolean isInputError() {
Editable linkText = linkInput.getText();
boolean briarLink = viewModel.isValidRemoteContactLink(linkText);
if (!briarLink) {
if (linkText == null || linkText.length() == 0) {
linkInputLayout.setError(getString(R.string.missing_link));
} else {
linkInputLayout.setError(getString(R.string.invalid_link));
}
linkInput.requestFocus();
return true;
} else linkInputLayout.setError(null);
String link = getLink();
boolean isOurLink = link != null &&
("briar://" + link).equals(viewModel.getOurLink().getValue());
if (isOurLink) {
linkInputLayout.setError(getString(R.string.own_link_error));
linkInput.requestFocus();
return true;
} else linkInputLayout.setError(null);
return false;
}
@Nullable
private String getLink() {
CharSequence link = linkInput.getText();
if (link == null) return null;
Matcher matcher = LINK_REGEX.matcher(link);
if (matcher.matches()) // needs to be called before groups become available
return matcher.group(2);
else
return null;
}
private void onContinueButtonClicked() {
if (isInputError()) return;
String linkText = getLink();
if (linkText == null) throw new AssertionError();
viewModel.setRemoteContactLink(linkText);
viewModel.onRemoteLinkEntered();
}
}

View File

@@ -0,0 +1,98 @@
package org.briarproject.briar.android.contact.add.remote;
import android.arch.lifecycle.ViewModelProvider;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.MainThread;
import android.support.annotation.UiThread;
import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static java.util.Objects.requireNonNull;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class NicknameFragment extends BaseFragment {
private static final String TAG = NicknameFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private AddContactViewModel viewModel;
private TextInputLayout contactNameLayout;
private TextInputEditText contactNameInput;
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (getActivity() == null || getContext() == null) return null;
View v = inflater.inflate(R.layout.fragment_nickname,
container, false);
viewModel = ViewModelProviders.of(getActivity(), viewModelFactory)
.get(AddContactViewModel.class);
Button addButton = v.findViewById(R.id.addButton);
addButton.setOnClickListener(view -> onAddButtonClicked());
contactNameLayout = v.findViewById(R.id.contactNameLayout);
contactNameInput = v.findViewById(R.id.contactNameInput);
return v;
}
@MainThread
@UiThread
private boolean isInputError() {
boolean validContactName = contactNameInput.getText() != null &&
contactNameInput.getText().toString().trim().length() > 0;
if (!validContactName) {
contactNameLayout.setError(getString(R.string.nickname_missing));
contactNameInput.requestFocus();
return true;
} else contactNameLayout.setError(null);
return false;
}
private void onAddButtonClicked() {
if (isInputError()) return;
String name = requireNonNull(contactNameInput.getText()).toString();
viewModel.addContact(name);
Intent intent =
new Intent(getActivity(), PendingRequestsActivity.class);
startActivity(intent);
finish();
}
}

View File

@@ -0,0 +1,125 @@
package org.briarproject.briar.android.contact.add.remote;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.LinearLayoutManager;
import android.view.MenuItem;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.view.BriarRecyclerView;
import java.util.Collection;
import javax.annotation.Nullable;
import javax.inject.Inject;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class PendingRequestsActivity extends BriarActivity
implements EventListener {
@Inject
ContactManager contactManager;
@Inject
EventBus eventBus;
private PendingRequestsAdapter adapter;
private BriarRecyclerView list;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onCreate(@Nullable Bundle state) {
super.onCreate(state);
setContentView(R.layout.list);
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setDisplayHomeAsUpEnabled(true);
}
adapter = new PendingRequestsAdapter(this, PendingContact.class);
list = findViewById(R.id.list);
list.setLayoutManager(new LinearLayoutManager(this));
list.setAdapter(adapter);
}
@Override
public void onStart() {
super.onStart();
eventBus.addListener(this);
list.startPeriodicUpdate();
runOnDbThread(() -> {
try {
Collection<PendingContact> contacts =
contactManager.getPendingContacts();
addPendingContacts(contacts);
} catch (DbException e) {
e.printStackTrace();
}
});
}
@Override
protected void onStop() {
super.onStop();
list.stopPeriodicUpdate();
adapter.clear();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactAddedEvent) {
runOnDbThread(() -> {
try {
Contact contact = contactManager
.getContact(((ContactAddedEvent) e).getContactId());
runOnUiThreadUnlessDestroyed(() -> {
adapter.remove(contact);
if (adapter.isEmpty()) finish();
});
} catch (DbException e1) {
e1.printStackTrace();
}
});
}
}
private void addPendingContacts(Collection<PendingContact> contacts) {
runOnUiThreadUnlessDestroyed(() -> {
if (contacts.isEmpty()) {
list.showData();
} else {
adapter.addAll(contacts);
}
});
}
}

View File

@@ -0,0 +1,65 @@
package org.briarproject.briar.android.contact.add.remote;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.util.BriarAdapter;
@NotNullByDefault
public class PendingRequestsAdapter extends
BriarAdapter<PendingContact, PendingRequestsViewHolder> {
public PendingRequestsAdapter(Context ctx, Class<PendingContact> c) {
super(ctx, c);
}
@Override
public PendingRequestsViewHolder onCreateViewHolder(
ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_pending_contact, viewGroup, false);
return new PendingRequestsViewHolder(v);
}
@Override
public void onBindViewHolder(
PendingRequestsViewHolder pendingRequestsViewHolder, int i) {
pendingRequestsViewHolder.bind(items.get(i));
}
@Override
public int compare(PendingContact item1, PendingContact item2) {
return (int) (item1.getTimestamp() - item2.getTimestamp());
}
@Override
public boolean areContentsTheSame(PendingContact item1,
PendingContact item2) {
return item1.getAlias().equals(item2.getAlias()) &&
item1.getTimestamp() == item2.getTimestamp();
}
@Override
public boolean areItemsTheSame(PendingContact item1,
PendingContact item2) {
return item1.getAlias().equals(item2.getAlias()) &&
item1.getTimestamp() == item2.getTimestamp();
}
// TODO use PendingContactId
public void remove(Contact contact) {
for (int i = 0; i < items.size(); i++) {
if (items.get(i).getAlias().equals(contact.getAuthor().getName())) {
items.removeItemAt(i);
return;
}
}
}
}

View File

@@ -0,0 +1,64 @@
package org.briarproject.briar.android.contact.add.remote;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.View;
import android.widget.TextView;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.view.TextAvatarView;
import static org.briarproject.bramble.util.StringUtils.toUtf8;
import static org.briarproject.briar.android.util.UiUtils.formatDate;
@NotNullByDefault
public class PendingRequestsViewHolder extends ViewHolder {
private final TextAvatarView avatar;
private final TextView name;
private final TextView time;
private final TextView status;
public PendingRequestsViewHolder(View v) {
super(v);
avatar = v.findViewById(R.id.avatar);
name = v.findViewById(R.id.name);
time = v.findViewById(R.id.time);
status = v.findViewById(R.id.status);
}
public void bind(PendingContact item) {
avatar.setText(item.getAlias());
avatar.setBackgroundBytes(toUtf8(item.getAlias() + item.getTimestamp()));
name.setText(item.getAlias());
time.setText(formatDate(time.getContext(), item.getTimestamp()));
int color = ContextCompat
.getColor(status.getContext(), R.color.briar_green);
switch (item.getState()) {
case WAITING_FOR_CONNECTION:
color = ContextCompat
.getColor(status.getContext(), R.color.briar_yellow);
status.setText(R.string.waiting_for_contact_to_come_online);
break;
case CONNECTED:
status.setText(R.string.connecting);
break;
case ADDING_CONTACT:
status.setText(R.string.adding_contact);
break;
case FAILED:
// TODO add remove button
color = ContextCompat
.getColor(status.getContext(), R.color.briar_red);
status.setText(R.string.adding_contact_failed);
break;
default:
throw new IllegalStateException();
}
status.setTextColor(color);
}
}

View File

@@ -3,6 +3,7 @@ package org.briarproject.briar.android.viewmodel;
import android.arch.lifecycle.ViewModel;
import android.arch.lifecycle.ViewModelProvider;
import org.briarproject.briar.android.contact.add.remote.AddContactViewModel;
import org.briarproject.briar.android.conversation.ConversationViewModel;
import org.briarproject.briar.android.conversation.ImageViewModel;
@@ -27,6 +28,12 @@ public abstract class ViewModelModule {
abstract ViewModel bindImageViewModel(
ImageViewModel imageViewModel);
@Binds
@IntoMap
@ViewModelKey(AddContactViewModel.class)
abstract ViewModel bindAddContactViewModel(
AddContactViewModel addContactViewModel);
@Binds
@Singleton
abstract ViewModelProvider.Factory bindViewModelFactory(