mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 03:09:04 +01:00
[android] rough sketch of UI for adding contacts remotely
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user