mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-16 20:59:54 +01:00
Merge branch '1565-duplicate-remote-contacts' into 'master'
UX for handling duplicate handshake links Closes #1565 See merge request briar/briar!1173
This commit is contained in:
@@ -4,8 +4,10 @@ import org.briarproject.bramble.api.FormatException;
|
|||||||
import org.briarproject.bramble.api.Pair;
|
import org.briarproject.bramble.api.Pair;
|
||||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.db.ContactExistsException;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
|
import org.briarproject.bramble.api.db.PendingContactExistsException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
@@ -117,9 +119,14 @@ public interface ContactManager {
|
|||||||
* @throws FormatException If the link is invalid
|
* @throws FormatException If the link is invalid
|
||||||
* @throws GeneralSecurityException If the pending contact's handshake
|
* @throws GeneralSecurityException If the pending contact's handshake
|
||||||
* public key is invalid
|
* public key is invalid
|
||||||
|
* @throws ContactExistsException If a contact with the same handshake
|
||||||
|
* public key already exists
|
||||||
|
* @throws PendingContactExistsException If a pending contact with the same
|
||||||
|
* handshake public key already exists
|
||||||
*/
|
*/
|
||||||
PendingContact addPendingContact(String link, String alias)
|
PendingContact addPendingContact(String link, String alias)
|
||||||
throws DbException, FormatException, GeneralSecurityException;
|
throws DbException, FormatException, GeneralSecurityException,
|
||||||
|
ContactExistsException, PendingContactExistsException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the pending contact with the given ID.
|
* Returns the pending contact with the given ID.
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
/**
|
/**
|
||||||
* Stores a pending contact.
|
* Stores a pending contact.
|
||||||
*/
|
*/
|
||||||
void addPendingContact(Transaction txn, PendingContact p)
|
void addPendingContact(Transaction txn, PendingContact p, AuthorId local)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
package org.briarproject.bramble.api.db;
|
package org.briarproject.bramble.api.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.PendingContact;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown when a duplicate pending contact is added to the database. This
|
* Thrown when a duplicate pending contact is added to the database. This
|
||||||
* exception may occur due to concurrent updates and does not indicate a
|
* exception may occur due to concurrent updates and does not indicate a
|
||||||
* database error.
|
* database error.
|
||||||
*/
|
*/
|
||||||
public class PendingContactExistsException extends DbException {
|
public class PendingContactExistsException extends DbException {
|
||||||
|
|
||||||
|
private final PendingContact pendingContact;
|
||||||
|
|
||||||
|
public PendingContactExistsException(PendingContact pendingContact) {
|
||||||
|
this.pendingContact = pendingContact;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PendingContact getPendingContact() {
|
||||||
|
return pendingContact;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,7 +139,8 @@ class ContactManagerImpl implements ContactManager, EventListener {
|
|||||||
pendingContactFactory.createPendingContact(link, alias);
|
pendingContactFactory.createPendingContact(link, alias);
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
db.addPendingContact(txn, p);
|
AuthorId local = identityManager.getLocalAuthor(txn).getId();
|
||||||
|
db.addPendingContact(txn, p, local);
|
||||||
KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn);
|
KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn);
|
||||||
keyManager.addPendingContact(txn, p.getId(), p.getPublicKey(),
|
keyManager.addPendingContact(txn, p.getId(), p.getPublicKey(),
|
||||||
ourKeyPair);
|
ourKeyPair);
|
||||||
|
|||||||
@@ -267,6 +267,16 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
Collection<ContactId> getContacts(T txn, AuthorId local) throws DbException;
|
Collection<ContactId> getContacts(T txn, AuthorId local) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the contact with the given {@code handshakePublicKey}
|
||||||
|
* for the given local pseudonym or {@code null} if none exists.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
Contact getContact(T txn, PublicKey handshakePublicKey, AuthorId local)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the group with the given ID.
|
* Returns the group with the given ID.
|
||||||
* <p/>
|
* <p/>
|
||||||
|
|||||||
@@ -291,12 +291,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addPendingContact(Transaction transaction, PendingContact p)
|
public void addPendingContact(Transaction transaction, PendingContact p,
|
||||||
throws DbException {
|
AuthorId local) throws DbException {
|
||||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (db.containsPendingContact(txn, p.getId()))
|
Contact contact = db.getContact(txn, p.getPublicKey(), local);
|
||||||
throw new PendingContactExistsException();
|
if (contact != null)
|
||||||
|
throw new ContactExistsException(local, contact.getAuthor());
|
||||||
|
if (db.containsPendingContact(txn, p.getId())) {
|
||||||
|
PendingContact existing = db.getPendingContact(txn, p.getId());
|
||||||
|
throw new PendingContactExistsException(existing);
|
||||||
|
}
|
||||||
db.addPendingContact(txn, p);
|
db.addPendingContact(txn, p);
|
||||||
transaction.attach(new PendingContactAddedEvent(p));
|
transaction.attach(new PendingContactAddedEvent(p));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1465,6 +1465,47 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Contact getContact(Connection txn, PublicKey handshakePublicKey,
|
||||||
|
AuthorId localAuthorId) throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT contactId, authorId, formatVersion, name,"
|
||||||
|
+ " alias, publicKey, verified"
|
||||||
|
+ " FROM contacts"
|
||||||
|
+ " WHERE handshakePublicKey = ? AND localAuthorId = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, handshakePublicKey.getEncoded());
|
||||||
|
ps.setBytes(2, localAuthorId.getBytes());
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
if (!rs.next()) {
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ContactId contactId = new ContactId(rs.getInt(1));
|
||||||
|
AuthorId authorId = new AuthorId(rs.getBytes(2));
|
||||||
|
int formatVersion = rs.getInt(3);
|
||||||
|
String name = rs.getString(4);
|
||||||
|
String alias = rs.getString(5);
|
||||||
|
PublicKey publicKey = new SignaturePublicKey(rs.getBytes(6));
|
||||||
|
boolean verified = rs.getBoolean(7);
|
||||||
|
if (rs.next()) throw new DbStateException();
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
Author author =
|
||||||
|
new Author(authorId, formatVersion, name, publicKey);
|
||||||
|
return new Contact(contactId, author, localAuthorId, alias,
|
||||||
|
handshakePublicKey, verified);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(rs, LOG, WARNING);
|
||||||
|
tryToClose(ps, LOG, WARNING);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Group getGroup(Connection txn, GroupId g) throws DbException {
|
public Group getGroup(Connection txn, GroupId g) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import org.briarproject.bramble.test.BrambleTestCase;
|
|||||||
import org.briarproject.bramble.test.SettableClock;
|
import org.briarproject.bramble.test.SettableClock;
|
||||||
import org.briarproject.bramble.test.TestDatabaseConfig;
|
import org.briarproject.bramble.test.TestDatabaseConfig;
|
||||||
import org.briarproject.bramble.test.TestMessageFactory;
|
import org.briarproject.bramble.test.TestMessageFactory;
|
||||||
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -1149,6 +1150,43 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetContactsByHandshakePublicKey() throws Exception {
|
||||||
|
Database<Connection> db = open(false);
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
|
||||||
|
// Add an identity for a local author - no contacts should be
|
||||||
|
// associated
|
||||||
|
db.addIdentity(txn, identity);
|
||||||
|
PublicKey handshakePublicKey = TestUtils.getSignaturePublicKey();
|
||||||
|
Contact contact =
|
||||||
|
db.getContact(txn, handshakePublicKey, localAuthor.getId());
|
||||||
|
assertNull(contact);
|
||||||
|
|
||||||
|
// Add a contact associated with the local author
|
||||||
|
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||||
|
handshakePublicKey, true));
|
||||||
|
contact = db.getContact(txn, handshakePublicKey, localAuthor.getId());
|
||||||
|
assertNotNull(contact);
|
||||||
|
assertEquals(contactId, contact.getId());
|
||||||
|
assertEquals(author, contact.getAuthor());
|
||||||
|
assertNull(contact.getAlias());
|
||||||
|
assertEquals(handshakePublicKey, contact.getHandshakePublicKey());
|
||||||
|
assertTrue(contact.isVerified());
|
||||||
|
assertEquals(author.getName(), contact.getAuthor().getName());
|
||||||
|
assertEquals(author.getPublicKey(), contact.getAuthor().getPublicKey());
|
||||||
|
assertEquals(author.getFormatVersion(),
|
||||||
|
contact.getAuthor().getFormatVersion());
|
||||||
|
|
||||||
|
// Ensure no contacts are returned after contact was deleted
|
||||||
|
db.removeContact(txn, contactId);
|
||||||
|
contact = db.getContact(txn, handshakePublicKey, localAuthor.getId());
|
||||||
|
assertNull(contact);
|
||||||
|
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOfferedMessages() throws Exception {
|
public void testOfferedMessages() throws Exception {
|
||||||
Database<Connection> db = open(false);
|
Database<Connection> db = open(false);
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ import android.support.annotation.Nullable;
|
|||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
|
import org.briarproject.bramble.api.contact.PendingContact;
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.NoSuchPendingContactException;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||||
import org.briarproject.briar.android.viewmodel.LiveResult;
|
import org.briarproject.briar.android.viewmodel.LiveResult;
|
||||||
@@ -118,4 +120,19 @@ public class AddContactViewModel extends AndroidViewModel {
|
|||||||
return addContactResult;
|
return addContactResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updatePendingContact(String name, PendingContact p) {
|
||||||
|
dbExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
contactManager.removePendingContact(p.getId());
|
||||||
|
addContact(name);
|
||||||
|
} catch(NoSuchPendingContactException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
// no error in UI as pending contact was converted into contact
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
addContactResult.postValue(new LiveResult<>(e));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,15 @@ package org.briarproject.briar.android.contact.add.remote;
|
|||||||
|
|
||||||
import android.arch.lifecycle.ViewModelProvider;
|
import android.arch.lifecycle.ViewModelProvider;
|
||||||
import android.arch.lifecycle.ViewModelProviders;
|
import android.arch.lifecycle.ViewModelProviders;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface.OnClickListener;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.StringRes;
|
||||||
import android.support.design.widget.TextInputEditText;
|
import android.support.design.widget.TextInputEditText;
|
||||||
import android.support.design.widget.TextInputLayout;
|
import android.support.design.widget.TextInputLayout;
|
||||||
|
import android.support.v7.app.AlertDialog.Builder;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -15,6 +20,10 @@ import android.widget.ProgressBar;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||||
|
import org.briarproject.bramble.api.contact.PendingContact;
|
||||||
|
import org.briarproject.bramble.api.db.ContactExistsException;
|
||||||
|
import org.briarproject.bramble.api.db.PendingContactExistsException;
|
||||||
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
@@ -24,9 +33,13 @@ import org.briarproject.briar.android.fragment.BaseFragment;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static android.support.v4.content.ContextCompat.getColor;
|
||||||
|
import static android.support.v4.content.ContextCompat.getDrawable;
|
||||||
|
import static android.support.v4.graphics.drawable.DrawableCompat.setTint;
|
||||||
import static android.view.View.INVISIBLE;
|
import static android.view.View.INVISIBLE;
|
||||||
import static android.view.View.VISIBLE;
|
import static android.view.View.VISIBLE;
|
||||||
import static android.widget.Toast.LENGTH_LONG;
|
import static android.widget.Toast.LENGTH_LONG;
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
|
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
|
||||||
|
|
||||||
@@ -107,23 +120,95 @@ public class NicknameFragment extends BaseFragment {
|
|||||||
|
|
||||||
viewModel.getAddContactResult().observe(this, result -> {
|
viewModel.getAddContactResult().observe(this, result -> {
|
||||||
if (result == null) return;
|
if (result == null) return;
|
||||||
if (result.hasError()) {
|
if (result.hasError())
|
||||||
int stringRes;
|
handleException(name, requireNonNull(result.getException()));
|
||||||
if (result
|
else
|
||||||
.getException() instanceof UnsupportedVersionException) {
|
showPendingContactListActivity();
|
||||||
stringRes = R.string.unsupported_link;
|
|
||||||
} else {
|
|
||||||
stringRes = R.string.adding_contact_error;
|
|
||||||
}
|
|
||||||
Toast.makeText(getContext(), stringRes, LENGTH_LONG).show();
|
|
||||||
} else {
|
|
||||||
Intent intent = new Intent(getActivity(),
|
|
||||||
PendingContactListActivity.class);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
finish();
|
|
||||||
});
|
});
|
||||||
viewModel.addContact(name);
|
viewModel.addContact(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showPendingContactListActivity() {
|
||||||
|
Intent intent = new Intent(getActivity(),
|
||||||
|
PendingContactListActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleException(String name, Exception e) {
|
||||||
|
if (e instanceof ContactExistsException) {
|
||||||
|
ContactExistsException ce = (ContactExistsException) e;
|
||||||
|
handleExistingContact(name, ce.getRemoteAuthor());
|
||||||
|
} else if (e instanceof PendingContactExistsException) {
|
||||||
|
PendingContactExistsException pe =
|
||||||
|
(PendingContactExistsException) e;
|
||||||
|
handleExistingPendingContact(name, pe.getPendingContact());
|
||||||
|
} else if (e instanceof UnsupportedVersionException) {
|
||||||
|
int stringRes = R.string.unsupported_link;
|
||||||
|
Toast.makeText(getContext(), stringRes, LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
} else {
|
||||||
|
int stringRes = R.string.adding_contact_error;
|
||||||
|
Toast.makeText(getContext(), stringRes, LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleExistingContact(String name, Author existing) {
|
||||||
|
OnClickListener listener = (d, w) -> {
|
||||||
|
d.dismiss();
|
||||||
|
String str = getString(R.string.contact_already_exists, name);
|
||||||
|
Toast.makeText(getContext(), str, LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
};
|
||||||
|
showSameLinkDialog(existing.getName(), name,
|
||||||
|
R.string.duplicate_link_dialog_text_1_contact, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleExistingPendingContact(String name, PendingContact p) {
|
||||||
|
OnClickListener listener = (d, w) -> {
|
||||||
|
viewModel.updatePendingContact(name, p);
|
||||||
|
Toast.makeText(getContext(), R.string.pending_contact_updated_toast,
|
||||||
|
LENGTH_LONG).show();
|
||||||
|
d.dismiss();
|
||||||
|
showPendingContactListActivity();
|
||||||
|
};
|
||||||
|
showSameLinkDialog(p.getAlias(), name,
|
||||||
|
R.string.duplicate_link_dialog_text_1, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSameLinkDialog(String name1, String name2,
|
||||||
|
@StringRes int existsRes, OnClickListener samePersonListener) {
|
||||||
|
Context ctx = requireContext();
|
||||||
|
Builder b = new Builder(ctx, R.style.BriarDialogTheme_Neutral);
|
||||||
|
b.setTitle(getString(R.string.duplicate_link_dialog_title));
|
||||||
|
String msg = getString(existsRes, name1) + "\n\n" +
|
||||||
|
getString(R.string.duplicate_link_dialog_text_2, name2, name1);
|
||||||
|
b.setMessage(msg);
|
||||||
|
b.setPositiveButton(R.string.same_person_button, samePersonListener);
|
||||||
|
b.setNegativeButton(R.string.different_person_button, (d, w) -> {
|
||||||
|
d.dismiss();
|
||||||
|
showWarningDialog(name1, name2);
|
||||||
|
});
|
||||||
|
b.setCancelable(false);
|
||||||
|
b.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showWarningDialog(String name1, String name2) {
|
||||||
|
Context ctx = requireContext();
|
||||||
|
Builder b = new Builder(ctx, R.style.BriarDialogTheme);
|
||||||
|
Drawable icon = getDrawable(ctx, R.drawable.alerts_and_states_error);
|
||||||
|
setTint(requireNonNull(icon), getColor(ctx, R.color.color_primary));
|
||||||
|
b.setIcon(icon);
|
||||||
|
b.setTitle(getString(R.string.duplicate_link_dialog_title));
|
||||||
|
b.setMessage(
|
||||||
|
getString(R.string.duplicate_link_dialog_text_3, name1, name2));
|
||||||
|
b.setPositiveButton(R.string.ok, (d, w) -> {
|
||||||
|
d.dismiss();
|
||||||
|
finish();
|
||||||
|
});
|
||||||
|
b.setCancelable(false);
|
||||||
|
b.show();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,8 +62,7 @@ public class ContactExchangeActivity extends KeyAgreementActivity {
|
|||||||
@UiThread
|
@UiThread
|
||||||
private void contactExchangeSucceeded(Author remoteAuthor) {
|
private void contactExchangeSucceeded(Author remoteAuthor) {
|
||||||
String contactName = remoteAuthor.getName();
|
String contactName = remoteAuthor.getName();
|
||||||
String format = getString(R.string.contact_added_toast);
|
String text = getString(R.string.contact_added_toast, contactName);
|
||||||
String text = String.format(format, contactName);
|
|
||||||
Toast.makeText(this, text, LENGTH_LONG).show();
|
Toast.makeText(this, text, LENGTH_LONG).show();
|
||||||
supportFinishAfterTransition();
|
supportFinishAfterTransition();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,7 +199,7 @@
|
|||||||
<string name="adding_contact_slow_text">添加此联系人比通常花费了更多时间。\n\n请检查您的联系人是否收到您的链接并已添加您:</string>
|
<string name="adding_contact_slow_text">添加此联系人比通常花费了更多时间。\n\n请检查您的联系人是否收到您的链接并已添加您:</string>
|
||||||
<string name="offline_state">无网络连接</string>
|
<string name="offline_state">无网络连接</string>
|
||||||
<string name="duplicate_link_dialog_title">重复的链接</string>
|
<string name="duplicate_link_dialog_title">重复的链接</string>
|
||||||
<string name="duplicate_link_dialog_text_1">您已经有此链接的待处理联系人:</string>
|
<string name="duplicate_link_dialog_text_1">您已经有此链接的待处理联系人:%s</string>
|
||||||
<!--This is a question asking whether two nicknames refer to the same person-->
|
<!--This is a question asking whether two nicknames refer to the same person-->
|
||||||
<string name="duplicate_link_dialog_text_2">%s 和 %s 是同一个人吗?</string>
|
<string name="duplicate_link_dialog_text_2">%s 和 %s 是同一个人吗?</string>
|
||||||
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
|
||||||
|
|||||||
@@ -227,6 +227,7 @@
|
|||||||
<string name="offline_state">No Internet connection</string>
|
<string name="offline_state">No Internet connection</string>
|
||||||
<string name="duplicate_link_dialog_title">Duplicate Link</string>
|
<string name="duplicate_link_dialog_title">Duplicate Link</string>
|
||||||
<string name="duplicate_link_dialog_text_1">You already have a pending contact with this link: %s</string>
|
<string name="duplicate_link_dialog_text_1">You already have a pending contact with this link: %s</string>
|
||||||
|
<string name="duplicate_link_dialog_text_1_contact">You already have a contact with this link: %s</string>
|
||||||
<!-- This is a question asking whether two nicknames refer to the same person -->
|
<!-- This is a question asking whether two nicknames refer to the same person -->
|
||||||
<string name="duplicate_link_dialog_text_2">Are %s and %s the same person?</string>
|
<string name="duplicate_link_dialog_text_2">Are %s and %s the same person?</string>
|
||||||
<!-- This is a button for answering that two nicknames do indeed refer to the same person. This
|
<!-- This is a button for answering that two nicknames do indeed refer to the same person. This
|
||||||
|
|||||||
Reference in New Issue
Block a user