Commit 7a71d2ba authored by Torsten Grote's avatar Torsten Grote

Remote Contact Adding UI: Address review comments

parent 4bf21b2f
......@@ -62,11 +62,6 @@ public interface ContactManager {
*/
String getRemoteContactLink() throws DbException;
/**
* Returns true if the given link is syntactically valid.
*/
boolean isValidRemoteContactLink(String link);
/**
* Requests a new contact to be added via the given {@code link}.
*
......
......@@ -138,11 +138,6 @@ class ContactManagerImpl implements ContactManager {
return new String(c);
}
@Override
public boolean isValidRemoteContactLink(String link) {
return LINK_REGEX.matcher(link).matches();
}
@Override
public void addRemoteContactRequest(String link, String alias) {
// TODO replace with real implementation
......@@ -199,7 +194,7 @@ class ContactManagerImpl implements ContactManager {
pendingContacts.remove(pendingContact);
Event e;
try {
if (true || new Random().nextBoolean()) {
if (new Random().nextBoolean()) {
getLogger("TMP").warning("FAILED");
e = new PendingContactStateChangedEvent(id, FAILED);
PendingContact updated = new PendingContact(id,
......
......@@ -444,7 +444,7 @@
</activity>
<activity
android:name=".android.contact.add.remote.PendingRequestsActivity"
android:name=".android.contact.add.remote.PendingContactListActivity"
android:label="@string/pending_contact_requests"
android:theme="@style/BriarTheme"/>
......
......@@ -572,11 +572,11 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
private void updateContactAddedNotification() {
BriarNotificationBuilder b =
new BriarNotificationBuilder(appContext, CONTACT_CHANNEL_ID);
b.setSmallIcon(R.drawable.notification_introduction);
b.setSmallIcon(R.drawable.notification_contact_added);
b.setColorRes(R.color.briar_primary);
b.setContentTitle(appContext.getText(R.string.app_name));
b.setContentText(appContext.getResources().getQuantityString(
R.plurals.introduction_notification_text, contactAddedTotal,
R.plurals.contact_added_notification_text, contactAddedTotal,
contactAddedTotal));
b.setNotificationCategory(CATEGORY_MESSAGE);
setAlertProperties(b);
......
......@@ -20,7 +20,7 @@ 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.contact.add.remote.PendingContactListActivity;
import org.briarproject.briar.android.conversation.AliasDialogFragment;
import org.briarproject.briar.android.conversation.ConversationActivity;
import org.briarproject.briar.android.conversation.ImageActivity;
......@@ -174,7 +174,7 @@ public interface ActivityComponent {
void inject(AddContactActivity activity);
void inject(PendingRequestsActivity activity);
void inject(PendingContactListActivity activity);
// Fragments
void inject(AuthorNameFragment fragment);
......
......@@ -35,7 +35,7 @@ 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.contact.add.remote.PendingContactListActivity;
import org.briarproject.briar.android.conversation.ConversationActivity;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.keyagreement.ContactExchangeActivity;
......@@ -179,7 +179,7 @@ public class ContactListFragment extends BaseFragment implements EventListener,
snackbar = new BriarSnackbarBuilder()
.setAction(R.string.show, v ->
startActivity(new Intent(getContext(),
PendingRequestsActivity.class)))
PendingContactListActivity.class)))
.make(contentView, R.string.pending_contact_requests_snackbar,
LENGTH_INDEFINITE);
......
......@@ -13,12 +13,21 @@ 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.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.contact.ContactManager.LINK_REGEX;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
public class AddContactViewModel extends AndroidViewModel {
private final static Logger LOG =
getLogger(AddContactViewModel.class.getName());
private final ContactManager contactManager;
@DatabaseExecutor
private final Executor dbExecutor;
......@@ -44,7 +53,9 @@ public class AddContactViewModel extends AndroidViewModel {
try {
ourLink.postValue(contactManager.getRemoteContactLink());
} catch (DbException e) {
throw new AssertionError(e);
logException(LOG, WARNING, e);
// the UI should stay disable in this case,
// leaving the user unable to proceed
}
});
}
......@@ -57,14 +68,8 @@ public class AddContactViewModel extends AndroidViewModel {
remoteContactLink = link;
}
@Nullable
String getRemoteContactLink() {
return remoteContactLink;
}
boolean isValidRemoteContactLink(@Nullable CharSequence link) {
return link != null &&
contactManager.isValidRemoteContactLink(link.toString());
return link != null && LINK_REGEX.matcher(link).find();
}
LiveData<Boolean> getRemoteLinkEntered() {
......@@ -74,6 +79,7 @@ public class AddContactViewModel extends AndroidViewModel {
void onRemoteLinkEntered() {
if (remoteContactLink == null) throw new IllegalStateException();
remoteLinkEntered.setValue(true);
remoteLinkEntered.postValue(false); // reset, so we can navigate back
}
void addContact(String nickname) {
......
......@@ -8,7 +8,6 @@ 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;
......@@ -73,9 +72,6 @@ public class LinkExchangeFragment extends BaseFragment {
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));
......@@ -83,7 +79,7 @@ public class LinkExchangeFragment extends BaseFragment {
Button pasteButton = v.findViewById(R.id.pasteButton);
pasteButton.setOnClickListener(view -> {
ClipData clipData = clipboard.getPrimaryClip();
if (clipData != null)
if (clipData != null && clipData.getItemCount() > 0)
linkInput.setText(clipData.getItemAt(0).getText());
});
......@@ -95,7 +91,7 @@ public class LinkExchangeFragment extends BaseFragment {
private void onOwnLinkLoaded(String link) {
View v = requireNonNull(getView());
TextView linkView = v.findViewById(R.id.linkView);
TextView linkView = v.findViewById(R.id.linkView);
linkView.setText(link);
Button copyButton = v.findViewById(R.id.copyButton);
......@@ -110,10 +106,10 @@ public class LinkExchangeFragment extends BaseFragment {
Button shareButton = v.findViewById(R.id.shareButton);
shareButton.setOnClickListener(view ->
IntentBuilder.from(requireNonNull(getActivity()))
.setText(link)
.setType("text/plain")
.startChooser());
IntentBuilder.from(requireActivity())
.setText(link)
.setType("text/plain")
.startChooser());
shareButton.setEnabled(true);
Button continueButton = v.findViewById(R.id.addButton);
......@@ -121,47 +117,42 @@ public class LinkExchangeFragment extends BaseFragment {
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;
}
/**
* Requires {@link AddContactViewModel#getOurLink()} to be loaded.
*/
@Nullable
private String getLink() {
private String getEnteredLinkOrNull() {
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
if (link == null || link.length() == 0) {
linkInputLayout.setError(getString(R.string.missing_link));
linkInput.requestFocus();
return null;
}
Matcher matcher = LINK_REGEX.matcher(link);
if (matcher.find()) {
String linkWithoutSchema = matcher.group(2);
// Check also if this is our own link. This was loaded already,
// because it enables the Continue button which is the only caller.
if (("briar://" + linkWithoutSchema)
.equals(viewModel.getOurLink().getValue())) {
linkInputLayout.setError(getString(R.string.own_link_error));
linkInput.requestFocus();
return null;
}
linkInputLayout.setError(null);
return linkWithoutSchema;
}
linkInputLayout.setError(getString(R.string.invalid_link));
linkInput.requestFocus();
return null;
}
private void onContinueButtonClicked() {
if (isInputError()) return;
String linkText = getLink();
if (linkText == null) throw new AssertionError();
viewModel.setRemoteContactLink(linkText);
String link = getEnteredLinkOrNull();
if (link == null) return; // invalid link
viewModel.setRemoteContactLink(link);
viewModel.onRemoteLinkEntered();
}
......
......@@ -4,10 +4,9 @@ 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.text.Editable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
......@@ -22,7 +21,8 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static java.util.Objects.requireNonNull;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.util.StringUtils.utf8IsTooLong;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
......@@ -70,27 +70,31 @@ public class NicknameFragment extends BaseFragment {
return v;
}
@MainThread
@UiThread
private boolean isInputError() {
boolean validContactName = contactNameInput.getText() != null &&
contactNameInput.getText().toString().trim().length() > 0;
if (!validContactName) {
@Nullable
private String getNicknameOrNull() {
Editable name = contactNameInput.getText();
if (name == null || name.toString().trim().length() == 0) {
contactNameLayout.setError(getString(R.string.nickname_missing));
contactNameInput.requestFocus();
return true;
} else contactNameLayout.setError(null);
return false;
return null;
}
if (utf8IsTooLong(name.toString(), MAX_AUTHOR_NAME_LENGTH)) {
contactNameLayout.setError(getString(R.string.name_too_long));
contactNameInput.requestFocus();
return null;
}
contactNameLayout.setError(null);
return name.toString().trim();
}
private void onAddButtonClicked() {
if (isInputError()) return;
String name = getNicknameOrNull();
if (name == null) return; // invalid nickname
String name = requireNonNull(contactNameInput.getText()).toString();
viewModel.addContact(name);
Intent intent =
new Intent(getActivity(), PendingRequestsActivity.class);
new Intent(getActivity(), PendingContactListActivity.class);
startActivity(intent);
finish();
}
......
......@@ -22,14 +22,14 @@ import javax.inject.Inject;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class PendingRequestsActivity extends BriarActivity
public class PendingContactListActivity extends BriarActivity
implements PendingContactListener {
@Inject
ViewModelProvider.Factory viewModelFactory;
private PendingRequestsViewModel viewModel;
private PendingRequestsAdapter adapter;
private PendingContactListViewModel viewModel;
private PendingContactListAdapter adapter;
private BriarRecyclerView list;
@Override
......@@ -49,11 +49,11 @@ public class PendingRequestsActivity extends BriarActivity
}
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(PendingRequestsViewModel.class);
.get(PendingContactListViewModel.class);
viewModel.getPendingContacts()
.observe(this, this::onPendingContactsChanged);
adapter = new PendingRequestsAdapter(this, this, PendingContact.class);
adapter = new PendingContactListAdapter(this, this, PendingContact.class);
list = findViewById(R.id.list);
list.setEmptyText(R.string.no_pending_contacts);
list.setLayoutManager(new LinearLayoutManager(this));
......@@ -92,12 +92,11 @@ public class PendingRequestsActivity extends BriarActivity
private void onPendingContactsChanged(Collection<PendingContact> contacts) {
if (contacts.isEmpty()) {
if (!adapter.isEmpty()) {
if (adapter.isEmpty()) {
list.showData(); // hides progress bar, shows empty text
} else {
// all previous contacts have been removed, so we can finish
supportFinishAfterTransition();
} else {
adapter.clear();
list.showData();
}
} else {
adapter.setItems(contacts);
......
......@@ -11,29 +11,29 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.util.BriarAdapter;
@NotNullByDefault
public class PendingRequestsAdapter extends
BriarAdapter<PendingContact, PendingRequestsViewHolder> {
class PendingContactListAdapter extends
BriarAdapter<PendingContact, PendingContactViewHolder> {
private final PendingContactListener listener;
public PendingRequestsAdapter(Context ctx, PendingContactListener listener,
PendingContactListAdapter(Context ctx, PendingContactListener listener,
Class<PendingContact> c) {
super(ctx, c);
this.listener = listener;
}
@Override
public PendingRequestsViewHolder onCreateViewHolder(
ViewGroup viewGroup, int i) {
public PendingContactViewHolder onCreateViewHolder(ViewGroup viewGroup,
int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_pending_contact, viewGroup, false);
return new PendingRequestsViewHolder(v, listener);
return new PendingContactViewHolder(v, listener);
}
@Override
public void onBindViewHolder(
PendingRequestsViewHolder pendingRequestsViewHolder, int i) {
pendingRequestsViewHolder.bind(items.get(i));
PendingContactViewHolder pendingContactViewHolder, int i) {
pendingContactViewHolder.bind(items.get(i));
}
@Override
......@@ -44,15 +44,16 @@ public class PendingRequestsAdapter extends
@Override
public boolean areContentsTheSame(PendingContact item1,
PendingContact item2) {
return item1.getAlias().equals(item2.getAlias()) &&
item1.getTimestamp() == item2.getTimestamp();
return item1.getId().equals(item2.getId()) &&
item1.getAlias().equals(item2.getAlias()) &&
item1.getTimestamp() == item2.getTimestamp() &&
item1.getState() == item2.getState();
}
@Override
public boolean areItemsTheSame(PendingContact item1,
PendingContact item2) {
return item1.getAlias().equals(item2.getAlias()) &&
item1.getTimestamp() == item2.getTimestamp();
return item1.getId().equals(item2.getId());
}
}
......@@ -27,11 +27,11 @@ import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
public class PendingRequestsViewModel extends AndroidViewModel
public class PendingContactListViewModel extends AndroidViewModel
implements EventListener {
private final Logger LOG =
getLogger(PendingRequestsViewModel.class.getName());
getLogger(PendingContactListViewModel.class.getName());
@DatabaseExecutor
private final Executor dbExecutor;
......@@ -42,7 +42,7 @@ public class PendingRequestsViewModel extends AndroidViewModel
new MutableLiveData<>();
@Inject
public PendingRequestsViewModel(Application application,
public PendingContactListViewModel(Application application,
@DatabaseExecutor Executor dbExecutor,
ContactManager contactManager, EventBus eventBus) {
super(application);
......
......@@ -16,22 +16,22 @@ import static android.view.View.VISIBLE;
import static org.briarproject.briar.android.util.UiUtils.formatDate;
@NotNullByDefault
public class PendingRequestsViewHolder extends ViewHolder {
class PendingContactViewHolder extends ViewHolder {
private final PendingContactListener listener;
private final TextAvatarView avatar;
private final TextView name;
private final TextView time;
private final TextView status;
private final Button button;
private final Button removeButton;
public PendingRequestsViewHolder(View v, PendingContactListener listener) {
PendingContactViewHolder(View v, PendingContactListener listener) {
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);
button = v.findViewById(R.id.button);
removeButton = v.findViewById(R.id.removeButton);
this.listener = listener;
}
......@@ -40,7 +40,7 @@ public class PendingRequestsViewHolder extends ViewHolder {
avatar.setBackgroundBytes(item.getId().getBytes());
name.setText(item.getAlias());
time.setText(formatDate(time.getContext(), item.getTimestamp()));
button.setOnClickListener(
removeButton.setOnClickListener(
v -> listener.onFailedPendingContactRemoved(item));
int color = ContextCompat
......@@ -68,7 +68,7 @@ public class PendingRequestsViewHolder extends ViewHolder {
throw new IllegalStateException();
}
status.setTextColor(color);
button.setVisibility(buttonVisibility);
removeButton.setVisibility(buttonVisibility);
}
}
......@@ -4,7 +4,7 @@ import android.arch.lifecycle.ViewModel;
import android.arch.lifecycle.ViewModelProvider;
import org.briarproject.briar.android.contact.add.remote.AddContactViewModel;
import org.briarproject.briar.android.contact.add.remote.PendingRequestsViewModel;
import org.briarproject.briar.android.contact.add.remote.PendingContactListViewModel;
import org.briarproject.briar.android.conversation.ConversationViewModel;
import org.briarproject.briar.android.conversation.ImageViewModel;
......@@ -37,9 +37,9 @@ public abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(PendingRequestsViewModel.class)
@ViewModelKey(PendingContactListViewModel.class)
abstract ViewModel bindPendingRequestsViewModel(
PendingRequestsViewModel pendingRequestsViewModel);
PendingContactListViewModel pendingContactListViewModel);
@Binds
@Singleton
......
......@@ -68,7 +68,7 @@
tools:text="Dec 24"/>
<Button
android:id="@+id/button"
android:id="@+id/removeButton"
style="@style/BriarButtonFlat.Negative"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
......@@ -88,7 +88,7 @@
android:layout_marginStart="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/avatar"
app:layout_constraintTop_toBottomOf="@+id/button"
app:layout_constraintTop_toBottomOf="@+id/removeButton"
app:layout_goneMarginTop="@dimen/margin_large"/>
</android.support.constraint.ConstraintLayout>
......@@ -188,7 +188,7 @@
<string name="introduction_response_accepted_received">لقد وافق/ت %1$s على تقديمه/ها إلى %2$s.</string>
<string name="introduction_response_declined_received">لقد رفض/ت %1$sتقديمه/ا إلى %2$s.</string>
<string name="introduction_response_declined_received_by_introducee">ي/تقول %1$sأن %2$s قد رفض/ت التقدمة.</string>
<plurals name="introduction_notification_text">
<plurals name="contact_added_notification_text">
<item quantity="zero">لا جهة اتصال تم إضافتها.</item>
<item quantity="one">جهة اتصال تم إضافتها.</item>
<item quantity="two">جهتيْ اتصال تم إضافتها.</item>
......
......@@ -99,7 +99,7 @@
<string name="try_again_button">Tentar nuevamente</string>
<string name="camera_error">Error de cámara</string>
<!--Introductions-->
<plurals name="introduction_notification_text">
<plurals name="contact_added_notification_text">
<item quantity="one">Añadióse un contactu nuevu.</item>
<item quantity="other">Añadiéronse %d contactos nuevos.</item>
</plurals>
......
......@@ -173,7 +173,7 @@
<string name="introduction_request_answered_received">%1$ssizi %2$s-ə təqdim etmək istədi.</string>
<string name="introduction_response_accepted_sent">%1$s-ə qəbul olundunuz</string>
<string name="introduction_response_declined_sent">%1$s-ə girişdən imtina etdiniz</string>
<plurals name="introduction_notification_text">
<plurals name="contact_added_notification_text">
<item quantity="one">yeni kontaktlar əlavə edildi.</item>
<item quantity="other">%dyeni kontaktlar əlavə edildi.</item>
</plurals>
......
......@@ -114,7 +114,7 @@
<string name="introduction_response_accepted_received">%1$s прие представянето на %2$s.</string>
<string name="introduction_response_declined_received">%1$s отказа представянето на %2$s.</string>
<string name="introduction_response_declined_received_by_introducee">%1$s казва, че %2$s отказва представянето.</string>
<plurals name="introduction_notification_text">
<plurals name="contact_added_notification_text">
<item quantity="one">Добавен нов контакт.</item>
<item quantity="other">%d добавени нови контакти.</item>
</plurals>
......
......@@ -111,7 +111,7 @@
<string name="introduction_sent">Kaset eo bet ho tigoradur.</string>
<string name="introduction_error">Ur fazi a zo c\'hoarvezet en ur ober an digoradur.</string>
<string name="introduction_response_error">Fazi en ur respont d\'an digoradur</string>
<plurals name="introduction_notification_text">
<plurals name="contact_added_notification_text">
<item quantity="one">Darempred nevez ouzhpennet.</item>
<item quantity="two">%d a zarempredoù nevez ouzhpennet.</item>
<item quantity="few">%d a zarempredoù nevez ouzhpennet.</item>
......
......@@ -178,7 +178,7 @@ Un cop s\'actualitzi ja li veureu una icona diferent </string>
<string name="introduction_response_accepted_received">%1$s ha acceptat conèixer a %2$s.</string>
<string name="introduction_response_declined_received">%1$s ha refusat conèixer a %2$s.</string>
<string name="introduction_response_declined_received_by_introducee">%1$s diu que %2$s ha refusat conèixer-lo.</string>
<plurals name="introduction_notification_text">
<plurals name="contact_added_notification_text">
<item quantity="one">S\'ha afegit un nou contacte.</item>
<item quantity="other">S\'han afegit %d nous contactes.</item>
</plurals>
......
......@@ -157,7 +157,7 @@
<string name="introduction_response_accepted_received">%1$s přijal pozvání do %2$s.</string>
<string name="introduction_response_declined_received">%1$s odmítl pozvání do %2$s.</string>
<string name="introduction_response_declined_received_by_introducee">%1$s řekl, že %2$s odmítl pozvání.</string>
<plurals name="introduction_notification_text">
<plurals name="contact_added_notification_text">
<item quantity="one">Nový kontakt byl přidán.</item>
<item quantity="few">Bylo přidáno %d nových kontaktů.</item>
<item quantity="many">%d nových kontaktů bylo přidáno.</item>
......
......@@ -177,7 +177,7 @@
<string name="introduction_response_accepted_received">%1$s hat die Kontaktempfehlung für %2$s angenommen.</string>
<string name="introduction_response_declined_received">%1$s hat die Kontaktempfehlung von %2$s abgelehnt.</string>
<string name="introduction_response_declined_received_by_introducee">%1$s meldet, dass %2$s die Kontaktempfehlung abgelehnt hat.</string>
<plurals name="introduction_notification_text">
<plurals name="contact_added_notification_text">
<item quantity="one">neuer Kontakt hinzugefügt.</item>
<item quantity="other">%d neue Kontakte hinzugefügt.</item>
</plurals>
......
......@@ -177,7 +177,7 @@
<string name="introduction_response_accepted_received">%1$s aceptó la presentación a %2$s.</string>
<string name="introduction_response_declined_received">%1$s rechazó la presentación a %2$s.</string>
<string name="introduction_response_declined_received_by_introducee">%1$s dice que %2$s rechazó la presentación.</string>
<plurals name="introduction_notification_text">
<plurals name="contact_added_notification_text">
<item quantity="one">nuevo contacto añadido.</item>
<item quantity="other">%d nuevos contactos añadidos.</item>
</plurals>
......