diff --git a/.github/workflows/ant.yml b/.github/workflows/ant.yml
new file mode 100644
index 0000000000..bed55d65d2
--- /dev/null
+++ b/.github/workflows/ant.yml
@@ -0,0 +1,25 @@
+# This workflow will build a Java project with Ant
+# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-ant
+
+name: Java CI
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up JDK 11
+ uses: actions/setup-java@v3
+ with:
+ java-version: '11'
+ distribution: 'temurin'
+ - name: Build with Ant
+ run: ant -noinput -buildfile build.xml
diff --git a/.github/workflows/clojure.yml b/.github/workflows/clojure.yml
new file mode 100644
index 0000000000..705b63e4b2
--- /dev/null
+++ b/.github/workflows/clojure.yml
@@ -0,0 +1,19 @@
+name: Clojure CI
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install dependencies
+ run: lein deps
+ - name: Run tests
+ run: lein test
diff --git a/app/build.gradle b/app/build.gradle
index 01bb3d987e..146bc917c4 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -49,7 +49,7 @@ android {
def DEFAULT_OPENSEA_API_KEY = "\"...\""; //Put your OpenSea developer API key in here, otherwise you are reliant on the backup NFT fetch method
def DEFAULT_POLYGONSCAN_API_KEY = "\"\""; //Put your Polygonscan developer API key in here to get access to Polygon/Mumbai token discovery and transactions
- buildConfigField 'int', 'DB_VERSION', '43'
+ buildConfigField 'int', 'DB_VERSION', '44'
buildConfigField "String", XInfuraAPI, DEFAULT_INFURA_API_KEY
ndk {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a069c50793..f44d196d21 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -357,6 +357,12 @@
android:name=".ui.QRScanning.QRScanner"
android:label="@string/qr_scanner" />
+
+
+
+
CREATOR = new Creator() {
+ @Override
+ public AddressBookContact createFromParcel(Parcel source) {
+ return new AddressBookContact(source);
+ }
+
+ @Override
+ public AddressBookContact[] newArray(int size) {
+ return new AddressBookContact[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "UserInfo{" +
+ "walletAddress='" + walletAddress + '\'' +
+ ", name='" + name + '\'' +
+ ", ethName='" + ethName + '\'' +
+ '}';
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/interact/AddressBookInteract.java b/app/src/main/java/com/alphawallet/app/interact/AddressBookInteract.java
new file mode 100644
index 0000000000..a76f491f8c
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/interact/AddressBookInteract.java
@@ -0,0 +1,50 @@
+package com.alphawallet.app.interact;
+
+import com.alphawallet.app.entity.AddressBookContact;
+import com.alphawallet.app.repository.ContactRepositoryType;
+import com.alphawallet.app.util.ItemCallback;
+
+import java.util.List;
+
+import io.reactivex.Completable;
+import io.reactivex.Single;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.schedulers.Schedulers;
+
+public class AddressBookInteract {
+
+ private ContactRepositoryType contactRepository;
+
+ public AddressBookInteract(ContactRepositoryType contactRepository) {
+ this.contactRepository = contactRepository;
+ }
+
+ public Single> getAllContacts() {
+ return contactRepository.getContacts();
+ }
+
+ public Completable addContact(AddressBookContact addressBookContact) {
+ return contactRepository.addContact(addressBookContact);
+ }
+
+ public Completable updateContact(AddressBookContact oldContact, AddressBookContact newContact) {
+ return contactRepository.updateContact(oldContact, newContact);
+ }
+
+ public Completable removeContact(AddressBookContact addressBookContact) {
+ return contactRepository.removeContact(addressBookContact);
+ }
+
+ public Single searchContact(String walletAddress) {
+ return contactRepository.searchContact(walletAddress);
+ }
+
+ /** Search a contact and invoke the callback*/
+ public void searchContactAsync(String walletAddress, ItemCallback callback) {
+ contactRepository.searchContact(walletAddress)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribeOn(Schedulers.io())
+ .subscribe( (callback::call), (throwable -> callback.call(null)) )
+ .isDisposed();
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/repository/AWRealmMigration.java b/app/src/main/java/com/alphawallet/app/repository/AWRealmMigration.java
index 5b3e502bc8..6174ae12ff 100644
--- a/app/src/main/java/com/alphawallet/app/repository/AWRealmMigration.java
+++ b/app/src/main/java/com/alphawallet/app/repository/AWRealmMigration.java
@@ -402,6 +402,18 @@ else if (oldVersion == 8)
oldVersion = 43;
}
+
+ if (oldVersion == 43)
+ {
+ RealmObjectSchema userData = schema.get("RealmContact");
+ if (userData == null)
+ schema.create("RealmContact")
+ .addField("walletAddress", String.class, FieldAttribute.PRIMARY_KEY)
+ .addField("name", String.class)
+ .addField("ethName", String.class);
+
+ oldVersion++;
+ }
}
@Override
diff --git a/app/src/main/java/com/alphawallet/app/repository/ContactRepository.java b/app/src/main/java/com/alphawallet/app/repository/ContactRepository.java
new file mode 100644
index 0000000000..0c4da6add1
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/repository/ContactRepository.java
@@ -0,0 +1,154 @@
+package com.alphawallet.app.repository;
+
+import com.alphawallet.app.entity.AddressBookContact;
+import com.alphawallet.app.entity.Wallet;
+import com.alphawallet.app.repository.entity.RealmContact;
+import com.alphawallet.app.service.RealmManager;
+import com.alphawallet.app.service.TokensService;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import io.reactivex.Completable;
+import io.reactivex.Single;
+import io.realm.Realm;
+import io.realm.RealmResults;
+import timber.log.Timber;
+
+public class ContactRepository implements ContactRepositoryType {
+
+ private RealmManager realmManager;
+ private TokensService tokensService;
+ private Wallet wallet;
+
+ public ContactRepository(TokensService tokensService, RealmManager realmManager) {
+ this.tokensService = tokensService;
+ this.realmManager = realmManager;
+
+ wallet = new Wallet(tokensService.getCurrentAddress());
+ }
+
+ @Override
+ public Single> getContacts() {
+
+ return Single.fromCallable(this::getAllContacts);
+ }
+
+ @Override
+ public Completable addContact(AddressBookContact contact) {
+ try (Realm realm = realmManager.getAddressBookRealmInstance())
+ {
+ realm.beginTransaction();
+ RealmContact realmContact = realm.createObject(RealmContact.class, contact.getWalletAddress());
+ realmContact.setName(contact.getName());
+ realmContact.setEthName(contact.getEthName());
+ realm.commitTransaction();
+ realm.close();
+ return Completable.complete();
+ }
+ catch (Exception e)
+ {
+ Timber.e(e);
+ return Completable.error(new RuntimeException("Failed to add Contact"));
+ }
+
+ }
+
+ @Override
+ public Completable updateContact(AddressBookContact oldContact, AddressBookContact newContact) {
+ Timber.d("New: %s, old: %s", newContact, oldContact);
+ try (Realm realm = realmManager.getAddressBookRealmInstance())
+ {
+ realm.beginTransaction();
+ RealmContact realmContact = realm.where(RealmContact.class)
+ .equalTo("walletAddress", oldContact.getWalletAddress())
+ .findFirst();
+ if (realmContact != null) {
+ realmContact.setName(newContact.getName());
+ }
+ realm.commitTransaction();
+ realm.close();
+ if (realmContact != null)
+ return Completable.complete();
+ else
+ return Completable.error(new RuntimeException("No matching contact found"));
+ }
+ catch (Exception e)
+ {
+ Timber.e(e);
+ return Completable.error(new RuntimeException("Failed to update Contact"));
+ }
+ }
+
+ @Override
+ public Completable removeContact(AddressBookContact contact) {
+ try (Realm realm = realmManager.getAddressBookRealmInstance())
+ {
+ realm.beginTransaction();
+ RealmContact realmContact = realm.where(RealmContact.class)
+ .equalTo("walletAddress", contact.getWalletAddress())
+ .findFirst();
+ if (realmContact != null) {
+ realmContact.deleteFromRealm();
+ }
+ realm.commitTransaction();
+ realm.close();
+ return Completable.complete();
+ }
+ catch (Exception e)
+ {
+ Timber.e(e);
+ return Completable.error(new RuntimeException("Failed to remove Contact"));
+ }
+ }
+
+ @Override
+ public Single searchContact(String walletAddress) {
+ return Single.fromCallable(new Callable() {
+ @Override
+ public AddressBookContact call() throws Exception {
+ List contacts = getAllContacts();
+ if (contacts != null) {
+ return searchContactFromList(walletAddress, contacts);
+ } else {
+ return null;
+ }
+ }
+ });
+ }
+
+ private ArrayList getAllContacts() {
+ ArrayList contacts = new ArrayList<>();
+ try (Realm realm = realmManager.getAddressBookRealmInstance()) {
+ RealmResults realmContacts = realm.where(RealmContact.class)
+ .findAll();
+
+ if (realmContacts != null) {
+ for (RealmContact item : realmContacts) {
+ contacts.add(createUserInfo(item));
+ }
+ }
+ Collections.sort(contacts, (o1, o2) -> o1.getName().compareToIgnoreCase(o2.getName()));
+ return contacts;
+ } catch (Exception e) {
+ Timber.e(e);
+ return null;
+ }
+ }
+
+ private AddressBookContact searchContactFromList(String walletAddress, List contacts) {
+ for (AddressBookContact contact : contacts) {
+ if (contact.getWalletAddress().equalsIgnoreCase(walletAddress)) {
+ return contact;
+ }
+ }
+ return null;
+ }
+
+ private AddressBookContact createUserInfo(RealmContact realmItem)
+ {
+ return new AddressBookContact(realmItem.getWalletAddress(), realmItem.getName(), realmItem.getEthName());
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/repository/ContactRepositoryType.java b/app/src/main/java/com/alphawallet/app/repository/ContactRepositoryType.java
new file mode 100644
index 0000000000..63a8b58759
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/repository/ContactRepositoryType.java
@@ -0,0 +1,21 @@
+package com.alphawallet.app.repository;
+
+import com.alphawallet.app.entity.AddressBookContact;
+
+import java.util.List;
+
+import io.reactivex.Completable;
+import io.reactivex.Single;
+
+/**
+ * Created by Asif Ghanchi on 24/11/21
+ * Repository for managing Contacts for Address Book.
+ */
+public interface ContactRepositoryType {
+
+ Single> getContacts();
+ Completable addContact(AddressBookContact contact);
+ Completable updateContact(AddressBookContact oldContact, AddressBookContact newContact);
+ Completable removeContact(AddressBookContact contact);
+ Single searchContact(String walletAddress);
+}
diff --git a/app/src/main/java/com/alphawallet/app/repository/entity/RealmContact.java b/app/src/main/java/com/alphawallet/app/repository/entity/RealmContact.java
new file mode 100644
index 0000000000..d6ed23076a
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/repository/entity/RealmContact.java
@@ -0,0 +1,50 @@
+package com.alphawallet.app.repository.entity;
+
+import io.realm.RealmObject;
+import io.realm.annotations.PrimaryKey;
+
+/**
+ * Created by Chintan on 20/10/2021.
+ */
+public class RealmContact extends RealmObject
+{
+ /**
+ * This is contact wallet address
+ */
+ @PrimaryKey
+ private String walletAddress;
+
+ /**
+ * This is contact name
+ */
+ private String name;
+
+ /**
+ * This is associated ETH name of #walletAddress
+ */
+ private String ethName;
+
+ public String getWalletAddress() {
+ return walletAddress;
+ }
+
+ public void setWalletAddress(String walletAddress) {
+ this.walletAddress = walletAddress;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getEthName() {
+ return ethName;
+ }
+
+ public void setEthName(String ethName) {
+ this.ethName = ethName;
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/router/AddEditAddressRouter.java b/app/src/main/java/com/alphawallet/app/router/AddEditAddressRouter.java
new file mode 100644
index 0000000000..b3719ffe6c
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/router/AddEditAddressRouter.java
@@ -0,0 +1,39 @@
+package com.alphawallet.app.router;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+
+import com.alphawallet.app.C;
+import com.alphawallet.app.entity.AddressBookContact;
+import com.alphawallet.app.ui.AddEditAddressActivity;
+
+public class AddEditAddressRouter {
+
+ public void open(Context context, int requestCode) {
+ ((Activity) context).startActivityForResult(new Intent(context, AddEditAddressActivity.class), requestCode);
+ }
+
+ /**
+ * Use to auto fill wallet address when called from Transaction Detail screen.
+ * @param context
+ * @param walletAddress Wallet Address to auto fill
+ */
+ public void open(Context context, int requestCode, String walletAddress) {
+ Intent intent = new Intent(context, AddEditAddressActivity.class);
+ intent.putExtra(C.EXTRA_CONTACT_WALLET_ADDRESS, walletAddress);
+ ((Activity) context).startActivityForResult(intent, requestCode);
+ }
+
+ /**
+ * Use to pass a contact for editing.
+ * @param context
+ * @param requestCode
+ * @param contact
+ */
+ public void open(Context context, int requestCode, AddressBookContact contact) {
+ Intent intent = new Intent(context, AddEditAddressActivity.class);
+ intent.putExtra(C.EXTRA_CONTACT, contact);
+ ((Activity) context).startActivityForResult(intent, requestCode);
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/router/AddressBookRouter.java b/app/src/main/java/com/alphawallet/app/router/AddressBookRouter.java
new file mode 100644
index 0000000000..7ed10d98ae
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/router/AddressBookRouter.java
@@ -0,0 +1,19 @@
+package com.alphawallet.app.router;
+
+import android.content.Context;
+import android.content.Intent;
+
+import com.alphawallet.app.ui.AddressBookActivity;
+import com.alphawallet.app.ui.BaseActivity;
+
+public class AddressBookRouter {
+
+ public void open(Context context) {
+ context.startActivity(new Intent(context, AddressBookActivity.class));
+ }
+
+ public void openForContactSelection(Context context, int requestCode) {
+ Intent i = new Intent(context, AddressBookActivity.class);
+ ((BaseActivity) context).startActivityForResult(i, requestCode);
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/service/RealmManager.java b/app/src/main/java/com/alphawallet/app/service/RealmManager.java
index 452891ef9c..738e6dbb1f 100644
--- a/app/src/main/java/com/alphawallet/app/service/RealmManager.java
+++ b/app/src/main/java/com/alphawallet/app/service/RealmManager.java
@@ -64,4 +64,8 @@ public Realm getWalletDataRealmInstance() {
public Realm getWalletTypeRealmInstance() {
return getRealmInstanceInternal("WalletType-db.realm");
}
+
+ public Realm getAddressBookRealmInstance() {
+ return getRealmInstanceInternal("AddressBook-db.realm");
+ }
}
diff --git a/app/src/main/java/com/alphawallet/app/ui/ActivityFragment.java b/app/src/main/java/com/alphawallet/app/ui/ActivityFragment.java
index 3d4c9798cd..90e066a9bb 100644
--- a/app/src/main/java/com/alphawallet/app/ui/ActivityFragment.java
+++ b/app/src/main/java/com/alphawallet/app/ui/ActivityFragment.java
@@ -180,7 +180,7 @@ private List getTokenTransfersForHash(Realm realm, Transactio
private void initViews(View view)
{
adapter = new ActivityAdapter(viewModel.getTokensService(), viewModel.provideTransactionsInteract(),
- viewModel.getAssetDefinitionService(), this);
+ viewModel.getAssetDefinitionService(), this, viewModel.getAddressBookInteract());
SwipeRefreshLayout refreshLayout = view.findViewById(R.id.refresh_layout);
systemView = view.findViewById(R.id.system_view);
listView = view.findViewById(R.id.list);
diff --git a/app/src/main/java/com/alphawallet/app/ui/AddEditAddressActivity.java b/app/src/main/java/com/alphawallet/app/ui/AddEditAddressActivity.java
new file mode 100644
index 0000000000..129a4d3417
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/ui/AddEditAddressActivity.java
@@ -0,0 +1,225 @@
+package com.alphawallet.app.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.ViewModelProvider;
+
+import com.alphawallet.app.C;
+import com.alphawallet.app.R;
+import com.alphawallet.app.entity.AddressBookContact;
+import com.alphawallet.app.ui.widget.entity.AddressReadyCallback;
+import com.alphawallet.app.util.Utils;
+import com.alphawallet.app.viewmodel.AddEditAddressViewModel;
+import com.alphawallet.app.widget.InputAddress;
+import com.alphawallet.app.widget.InputView;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import dagger.hilt.android.AndroidEntryPoint;
+import java8.util.Optional;
+import timber.log.Timber;
+
+@AndroidEntryPoint
+public class AddEditAddressActivity extends BaseActivity implements AddressReadyCallback {
+ private static final String TAG = "AddAddressActivity";
+
+ AddEditAddressViewModel viewModel;
+
+ private ADDRESS_OPERATION_MODE mode = ADDRESS_OPERATION_MODE.ADD; // default
+ private InputAddress inputViewAddress;
+ private InputView inputViewName;
+ private Button buttonSave;
+ private AddressBookContact contactToEdit = null;
+ private Map ensResolutionCache = new HashMap<>(); // address -> ensName cache to get ens name of entered wallet address
+ private enum ADDRESS_OPERATION_MODE {
+ ADD, EDIT
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_add_address);
+
+ toolbar();
+
+ contactToEdit = (AddressBookContact) getIntent().getParcelableExtra(C.EXTRA_CONTACT);
+ if (contactToEdit == null) {
+ setTitle(getString(R.string.title_add_address));
+ } else {
+ mode = ADDRESS_OPERATION_MODE.EDIT;
+ setTitle(getString(R.string.title_edit_address));
+ }
+ initViews();
+
+ viewModel.address.observe(this, this::onAddressCheck);
+ viewModel.contactName.observe(this, this::onNameCheck);
+ viewModel.saveContact.observe(this, this::onSave);
+ viewModel.updateContact.observe(this, this::onUpdateContact);
+ viewModel.error.observe(this, this::onError);
+ }
+
+ private void initViews() {
+ viewModel = new ViewModelProvider(this)
+ .get(AddEditAddressViewModel.class);
+
+ inputViewAddress = findViewById(R.id.input_view_address);
+ inputViewName = findViewById(R.id.input_view_name);
+
+ inputViewAddress.setAddressCallback(this);
+// inputViewAddress.enableFocusListener();
+ inputViewName.enableFocusListener();
+
+ inputViewAddress.getEditText().setImeOptions(EditorInfo.IME_ACTION_NEXT);
+
+ inputViewName.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
+
+ buttonSave = findViewById(R.id.btn_save);
+ buttonSave.setOnClickListener( (v) -> {
+ String address = inputViewAddress.getInputText();
+ String name = inputViewName.getText().toString().trim();
+ boolean isDataValid = true;
+ if (address.isEmpty()) {
+ inputViewAddress.setError(getString(R.string.error_address_empty));
+ isDataValid = false;
+ } else if (!Utils.isAddressValid(address)) {
+ inputViewAddress.setError(getString(R.string.error_invalid_address));
+ isDataValid = false;
+ }
+ if (name.isEmpty()) {
+ inputViewName.setError("Name should not be empty");
+ isDataValid = false;
+ }
+ if (isDataValid) {
+ if (mode == ADDRESS_OPERATION_MODE.ADD) {
+ String ensName = ensResolutionCache.get(inputViewAddress.getInputText());
+ ensName = ensName == null ? "" : ensName;
+ viewModel.onClickSave(address, name, ensName);
+ } else {
+ viewModel.updateContact(contactToEdit, new AddressBookContact(address, name, contactToEdit.getEthName()));
+ }
+ }
+ });
+
+ if (mode == ADDRESS_OPERATION_MODE.EDIT) {
+ inputViewAddress.setHandleENS(false);
+ inputViewAddress.setAddressCallback(null);
+ inputViewAddress.setAddress(contactToEdit.getWalletAddress());
+ inputViewName.setText(contactToEdit.getName());
+ inputViewAddress.setEditable(false);
+ inputViewName.requestFocus();
+ } else {
+ String walletAddress = getIntent().getStringExtra(C.EXTRA_CONTACT_WALLET_ADDRESS);
+ if (walletAddress != null) {
+ inputViewAddress.setAddress(walletAddress);
+ }
+ }
+ }
+
+ private void onAddressCheck(Optional userInfo) {
+ if (userInfo.isPresent()) {
+ // show error
+ inputViewAddress.setError(getString(R.string.error_contact_address_exists, userInfo.get().getName()));
+ } else {
+ viewModel.checkName(inputViewName.getText().toString());
+ }
+ }
+
+ private void onNameCheck(Optional userInfo) {
+ if (userInfo.isPresent()) {
+ // show error
+ inputViewName.setError(getString(R.string.error_contact_name_taken));
+ } else {
+ viewModel.addContact();
+ }
+ }
+
+ private void onSave(Boolean isSaved) {
+ if (isSaved) {
+ setResult(RESULT_OK);
+ finish();
+ }
+
+ }
+
+ private void onUpdateContact(boolean success) {
+ if (success) {
+ setResult(RESULT_OK);
+ } else {
+ setResult(RESULT_CANCELED);
+ }
+ finish();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (mode == ADDRESS_OPERATION_MODE.EDIT) {
+ getMenuInflater().inflate(R.menu.menu_close, menu);
+ }
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.action_cancel) {
+ super.onBackPressed();
+ return true;
+ } else {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ switch (requestCode) {
+ case C.BARCODE_READER_REQUEST_CODE: {
+ if (resultCode == RESULT_OK && data != null) {
+ String qrData = data.getStringExtra(C.EXTRA_QR_CODE);
+ Timber.d("onActivityResult: data: "+data+"\nqr: "+qrData);
+ if (Utils.isAddressValid(qrData)) {
+ inputViewAddress.setAddress(qrData);
+ }
+ }
+ }
+ }
+ }
+
+ private void onError(Throwable error) {
+ Timber.e(error, "onError: ");
+ displayToast(error.getMessage());
+ }
+
+ @Override
+ public void addressReady(String address, String ensName) {
+ Timber.d("Address ready: address: %s, ensName: %s", address, ensName);
+
+ }
+
+ @Override
+ public void resolvedAddress(String address, String ensName) {
+ AddressReadyCallback.super.resolvedAddress(address, ensName);
+ Timber.d("resolvedAddress: %s, %s", address, ensName);
+ // if the input address content is unchanged and this resolution is for that content
+ if (inputViewAddress.getInputText().equalsIgnoreCase(ensName) || inputViewAddress.getInputText().equalsIgnoreCase(address)) {
+ inputViewAddress.setHandleENS(false); // disable ens resolver
+ inputViewAddress.setAddress(address); // set the resolved address
+ inputViewAddress.setHandleENS(true); // enable ens resolver
+ }
+ inputViewName.setText(ensName); // prefill name using ens name
+ ensResolutionCache.put(address, ensName);
+ }
+
+ @Override
+ public void addressValid(boolean valid) {
+ AddressReadyCallback.super.addressValid(valid);
+ Timber.d("address Valid: %s", valid);
+ }
+
+}
diff --git a/app/src/main/java/com/alphawallet/app/ui/AddressBookActivity.java b/app/src/main/java/com/alphawallet/app/ui/AddressBookActivity.java
new file mode 100644
index 0000000000..e1149393ec
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/ui/AddressBookActivity.java
@@ -0,0 +1,314 @@
+package com.alphawallet.app.ui;
+
+import android.content.Intent;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.recyclerview.widget.ItemTouchHelper;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.alphawallet.app.C;
+import com.alphawallet.app.R;
+import com.alphawallet.app.entity.AddressBookContact;
+import com.alphawallet.app.entity.Wallet;
+import com.alphawallet.app.router.AddEditAddressRouter;
+import com.alphawallet.app.router.HomeRouter;
+import com.alphawallet.app.ui.widget.adapter.AddressBookAdapter;
+import com.alphawallet.app.ui.widget.adapter.TokenListAdapter;
+import com.alphawallet.app.ui.widget.decorator.RecyclerViewSwipeDecorator;
+import com.alphawallet.app.viewmodel.AddressBookViewModel;
+import com.alphawallet.app.widget.AWalletAlertDialog;
+import com.google.android.material.snackbar.Snackbar;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import dagger.hilt.android.AndroidEntryPoint;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.schedulers.Schedulers;
+import io.reactivex.subjects.PublishSubject;
+import timber.log.Timber;
+
+@AndroidEntryPoint
+public class AddressBookActivity extends BaseActivity
+{
+ private static final String TAG = "AddressBookActivity";
+
+ AddressBookViewModel viewModel;
+
+ private RecyclerView contactList;
+ private Button saveButton;
+ private TokenListAdapter adapter;
+ private EditText search;
+ private LinearLayout searchTokens;
+ private LinearLayout emptyAddressBook;
+ private Button addContactButton;
+
+ private Wallet wallet;
+ private String realmId;
+
+ private Handler delayHandler = new Handler(Looper.getMainLooper());
+
+ private final ArrayList contacts = new ArrayList<>();
+
+ /**
+ * Flag to check whether activity is started for result or not.
+ */
+ private Boolean setResult = false;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_address_book);
+ toolbar();
+ setResult = getCallingActivity() != null;
+ setTitle(setResult ? getString(R.string.title_select_recipient) : getString(R.string.title_address_book));
+ initViews();
+
+
+ }
+
+ private void initViews()
+ {
+ viewModel = new ViewModelProvider(this)
+ .get(AddressBookViewModel.class);
+
+ viewModel.contactsLD.observe(this, this::updateContactList);
+ viewModel.filteredContacts.observe(this, this::onContactsFiltered);
+
+// wallet = new Wallet(viewModel.getTokensService().getCurrentAddress());
+ contactList = findViewById(R.id.contact_list);
+ saveButton = findViewById(R.id.btn_apply);
+ search = findViewById(R.id.edit_search);
+ searchTokens = findViewById(R.id.layout_search_tokens);
+ emptyAddressBook = findViewById(R.id.layout_empty_address_book);
+ addContactButton = findViewById(R.id.btn_add_contact);
+
+ contactList.setLayoutManager(new LinearLayoutManager(this));
+
+ saveButton.setOnClickListener(v -> {
+ new HomeRouter().open(this, true);
+ });
+
+ addContactButton.setOnClickListener( (v) -> {
+ startAddAddressActivity();
+ });
+
+ contactList.requestFocus();
+// search.addTextChangedListener(textWatcher);
+ setupSearch();
+
+ AddressBookAdapter adapter = new AddressBookAdapter(contacts, this::OnAddressSelected);
+ contactList.setAdapter(adapter);
+
+ // Create and add a callback
+ ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
+ @Override
+ public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
+ return false;
+ }
+
+ @Override
+ public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
+ try
+ {
+ if (direction == ItemTouchHelper.RIGHT)
+ {
+ return;
+ }
+ final int position = viewHolder.getAdapterPosition();
+ final AddressBookContact item = adapter.removeItem(position);
+ viewModel.removeContact(item);
+ Snackbar snackbar = Snackbar.make(viewHolder.itemView, "Item " + (direction == ItemTouchHelper.LEFT ? "deleted" : "archived") + ".", Snackbar.LENGTH_LONG);
+ snackbar.setAction(android.R.string.cancel, new View.OnClickListener() {
+
+ @Override
+ public void onClick(View view) {
+ try
+ {
+ adapter.addItem(item, position);
+ viewModel.addContact(item);
+ }
+ catch(Exception e)
+ {
+ Timber.e(e);
+ }
+ }
+ });
+ snackbar.show();
+ }
+ catch(Exception e)
+ {
+ Timber.e(e);
+ }
+ }
+
+ // You must use @RecyclerViewSwipeDecorator inside the onChildDraw method
+ @Override
+ public void onChildDraw (Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive){
+ new RecyclerViewSwipeDecorator.Builder(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
+ .addSwipeLeftBackgroundColor(ContextCompat.getColor(AddressBookActivity.this, R.color.danger))
+ .addSwipeLeftActionIcon(R.drawable.ic_delete_contact)
+ .addSwipeLeftLabel(getString(R.string.delete))
+ .setSwipeLeftLabelColor(Color.WHITE)
+ .create()
+ .decorate();
+ super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
+ }
+ };
+ ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
+ if (!setResult) itemTouchHelper.attachToRecyclerView(contactList);
+
+ }
+
+ private void updateContactList(List contacts)
+ {
+ if (contacts.size() == 0) {
+ searchTokens.setVisibility(View.INVISIBLE);
+ contactList.setVisibility(View.INVISIBLE);
+ emptyAddressBook.setVisibility(View.VISIBLE);
+ } else {
+ searchTokens.setVisibility(View.VISIBLE);
+ contactList.setVisibility(View.VISIBLE);
+ emptyAddressBook.setVisibility(View.INVISIBLE);
+ }
+ this.contacts.clear();
+ this.contacts.addAll(contacts);
+ for (AddressBookContact contact : contacts)
+ {
+ Timber.d("Contact : " + contact.getWalletAddress() + ", " + contact.getName());
+ }
+ contactList.getAdapter().notifyDataSetChanged();
+ }
+
+ private void OnAddressSelected(int position, AddressBookContact addressBookContact) {
+ Timber.d("OnAddressSelected: pos: %s", position);
+
+ if (setResult) {
+ Intent resultIntent = new Intent();
+ resultIntent.putExtra(C.EXTRA_CONTACT, addressBookContact);
+ setResult(RESULT_OK, resultIntent);
+ finish();
+ } else {
+ // edit address
+ new AddEditAddressRouter().open(this, C.UPDATE_ADDRESS_REQUEST_CODE, addressBookContact);
+ }
+ }
+
+ private void setupSearch() {
+ search.setImeOptions(EditorInfo.IME_ACTION_DONE);
+ PublishSubject subject = PublishSubject.create();
+ subject.debounce(300, TimeUnit.MILLISECONDS)
+ .subscribeOn(Schedulers.computation())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(viewModel::filterByName, (throwable -> Timber.e(throwable, "searchException")))
+ .isDisposed();
+
+ search.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ subject.onNext(s.toString());
+ }
+ });
+ }
+
+ private void onContactsFiltered(List contacts) {
+ this.contacts.clear();
+ this.contacts.addAll(contacts);
+ for (AddressBookContact contact : contacts)
+ {
+ Timber.d("onContactsFiltered: Contact : " + contact.getWalletAddress() + ", " + contact.getName());
+ }
+ contactList.getAdapter().notifyDataSetChanged();
+ }
+
+// private void showSnackBar(String text)
+// {
+// Snackbar.make(findViewById(R.id.button_sign_in), text, Snackbar.LENGTH_LONG).show();
+// }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ viewModel.loadContacts();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (!setResult) getMenuInflater().inflate(R.menu.menu_add, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_add: {
+ startAddAddressActivity();
+ }
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ switch (requestCode) {
+ case C.ADD_ADDRESS_REQUEST_CODE: {
+ if (resultCode == RESULT_OK) {
+ // show tick dialog
+ AWalletAlertDialog dialog = new AWalletAlertDialog(this);
+ dialog.setIcon(AWalletAlertDialog.SUCCESS);
+ dialog.setMessage(getString(R.string.msg_address_added_succes));
+ dialog.setTextStyle(AWalletAlertDialog.TEXT_STYLE.CENTERED);
+ dialog.show();
+ }
+ }
+ break;
+
+ case C.UPDATE_ADDRESS_REQUEST_CODE: {
+ if (resultCode == RESULT_OK) {
+ // show tick dialog
+ AWalletAlertDialog dialog = new AWalletAlertDialog(this);
+ dialog.setIcon(AWalletAlertDialog.SUCCESS);
+ dialog.setMessage(getString(R.string.msg_address_update_succes));
+ dialog.setTextStyle(AWalletAlertDialog.TEXT_STYLE.CENTERED);
+ dialog.show();
+ viewModel.loadContacts();
+ }
+ }
+ break;
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ private void startAddAddressActivity() {
+ new AddEditAddressRouter().open(this, C.ADD_ADDRESS_REQUEST_CODE);
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/ui/Erc20DetailActivity.java b/app/src/main/java/com/alphawallet/app/ui/Erc20DetailActivity.java
index dbece60b0f..95804d6040 100644
--- a/app/src/main/java/com/alphawallet/app/ui/Erc20DetailActivity.java
+++ b/app/src/main/java/com/alphawallet/app/ui/Erc20DetailActivity.java
@@ -229,7 +229,7 @@ private void setUpRecentTransactionsView()
if (activityHistoryList != null) return;
activityHistoryList = findViewById(R.id.history_list);
ActivityAdapter adapter = new ActivityAdapter(viewModel.getTokensService(), viewModel.getTransactionsInteract(),
- viewModel.getAssetDefinitionService());
+ viewModel.getAssetDefinitionService(), viewModel.getAddressBookInteract());
adapter.setDefaultWallet(wallet);
diff --git a/app/src/main/java/com/alphawallet/app/ui/NewSettingsFragment.java b/app/src/main/java/com/alphawallet/app/ui/NewSettingsFragment.java
index a161916e02..32fdd013f8 100644
--- a/app/src/main/java/com/alphawallet/app/ui/NewSettingsFragment.java
+++ b/app/src/main/java/com/alphawallet/app/ui/NewSettingsFragment.java
@@ -40,6 +40,7 @@
import com.alphawallet.app.entity.WalletPage;
import com.alphawallet.app.entity.WalletType;
import com.alphawallet.app.interact.GenericWalletInteract;
+import com.alphawallet.app.router.AddressBookRouter;
import com.alphawallet.app.util.LocaleUtils;
import com.alphawallet.app.util.UpdateUtils;
import com.alphawallet.app.viewmodel.NewSettingsViewModel;
@@ -72,6 +73,7 @@ public class NewSettingsFragment extends BaseFragment {
private SettingsItemView walletConnectSetting;
private SettingsItemView showSeedPhrase;
private SettingsItemView nameThisWallet;
+ private SettingsItemView addressBook;
private LinearLayout layoutBackup;
private View warningSeparator;
@@ -186,6 +188,13 @@ private void initializeSettings(View view) {
.withListener(this::onNameThisWallet)
.build();
+ addressBook =
+ new SettingsItemView.Builder(getContext())
+ .withIcon(R.drawable.ic_address_book)
+ .withTitle(R.string.title_address_book)
+ .withListener(this::onAddressBookSettingClicked)
+ .build();
+
walletConnectSetting =
new SettingsItemView.Builder(getContext())
.withIcon(R.drawable.ic_wallet_connect)
@@ -248,6 +257,8 @@ private void addSettingsToLayout() {
walletSettingsLayout.addView(nameThisWallet, walletIndex++);
+ walletSettingsLayout.addView(addressBook, walletIndex++);
+
walletSettingsLayout.addView(walletConnectSetting, walletIndex++);
systemSettingsLayout.addView(notificationsSetting, systemIndex++);
@@ -486,6 +497,15 @@ private void onNameThisWallet()
requireActivity().startActivity(intent);
}
+ private void onAddressBookSettingClicked() {
+ new AddressBookRouter().open(getContext());
+ }
+
+ private void openAddressBookActivity() {
+ Intent intent = new Intent(getActivity(), AddressBookActivity.class);
+ getActivity().startActivity(intent);
+ }
+
private void onNotificationsSettingClicked() {
viewModel.setNotificationState(notificationsSetting.getToggleState());
}
diff --git a/app/src/main/java/com/alphawallet/app/ui/SendActivity.java b/app/src/main/java/com/alphawallet/app/ui/SendActivity.java
index bcd1ba6671..57f6f8c71b 100644
--- a/app/src/main/java/com/alphawallet/app/ui/SendActivity.java
+++ b/app/src/main/java/com/alphawallet/app/ui/SendActivity.java
@@ -1,7 +1,6 @@
package com.alphawallet.app.ui;
import static com.alphawallet.app.C.Key.WALLET;
-import static com.alphawallet.app.repository.EthereumNetworkBase.hasGasOverride;
import static com.alphawallet.app.widget.AWalletAlertDialog.ERROR;
import static com.alphawallet.app.widget.AWalletAlertDialog.WARNING;
import static com.alphawallet.ethereum.EthereumNetworkBase.MAINNET_ID;
@@ -30,6 +29,7 @@
import com.alphawallet.app.entity.SignAuthenticationCallback;
import com.alphawallet.app.entity.StandardFunctionInterface;
import com.alphawallet.app.entity.TransactionData;
+import com.alphawallet.app.entity.AddressBookContact;
import com.alphawallet.app.entity.Wallet;
import com.alphawallet.app.entity.tokens.Token;
import com.alphawallet.app.repository.EthereumNetworkBase;
@@ -287,6 +287,15 @@ else if (qrCode.startsWith("wc:"))
));
break;
}
+ } else if (requestCode == C.ADDRESS_BOOK_CONTACT_REQUEST_CODE) {
+ if (data != null) {
+ // set selected contact returned by AddressBook
+ AddressBookContact addressBookContact = data.getParcelableExtra(C.EXTRA_CONTACT);
+ if (addressBookContact != null) {
+ addressInput.onContactSelected(addressBookContact);
+ }
+ }
+// addressInput.onContactSelected();
} else {
super.onActivityResult(requestCode, resultCode, data);
}
diff --git a/app/src/main/java/com/alphawallet/app/ui/TokenActivityFragment.java b/app/src/main/java/com/alphawallet/app/ui/TokenActivityFragment.java
index 32011891c2..945e4b69b3 100644
--- a/app/src/main/java/com/alphawallet/app/ui/TokenActivityFragment.java
+++ b/app/src/main/java/com/alphawallet/app/ui/TokenActivityFragment.java
@@ -66,7 +66,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
private void setUpRecentTransactionsView()
{
ActivityAdapter adapter = new ActivityAdapter(viewModel.getTokensService(), viewModel.getTransactionsInteract(),
- viewModel.getAssetDefinitionService());
+ viewModel.getAssetDefinitionService(), viewModel.getAddressBookInteract());
adapter.setDefaultWallet(wallet);
history.setupAdapter(adapter);
history.startActivityListeners(viewModel.getRealmInstance(wallet), wallet,
diff --git a/app/src/main/java/com/alphawallet/app/ui/TokenFunctionActivity.java b/app/src/main/java/com/alphawallet/app/ui/TokenFunctionActivity.java
index a9deca3260..891993d03e 100644
--- a/app/src/main/java/com/alphawallet/app/ui/TokenFunctionActivity.java
+++ b/app/src/main/java/com/alphawallet/app/ui/TokenFunctionActivity.java
@@ -161,7 +161,7 @@ private void setTokenListener()
private void setEventListener(Wallet wallet)
{
ActivityAdapter adapter = new ActivityAdapter(viewModel.getTokensService(), viewModel.getTransactionsInteract(),
- viewModel.getAssetDefinitionService());
+ viewModel.getAssetDefinitionService(), viewModel.getAddressBookInteract());
adapter.setDefaultWallet(wallet);
diff --git a/app/src/main/java/com/alphawallet/app/ui/TransactionDetailActivity.java b/app/src/main/java/com/alphawallet/app/ui/TransactionDetailActivity.java
index 383fd98036..ae0a03fa72 100644
--- a/app/src/main/java/com/alphawallet/app/ui/TransactionDetailActivity.java
+++ b/app/src/main/java/com/alphawallet/app/ui/TransactionDetailActivity.java
@@ -21,6 +21,7 @@
import com.alphawallet.app.entity.StandardFunctionInterface;
import com.alphawallet.app.entity.Transaction;
import com.alphawallet.app.entity.TransactionData;
+import com.alphawallet.app.entity.AddressBookContact;
import com.alphawallet.app.entity.Wallet;
import com.alphawallet.app.entity.tokens.Token;
import com.alphawallet.app.repository.EthereumNetworkRepository;
@@ -72,6 +73,9 @@ public class TransactionDetailActivity extends BaseActivity implements StandardF
private ActionSheetDialog confirmationDialog;
private String tokenAddress;
+ private CopyTextView toValue;
+ private CopyTextView fromValue;
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -83,12 +87,17 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
viewModel.onTransaction().observe(this, this::onTransaction);
viewModel.transactionFinalised().observe(this, this::txWritten);
viewModel.transactionError().observe(this, this::txError);
+ viewModel.senderAddressContact().observe(this, this::onSenderContactFound);
+ viewModel.receiverAddressContact().observe(this, this::onReceiverContactFound);
String txHash = getIntent().getStringExtra(C.EXTRA_TXHASH);
long chainId = getIntent().getLongExtra(C.EXTRA_CHAIN_ID, MAINNET_ID);
wallet = getIntent().getParcelableExtra(WALLET);
tokenAddress = getIntent().getStringExtra(C.EXTRA_ADDRESS);
viewModel.fetchTransaction(wallet, txHash, chainId);
+
+ toValue = findViewById(R.id.to);
+ fromValue = findViewById(R.id.from);
}
private void onTransaction(Transaction tx)
@@ -119,12 +128,16 @@ private void onTransaction(Transaction tx)
setupVisibilities();
amount = findViewById(R.id.amount);
- CopyTextView toValue = findViewById(R.id.to);
- CopyTextView fromValue = findViewById(R.id.from);
+ toValue = findViewById(R.id.to);
+ fromValue = findViewById(R.id.from);
CopyTextView txHashView = findViewById(R.id.txn_hash);
fromValue.setText(transaction.from != null ? transaction.from : "");
toValue.setText(transaction.to != null ? transaction.to : "");
+
+ viewModel.resolveSenderAddress(fromValue.getText());
+ viewModel.resolveReceiverAddress(toValue.getText());
+
txHashView.setText(transaction.hash != null ? transaction.hash : "");
((TextView) findViewById(R.id.txn_time)).setText(Utils.localiseUnixDate(getApplicationContext(), transaction.timeStamp));
@@ -367,6 +380,19 @@ else if (requestCode >= SignTransactionDialog.REQUEST_CODE_CONFIRM_DEVICE_CREDEN
confirmationDialog.completeSignRequest(resultCode == RESULT_OK);
}
}
+ else if (requestCode == C.ADD_ADDRESS_REQUEST_CODE) {
+ if (resultCode == RESULT_OK) {
+ viewModel.loadAddressBook();
+ viewModel.resolveSenderAddress(fromValue.getText());
+ viewModel.resolveReceiverAddress(toValue.getText());
+ // show tick dialog
+ AWalletAlertDialog dialog = new AWalletAlertDialog(this);
+ dialog.setIcon(AWalletAlertDialog.SUCCESS);
+ dialog.setMessage(getString(R.string.msg_address_added_succes));
+ dialog.setTextStyle(AWalletAlertDialog.TEXT_STYLE.CENTERED);
+ dialog.show();
+ }
+ }
else
{
super.onActivityResult(requestCode, resultCode, data);
@@ -428,4 +454,14 @@ private void txError(Throwable throwable)
dialog.show();
confirmationDialog.dismiss();
}
+
+ private void onSenderContactFound(AddressBookContact addressBookContact) {
+ CopyTextView fromValue = findViewById(R.id.from);
+ fromValue.setAddressName(addressBookContact.getName());
+ }
+
+ private void onReceiverContactFound(AddressBookContact addressBookContact) {
+ CopyTextView toValue = findViewById(R.id.to);
+ toValue.setAddressName(addressBookContact.getName());
+ }
}
diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/OnAddressBookItemCLickListener.java b/app/src/main/java/com/alphawallet/app/ui/widget/OnAddressBookItemCLickListener.java
new file mode 100644
index 0000000000..f48998f5d5
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/ui/widget/OnAddressBookItemCLickListener.java
@@ -0,0 +1,7 @@
+package com.alphawallet.app.ui.widget;
+
+import com.alphawallet.app.entity.AddressBookContact;
+
+public interface OnAddressBookItemCLickListener {
+ void OnAddressSelected(int position, AddressBookContact addressBookContact);
+}
diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/ActivityAdapter.java b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/ActivityAdapter.java
index f4feead330..1bec7f6cf7 100644
--- a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/ActivityAdapter.java
+++ b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/ActivityAdapter.java
@@ -24,6 +24,7 @@
import com.alphawallet.app.entity.TransactionMeta;
import com.alphawallet.app.entity.Wallet;
import com.alphawallet.app.interact.ActivityDataInteract;
+import com.alphawallet.app.interact.AddressBookInteract;
import com.alphawallet.app.interact.FetchTransactionsInteract;
import com.alphawallet.app.service.AssetDefinitionService;
import com.alphawallet.app.service.TokensService;
@@ -92,6 +93,7 @@ public void onMoved(int fromPosition, int toPosition) {
private final FetchTransactionsInteract fetchTransactionsInteract;
private final ActivityDataInteract dataInteract;
private final AssetDefinitionService assetService;
+ private final AddressBookInteract addressBookInteract;
private long fetchData = 0;
private final Handler handler = new Handler();
private int itemLimit = 0;
@@ -99,19 +101,21 @@ public void onMoved(int fromPosition, int toPosition) {
private boolean pendingReset = false;
public ActivityAdapter(TokensService service, FetchTransactionsInteract fetchTransactionsInteract,
- AssetDefinitionService svs, ActivityDataInteract dataInteract) {
+ AssetDefinitionService svs, ActivityDataInteract dataInteract, AddressBookInteract addressBookInteract) {
this.fetchTransactionsInteract = fetchTransactionsInteract;
this.dataInteract = dataInteract;
this.assetService = svs;
tokensService = service;
+ this.addressBookInteract = addressBookInteract;
}
- public ActivityAdapter(TokensService service, FetchTransactionsInteract fetchTransactionsInteract, AssetDefinitionService svs)
+ public ActivityAdapter(TokensService service, FetchTransactionsInteract fetchTransactionsInteract, AssetDefinitionService svs, AddressBookInteract addressBookInteract)
{
this.fetchTransactionsInteract = fetchTransactionsInteract;
tokensService = service;
this.dataInteract = null;
this.assetService = svs;
+ this.addressBookInteract = addressBookInteract;
}
@Override
@@ -119,7 +123,7 @@ public BinderViewHolder> onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case TransactionHolder.VIEW_TYPE:
return new TransactionHolder(parent, tokensService, fetchTransactionsInteract,
- assetService);
+ assetService, addressBookInteract);
case EventHolder.VIEW_TYPE:
return new EventHolder(parent, tokensService, fetchTransactionsInteract,
assetService, this);
diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/AddressBookAdapter.java b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/AddressBookAdapter.java
new file mode 100644
index 0000000000..2502522061
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/AddressBookAdapter.java
@@ -0,0 +1,101 @@
+package com.alphawallet.app.ui.widget.adapter;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.alphawallet.app.R;
+import com.alphawallet.app.entity.AddressBookContact;
+import com.alphawallet.app.ui.widget.OnAddressBookItemCLickListener;
+
+import java.util.List;
+
+import timber.log.Timber;
+
+public class AddressBookAdapter extends RecyclerView.Adapter {
+ private List data;
+ private OnAddressBookItemCLickListener listener;
+
+ class ViewHolder extends RecyclerView.ViewHolder {
+ ImageView icon;
+ TextView name, address;
+ View root;
+
+ ViewHolder(@NonNull View itemView) {
+ super(itemView);
+ icon = itemView.findViewById(R.id.icon);
+ name = itemView.findViewById(R.id.name);
+ address = itemView.findViewById(R.id.address);
+ root = itemView.findViewById(R.id.layout_list_item);
+ }
+ }
+
+ public AddressBookAdapter(List data, OnAddressBookItemCLickListener listener) {
+ this.data = data;
+ this.listener = listener;
+ }
+
+ public void addItem(AddressBookContact item, int position) {
+ try {
+ data.add(position, item);
+ notifyItemInserted(position);
+ } catch(Exception e) {
+ Timber.e(e);
+ }
+ }
+
+ public void setData(List data) {
+ this.data = data;
+ notifyDataSetChanged();
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
+ View view = LayoutInflater.from(viewGroup.getContext())
+ .inflate(R.layout.item_contact, viewGroup, false);
+
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
+ AddressBookContact addressBookContact = data.get(i);
+ viewHolder.name.setText(addressBookContact.getName());
+ viewHolder.address.setText(addressBookContact.getWalletAddress());
+
+ if (!addressBookContact.getEthName().isEmpty()) {
+ viewHolder.address.setText(addressBookContact.getEthName() + " | " + addressBookContact.getWalletAddress());
+ } else {
+ viewHolder.address.setText(addressBookContact.getWalletAddress());
+ }
+ viewHolder.root.setOnClickListener( (v ->{
+ listener.OnAddressSelected(i, data.get(i));
+ }));
+ }
+
+ @Override
+ public int getItemCount() {
+ return data.size();
+ }
+
+ public AddressBookContact removeItem(int position) {
+ AddressBookContact item = null;
+ try
+ {
+ item = data.get(position);
+ data.remove(position);
+ notifyItemRemoved(position);
+ }
+ catch(Exception e)
+ {
+ Timber.e(e);
+ }
+ return item;
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/decorator/RecyclerViewSwipeDecorator.java b/app/src/main/java/com/alphawallet/app/ui/widget/decorator/RecyclerViewSwipeDecorator.java
new file mode 100644
index 0000000000..f6b4c60151
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/ui/widget/decorator/RecyclerViewSwipeDecorator.java
@@ -0,0 +1,599 @@
+package com.alphawallet.app.ui.widget.decorator;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.Typeface;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.text.TextPaint;
+import android.util.Log;
+import android.util.TypedValue;
+
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.ItemTouchHelper;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * A simple utility class to add a background and/or an icon while swiping a RecyclerView item left or right.
+ */
+
+public class RecyclerViewSwipeDecorator {
+ private Canvas canvas;
+ private RecyclerView recyclerView;
+ private RecyclerView.ViewHolder viewHolder;
+ private float dX;
+ private float dY;
+ private int actionState;
+ private boolean isCurrentlyActive;
+
+ private int swipeLeftBackgroundColor;
+ private int swipeLeftActionIconId;
+ private Integer swipeLeftActionIconTint;
+
+ private int swipeRightBackgroundColor;
+ private int swipeRightActionIconId;
+ private Integer swipeRightActionIconTint;
+
+ private int iconHorizontalMargin;
+
+ private String mSwipeLeftText;
+ private float mSwipeLeftTextSize = 14;
+ private int mSwipeLeftTextUnit = TypedValue.COMPLEX_UNIT_SP;
+ private int mSwipeLeftTextColor = Color.DKGRAY;
+ private Typeface mSwipeLeftTypeface = Typeface.SANS_SERIF;
+
+ private String mSwipeRightText;
+ private float mSwipeRightTextSize = 14;
+ private int mSwipeRightTextUnit = TypedValue.COMPLEX_UNIT_SP;
+ private int mSwipeRightTextColor = Color.DKGRAY;
+ private Typeface mSwipeRightTypeface = Typeface.SANS_SERIF;
+
+ private RecyclerViewSwipeDecorator() {
+ swipeLeftBackgroundColor = 0;
+ swipeRightBackgroundColor = 0;
+ swipeLeftActionIconId = 0;
+ swipeRightActionIconId = 0;
+ swipeLeftActionIconTint = null;
+ swipeRightActionIconTint = null;
+ }
+
+ /**
+ * Create a @RecyclerViewSwipeDecorator
+ * @param context A valid Context object for the RecyclerView
+ * @param canvas The canvas which RecyclerView is drawing its children
+ * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to
+ * @param viewHolder The ViewHolder which is being interacted by the User or it was interacted and simply animating to its original position
+ * @param dX The amount of horizontal displacement caused by user's action
+ * @param dY The amount of vertical displacement caused by user's action
+ * @param actionState The type of interaction on the View. Is either ACTION_STATE_DRAG or ACTION_STATE_SWIPE.
+ * @param isCurrentlyActive True if this view is currently being controlled by the user or false it is simply animating back to its original state
+ *
+ * @deprecated in RecyclerViewSwipeDecorator 1.2.2
+ */
+ @Deprecated
+ public RecyclerViewSwipeDecorator(Context context, Canvas canvas, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
+ this(canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
+ }
+
+ /**
+ * Create a @RecyclerViewSwipeDecorator
+ * @param canvas The canvas which RecyclerView is drawing its children
+ * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to
+ * @param viewHolder The ViewHolder which is being interacted by the User or it was interacted and simply animating to its original position
+ * @param dX The amount of horizontal displacement caused by user's action
+ * @param dY The amount of vertical displacement caused by user's action
+ * @param actionState The type of interaction on the View. Is either ACTION_STATE_DRAG or ACTION_STATE_SWIPE.
+ * @param isCurrentlyActive True if this view is currently being controlled by the user or false it is simply animating back to its original state
+ */
+ public RecyclerViewSwipeDecorator(Canvas canvas, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
+ this();
+ this.canvas = canvas;
+ this.recyclerView = recyclerView;
+ this.viewHolder = viewHolder;
+ this.dX = dX;
+ this.dY = dY;
+ this.actionState = actionState;
+ this.isCurrentlyActive = isCurrentlyActive;
+ this.iconHorizontalMargin = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, recyclerView.getContext().getResources().getDisplayMetrics());
+ }
+
+ /**
+ * Set the background color for either (left/right) swipe directions
+ * @param backgroundColor The resource id of the background color to be set
+ */
+ public void setBackgroundColor(int backgroundColor) {
+ this.swipeLeftBackgroundColor = backgroundColor;
+ this.swipeRightBackgroundColor = backgroundColor;
+ }
+
+ /**
+ * Set the action icon for either (left/right) swipe directions
+ * @param actionIconId The resource id of the icon to be set
+ */
+ public void setActionIconId(int actionIconId) {
+ this.swipeLeftActionIconId = actionIconId;
+ this.swipeRightActionIconId = actionIconId;
+ }
+
+ /**
+ * Set the tint color for either (left/right) action icons
+ * @param color a color in ARGB format (e.g. 0xFF0000FF for blue)
+ */
+ public void setActionIconTint(int color) {
+ this.setSwipeLeftActionIconTint(color);
+ this.setSwipeRightActionIconTint(color);
+ }
+
+ /**
+ * Set the background color for left swipe direction
+ * @param swipeLeftBackgroundColor The resource id of the background color to be set
+ */
+ public void setSwipeLeftBackgroundColor(int swipeLeftBackgroundColor) {
+ this.swipeLeftBackgroundColor = swipeLeftBackgroundColor;
+ }
+
+ /**
+ * Set the action icon for left swipe direction
+ * @param swipeLeftActionIconId The resource id of the icon to be set
+ */
+ public void setSwipeLeftActionIconId(int swipeLeftActionIconId) {
+ this.swipeLeftActionIconId = swipeLeftActionIconId;
+ }
+
+ /**
+ * Set the tint color for action icon drawn while swiping left
+ * @param color a color in ARGB format (e.g. 0xFF0000FF for blue)
+ */
+ public void setSwipeLeftActionIconTint(int color) {
+ swipeLeftActionIconTint = color;
+ }
+
+ /**
+ * Set the background color for right swipe direction
+ * @param swipeRightBackgroundColor The resource id of the background color to be set
+ */
+ public void setSwipeRightBackgroundColor(int swipeRightBackgroundColor) {
+ this.swipeRightBackgroundColor = swipeRightBackgroundColor;
+ }
+
+ /**
+ * Set the action icon for right swipe direction
+ * @param swipeRightActionIconId The resource id of the icon to be set
+ */
+ public void setSwipeRightActionIconId(int swipeRightActionIconId) {
+ this.swipeRightActionIconId = swipeRightActionIconId;
+ }
+
+ /**
+ * Set the tint color for action icon drawn while swiping right
+ * @param color a color in ARGB format (e.g. 0xFF0000FF for blue)
+ */
+ public void setSwipeRightActionIconTint(int color) {
+ swipeRightActionIconTint = color;
+ }
+
+ /**
+ * Set the label shown when swiping right
+ * @param label a String
+ */
+ public void setSwipeRightLabel(String label) {
+ mSwipeRightText = label;
+ }
+
+ /**
+ * Set the size of the text shown when swiping right
+ * @param unit TypedValue (default is COMPLEX_UNIT_SP)
+ * @param size the size value
+ */
+ public void setSwipeRightTextSize(int unit, float size) {
+ mSwipeRightTextUnit = unit;
+ mSwipeRightTextSize = size;
+ }
+
+ /**
+ * Set the color of the text shown when swiping right
+ * @param color the color to be set
+ */
+ public void setSwipeRightTextColor(int color) {
+ mSwipeRightTextColor = color;
+ }
+
+ /**
+ * Set the Typeface of the text shown when swiping right
+ * @param typeface the Typeface to be set
+ */
+ public void setSwipeRightTypeface(Typeface typeface) {
+ mSwipeRightTypeface = typeface;
+ }
+
+ /**
+ * Set the horizontal margin of the icon in DPs (default is 16dp)
+ * @param iconHorizontalMargin the margin in pixels
+ *
+ * @deprecated in RecyclerViewSwipeDecorator 1.2, use {@link #setIconHorizontalMargin(int, int)} instead.
+ */
+ @Deprecated
+ public void setIconHorizontalMargin(int iconHorizontalMargin) {
+ setIconHorizontalMargin(TypedValue.COMPLEX_UNIT_DIP, iconHorizontalMargin);
+ }
+
+ /**
+ * Set the horizontal margin of the icon in the given unit (default is 16dp)
+ * @param unit TypedValue
+ * @param iconHorizontalMargin the margin in the given unit
+ */
+ public void setIconHorizontalMargin(int unit, int iconHorizontalMargin) {
+ this.iconHorizontalMargin = (int)TypedValue.applyDimension(unit, iconHorizontalMargin, recyclerView.getContext().getResources().getDisplayMetrics());
+ }
+
+ /**
+ * Set the label shown when swiping left
+ * @param label a String
+ */
+ public void setSwipeLeftLabel(String label) {
+ mSwipeLeftText = label;
+ }
+
+ /**
+ * Set the size of the text shown when swiping left
+ * @param unit TypedValue (default is COMPLEX_UNIT_SP)
+ * @param size the size value
+ */
+ public void setSwipeLeftTextSize(int unit, float size) {
+ mSwipeLeftTextUnit = unit;
+ mSwipeLeftTextSize = size;
+ }
+
+ /**
+ * Set the color of the text shown when swiping left
+ * @param color the color to be set
+ */
+ public void setSwipeLeftTextColor(int color) {
+ mSwipeLeftTextColor = color;
+ }
+
+ /**
+ * Set the Typeface of the text shown when swiping left
+ * @param typeface the Typeface to be set
+ */
+ public void setSwipeLeftTypeface(Typeface typeface) {
+ mSwipeLeftTypeface = typeface;
+ }
+
+ /**
+ * Decorate the RecyclerView item with the chosen backgrounds and icons
+ */
+ public void decorate() {
+ try {
+ if ( actionState != ItemTouchHelper.ACTION_STATE_SWIPE ) return;
+
+ if ( dX > 0 ) {
+ // Swiping Right
+ canvas.clipRect(viewHolder.itemView.getLeft(), viewHolder.itemView.getTop(), viewHolder.itemView.getLeft() + (int) dX, viewHolder.itemView.getBottom());
+ if ( swipeRightBackgroundColor != 0 ) {
+ final ColorDrawable background = new ColorDrawable(swipeRightBackgroundColor);
+ background.setBounds(viewHolder.itemView.getLeft(), viewHolder.itemView.getTop(), viewHolder.itemView.getLeft() + (int) dX, viewHolder.itemView.getBottom());
+ background.draw(canvas);
+ }
+
+ int iconSize = 0;
+ if ( swipeRightActionIconId != 0 && dX > iconHorizontalMargin ) {
+ Drawable icon = ContextCompat.getDrawable(recyclerView.getContext(), swipeRightActionIconId);
+ if ( icon != null ) {
+ iconSize = icon.getIntrinsicHeight();
+ int halfIcon = iconSize / 2;
+ int top = viewHolder.itemView.getTop() + ((viewHolder.itemView.getBottom() - viewHolder.itemView.getTop()) / 2 - halfIcon);
+ icon.setBounds(viewHolder.itemView.getLeft() + iconHorizontalMargin, top, viewHolder.itemView.getLeft() + iconHorizontalMargin + icon.getIntrinsicWidth(), top + icon.getIntrinsicHeight());
+ if (swipeRightActionIconTint != null)
+ icon.setColorFilter(swipeRightActionIconTint, PorterDuff.Mode.SRC_IN);
+ icon.draw(canvas);
+ }
+ }
+
+ if ( mSwipeRightText != null && mSwipeRightText.length() > 0 && dX > iconHorizontalMargin + iconSize) {
+ TextPaint textPaint = new TextPaint();
+ textPaint.setAntiAlias(true);
+ textPaint.setTextSize(TypedValue.applyDimension(mSwipeRightTextUnit, mSwipeRightTextSize, recyclerView.getContext().getResources().getDisplayMetrics()));
+ textPaint.setColor(mSwipeRightTextColor);
+ textPaint.setTypeface(mSwipeRightTypeface);
+
+ int textTop = (int) (viewHolder.itemView.getTop() + ((viewHolder.itemView.getBottom() - viewHolder.itemView.getTop()) / 2.0) + textPaint.getTextSize()/2);
+ canvas.drawText(mSwipeRightText, viewHolder.itemView.getLeft() + iconHorizontalMargin + iconSize + (iconSize > 0 ? iconHorizontalMargin/2 : 0), textTop,textPaint);
+ }
+
+ } else if ( dX < 0 ) {
+ // Swiping Left
+ canvas.clipRect(viewHolder.itemView.getRight() + (int) dX, viewHolder.itemView.getTop(), viewHolder.itemView.getRight(), viewHolder.itemView.getBottom());
+ if ( swipeLeftBackgroundColor != 0 ) {
+ final ColorDrawable background = new ColorDrawable(swipeLeftBackgroundColor);
+ background.setBounds(viewHolder.itemView.getRight() + (int) dX, viewHolder.itemView.getTop(), viewHolder.itemView.getRight(), viewHolder.itemView.getBottom());
+ background.draw(canvas);
+ }
+
+ int iconSize = 0;
+ int imgLeft = viewHolder.itemView.getRight();
+ if ( swipeLeftActionIconId != 0 && dX < - iconHorizontalMargin ) {
+ Drawable icon = ContextCompat.getDrawable(recyclerView.getContext(), swipeLeftActionIconId);
+ if ( icon != null ) {
+ iconSize = icon.getIntrinsicHeight();
+ int halfIcon = iconSize / 2;
+ int top = viewHolder.itemView.getTop() + ((viewHolder.itemView.getBottom() - viewHolder.itemView.getTop()) / 2 - halfIcon);
+ imgLeft = viewHolder.itemView.getRight() - iconHorizontalMargin - halfIcon * 2;
+ icon.setBounds(imgLeft, top, viewHolder.itemView.getRight() - iconHorizontalMargin, top + icon.getIntrinsicHeight());
+ if (swipeLeftActionIconTint != null)
+ icon.setColorFilter(swipeLeftActionIconTint, PorterDuff.Mode.SRC_IN);
+ icon.draw(canvas);
+ }
+ }
+
+ if ( mSwipeLeftText != null && mSwipeLeftText.length() > 0 && dX < - iconHorizontalMargin - iconSize ) {
+ TextPaint textPaint = new TextPaint();
+ textPaint.setAntiAlias(true);
+ textPaint.setTextSize(TypedValue.applyDimension(mSwipeLeftTextUnit, mSwipeLeftTextSize, recyclerView.getContext().getResources().getDisplayMetrics()));
+ textPaint.setColor(mSwipeLeftTextColor);
+ textPaint.setTypeface(mSwipeLeftTypeface);
+
+ float width = textPaint.measureText(mSwipeLeftText);
+ int textTop = (int) (viewHolder.itemView.getTop() + ((viewHolder.itemView.getBottom() - viewHolder.itemView.getTop()) / 2.0) + textPaint.getTextSize() / 2);
+ canvas.drawText(mSwipeLeftText, imgLeft - width - ( imgLeft == viewHolder.itemView.getRight() ? iconHorizontalMargin : iconHorizontalMargin/2 ), textTop, textPaint);
+ }
+ }
+ } catch(Exception e) {
+ Log.e(this.getClass().getName(), e.getMessage());
+ }
+ }
+
+ /**
+ * A Builder for the RecyclerViewSwipeDecorator class
+ */
+ public static class Builder {
+ private RecyclerViewSwipeDecorator mDecorator;
+
+ /**
+ * Create a builder for a RecyclerViewsSwipeDecorator
+ * @param context A valid Context object for the RecyclerView
+ * @param canvas The canvas which RecyclerView is drawing its children
+ * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to
+ * @param viewHolder The ViewHolder which is being interacted by the User or it was interacted and simply animating to its original position
+ * @param dX The amount of horizontal displacement caused by user's action
+ * @param dY The amount of vertical displacement caused by user's action
+ * @param actionState The type of interaction on the View. Is either ACTION_STATE_DRAG or ACTION_STATE_SWIPE.
+ * @param isCurrentlyActive True if this view is currently being controlled by the user or false it is simply animating back to its original state
+ *
+ * @deprecated in RecyclerViewSwipeDecorator 1.2.2
+ */
+ @Deprecated
+ public Builder(Context context, Canvas canvas, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
+ this(canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
+ }
+
+ /**
+ * Create a builder for a RecyclerViewsSwipeDecorator
+ * @param canvas The canvas which RecyclerView is drawing its children
+ * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to
+ * @param viewHolder The ViewHolder which is being interacted by the User or it was interacted and simply animating to its original position
+ * @param dX The amount of horizontal displacement caused by user's action
+ * @param dY The amount of vertical displacement caused by user's action
+ * @param actionState The type of interaction on the View. Is either ACTION_STATE_DRAG or ACTION_STATE_SWIPE.
+ * @param isCurrentlyActive True if this view is currently being controlled by the user or false it is simply animating back to its original state
+ */
+ public Builder(Canvas canvas, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
+ mDecorator = new RecyclerViewSwipeDecorator(
+ canvas,
+ recyclerView,
+ viewHolder,
+ dX,
+ dY,
+ actionState,
+ isCurrentlyActive
+ );
+ }
+
+ /**
+ * Add a background color to both swiping directions
+ * @param color A single color value in the form 0xAARRGGBB
+ * @return This instance of @RecyclerViewSwipeDecorator.Builder
+ */
+ public Builder addBackgroundColor(int color) {
+ mDecorator.setBackgroundColor(color);
+ return this;
+ }
+
+ /**
+ * Add an action icon to both swiping directions
+ * @param drawableId The resource id of the icon to be set
+ * @return This instance of @RecyclerViewSwipeDecorator.Builder
+ */
+ public Builder addActionIcon(int drawableId) {
+ mDecorator.setActionIconId(drawableId);
+ return this;
+ }
+
+ /**
+ * Set the tint color for either (left/right) action icons
+ * @param color a color in ARGB format (e.g. 0xFF0000FF for blue)
+ * @return This instance of @RecyclerViewSwipeDecorator.Builder
+ */
+ public Builder setActionIconTint(int color) {
+ mDecorator.setActionIconTint(color);
+ return this;
+ }
+
+ /**
+ * Add a background color while swiping right
+ * @param color A single color value in the form 0xAARRGGBB
+ * @return This instance of @RecyclerViewSwipeDecorator.Builder
+ */
+ public Builder addSwipeRightBackgroundColor(int color) {
+ mDecorator.setSwipeRightBackgroundColor(color);
+ return this;
+ }
+
+ /**
+ * Add an action icon while swiping right
+ * @param drawableId The resource id of the icon to be set
+ * @return This instance of @RecyclerViewSwipeDecorator.Builder
+ */
+ public Builder addSwipeRightActionIcon(int drawableId) {
+ mDecorator.setSwipeRightActionIconId(drawableId);
+ return this;
+ }
+
+ /**
+ * Set the tint color for action icon shown while swiping right
+ * @param color a color in ARGB format (e.g. 0xFF0000FF for blue)
+ * @return This instance of @RecyclerViewSwipeDecorator.Builder
+ */
+ public Builder setSwipeRightActionIconTint(int color) {
+ mDecorator.setSwipeRightActionIconTint(color);
+ return this;
+ }
+
+ /**
+ * Add a label to be shown while swiping right
+ * @param label The string to be shown as label
+ * @return This instance of @RecyclerViewSwipeDecorator.Builder
+ */
+ public Builder addSwipeRightLabel(String label) {
+ mDecorator.setSwipeRightLabel(label);
+ return this;
+ }
+
+ /**
+ * Set the color of the label to be shown while swiping right
+ * @param color the color to be set
+ * @return This instance of @RecyclerViewSwipeDecorator.Builder
+ */
+ public Builder setSwipeRightLabelColor(int color) {
+ mDecorator.setSwipeRightTextColor(color);
+ return this;
+ }
+
+ /**
+ * Set the size of the label to be shown while swiping right
+ * @param unit the unit to convert from
+ * @param size the size to be set
+ * @return This instance of @RecyclerViewSwipeDecorator.Builder
+ */
+ public Builder setSwipeRightLabelTextSize(int unit, float size) {
+ mDecorator.setSwipeRightTextSize(unit, size);
+ return this;
+ }
+
+ /**
+ * Set the Typeface of the label to be shown while swiping right
+ * @param typeface the Typeface to be set
+ * @return This instance of @RecyclerViewSwipeDecorator.Builder
+ */
+ public Builder setSwipeRightLabelTypeface(Typeface typeface) {
+ mDecorator.setSwipeRightTypeface(typeface);
+ return this;
+ }
+
+ /**
+ * Adds a background color while swiping left
+ * @param color A single color value in the form 0xAARRGGBB
+ * @return This instance of @RecyclerViewSwipeDecorator.Builder
+ */
+ public Builder addSwipeLeftBackgroundColor(int color) {
+ mDecorator.setSwipeLeftBackgroundColor(color);
+ return this;
+ }
+
+ /**
+ * Add an action icon while swiping left
+ * @param drawableId The resource id of the icon to be set
+ * @return This instance of @RecyclerViewSwipeDecorator.Builder
+ */
+ public Builder addSwipeLeftActionIcon(int drawableId) {
+ mDecorator.setSwipeLeftActionIconId(drawableId);
+ return this;
+ }
+
+ /**
+ * Set the tint color for action icon shown while swiping left
+ * @param color a color in ARGB format (e.g. 0xFF0000FF for blue)
+ * @return This instance of @RecyclerViewSwipeDecorator.Builder
+ */
+ public Builder setSwipeLeftActionIconTint(int color) {
+ mDecorator.setSwipeLeftActionIconTint(color);
+ return this;
+ }
+
+ /**
+ * Add a label to be shown while swiping left
+ * @param label The string to be shown as label
+ * @return This instance of @RecyclerViewSwipeDecorator.Builder
+ */
+ public Builder addSwipeLeftLabel(String label) {
+ mDecorator.setSwipeLeftLabel(label);
+ return this;
+ }
+
+ /**
+ * Set the color of the label to be shown while swiping left
+ * @param color the color to be set
+ * @return This instance of @RecyclerViewSwipeDecorator.Builder
+ */
+ public Builder setSwipeLeftLabelColor(int color) {
+ mDecorator.setSwipeLeftTextColor(color);
+ return this;
+ }
+
+ /**
+ * Set the size of the label to be shown while swiping left
+ * @param unit the unit to convert from
+ * @param size the size to be set
+ * @return This instance of @RecyclerViewSwipeDecorator.Builder
+ */
+ public Builder setSwipeLeftLabelTextSize(int unit, float size) {
+ mDecorator.setSwipeLeftTextSize(unit, size);
+ return this;
+ }
+
+ /**
+ * Set the Typeface of the label to be shown while swiping left
+ * @param typeface the Typeface to be set
+ * @return This instance of @RecyclerViewSwipeDecorator.Builder
+ */
+ public Builder setSwipeLeftLabelTypeface(Typeface typeface) {
+ mDecorator.setSwipeLeftTypeface(typeface);
+ return this;
+ }
+
+ /**
+ * Set the horizontal margin of the icon in DPs (default is 16dp)
+ * @param pixels margin in pixels
+ * @return This instance of @RecyclerViewSwipeDecorator.Builder
+ *
+ * @deprecated in RecyclerViewSwipeDecorator 1.2, use {@link #setIconHorizontalMargin(int, int)} instead.
+ */
+ @Deprecated
+ public Builder setIconHorizontalMargin(int pixels) {
+ mDecorator.setIconHorizontalMargin(pixels);
+ return this;
+ }
+
+ /**
+ * Set the horizontal margin of the icon in the given unit (default is 16dp)
+ * @param unit TypedValue
+ * @param iconHorizontalMargin the margin in the given unit
+ *
+ * @return This instance of @RecyclerViewSwipeDecorator.Builder
+ */
+ public Builder setIconHorizontalMargin(int unit, int iconHorizontalMargin) {
+ mDecorator.setIconHorizontalMargin(unit, iconHorizontalMargin);
+ return this;
+ }
+
+ /**
+ * Create a RecyclerViewSwipeDecorator
+ * @return The created @RecyclerViewSwipeDecorator
+ */
+ public RecyclerViewSwipeDecorator create() {
+ return mDecorator;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/holder/TransactionHolder.java b/app/src/main/java/com/alphawallet/app/ui/widget/holder/TransactionHolder.java
index 4ce31f70f7..1fa0bfcbef 100644
--- a/app/src/main/java/com/alphawallet/app/ui/widget/holder/TransactionHolder.java
+++ b/app/src/main/java/com/alphawallet/app/ui/widget/holder/TransactionHolder.java
@@ -16,6 +16,7 @@
import com.alphawallet.app.entity.Transaction;
import com.alphawallet.app.entity.TransactionMeta;
import com.alphawallet.app.entity.tokens.Token;
+import com.alphawallet.app.interact.AddressBookInteract;
import com.alphawallet.app.interact.FetchTransactionsInteract;
import com.alphawallet.app.service.AssetDefinitionService;
import com.alphawallet.app.service.TokensService;
@@ -29,6 +30,8 @@
import static com.alphawallet.ethereum.EthereumNetworkBase.MAINNET_ID;
+import timber.log.Timber;
+
public class TransactionHolder extends BinderViewHolder implements View.OnClickListener
{
public static final int VIEW_TYPE = 1003;
@@ -47,12 +50,13 @@ public class TransactionHolder extends BinderViewHolder impleme
private final LinearLayout transactionBackground;
private final FetchTransactionsInteract transactionsInteract;
private final AssetDefinitionService assetService;
+ private final AddressBookInteract addressBookInteract;
private Transaction transaction;
private String defaultAddress;
private boolean fromTokenView;
- public TransactionHolder(ViewGroup parent, TokensService service, FetchTransactionsInteract interact, AssetDefinitionService svs)
+ public TransactionHolder(ViewGroup parent, TokensService service, FetchTransactionsInteract interact, AssetDefinitionService svs, AddressBookInteract addressBookInteract)
{
super(R.layout.item_transaction, parent);
date = findViewById(R.id.text_tx_time);
@@ -66,6 +70,7 @@ public TransactionHolder(ViewGroup parent, TokensService service, FetchTransacti
transactionsInteract = interact;
assetService = svs;
itemView.setOnClickListener(this);
+ this.addressBookInteract = addressBookInteract;
}
@Override
@@ -117,6 +122,14 @@ private void setupTransactionDetail(Token token)
{
String detailStr = token.getTransactionDetail(getContext(), transaction, tokensService);
address.setText(detailStr);
+ Timber.d(detailStr);
+ String walletAddress = detailStr.split(" ")[1];
+ // set name from address book
+ addressBookInteract.searchContactAsync(walletAddress, (contact) -> {
+ if (contact != null) {
+ address.setText(contact.getName()); // wont work as address is formatted with ... in middle
+ }
+ });
}
@Override
diff --git a/app/src/main/java/com/alphawallet/app/util/ItemCallback.java b/app/src/main/java/com/alphawallet/app/util/ItemCallback.java
new file mode 100644
index 0000000000..736db766ca
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/util/ItemCallback.java
@@ -0,0 +1,6 @@
+package com.alphawallet.app.util;
+
+/** Simple callback functional interface for any object*/
+public interface ItemCallback {
+ public void call(T object);
+}
diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/ActivityViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/ActivityViewModel.java
index 677ae56937..464ae25f68 100644
--- a/app/src/main/java/com/alphawallet/app/viewmodel/ActivityViewModel.java
+++ b/app/src/main/java/com/alphawallet/app/viewmodel/ActivityViewModel.java
@@ -7,6 +7,7 @@
import com.alphawallet.app.entity.ActivityMeta;
import com.alphawallet.app.entity.Transaction;
import com.alphawallet.app.entity.Wallet;
+import com.alphawallet.app.interact.AddressBookInteract;
import com.alphawallet.app.interact.FetchTransactionsInteract;
import com.alphawallet.app.interact.GenericWalletInteract;
import com.alphawallet.app.service.AssetDefinitionService;
@@ -42,6 +43,7 @@ public class ActivityViewModel extends BaseViewModel
private final TokensService tokensService;
private final TransactionsService transactionsService;
private final RealmManager realmManager;
+ private final AddressBookInteract addressBookInteract;
@Nullable
private Disposable queryUnknownTokensDisposable;
@@ -58,13 +60,15 @@ public LiveData defaultWallet() {
AssetDefinitionService assetDefinitionService,
TokensService tokensService,
TransactionsService transactionsService,
- RealmManager realmManager) {
+ RealmManager realmManager,
+ AddressBookInteract addressBookInteract) {
this.genericWalletInteract = genericWalletInteract;
this.fetchTransactionsInteract = fetchTransactionsInteract;
this.assetDefinitionService = assetDefinitionService;
this.tokensService = tokensService;
this.transactionsService = transactionsService;
this.realmManager = realmManager;
+ this.addressBookInteract = addressBookInteract;
}
public void prepare()
@@ -137,4 +141,9 @@ public AssetDefinitionService getAssetDefinitionService()
{
return assetDefinitionService;
}
+
+ public AddressBookInteract getAddressBookInteract()
+ {
+ return addressBookInteract;
+ }
}
diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/AddEditAddressViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/AddEditAddressViewModel.java
new file mode 100644
index 0000000000..e2d7de3ceb
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/viewmodel/AddEditAddressViewModel.java
@@ -0,0 +1,147 @@
+package com.alphawallet.app.viewmodel;
+
+import androidx.lifecycle.MutableLiveData;
+
+import com.alphawallet.app.entity.AddressBookContact;
+import com.alphawallet.app.interact.AddressBookInteract;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import dagger.hilt.android.lifecycle.HiltViewModel;
+import io.reactivex.Single;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.schedulers.Schedulers;
+import java8.util.Optional;
+import timber.log.Timber;
+
+@HiltViewModel
+public class AddEditAddressViewModel extends BaseViewModel {
+ private static final String TAG = "AddAddressViewModel";
+
+ private final AddressBookInteract addressBookInteract;
+
+ public MutableLiveData> address = new MutableLiveData<>();
+ public MutableLiveData addressError = new MutableLiveData<>();
+ public MutableLiveData> contactName = new MutableLiveData<>();
+ public MutableLiveData contactNameError = new MutableLiveData<>();
+ public MutableLiveData saveContact = new MutableLiveData<>();
+ public MutableLiveData updateContact = new MutableLiveData<>();
+ public MutableLiveData error = new MutableLiveData<>();
+
+ private final List contactsList = new ArrayList<>();
+ private AddressBookContact addressBookContactToSave = new AddressBookContact("", "", "");
+
+ @Inject
+ public AddEditAddressViewModel(AddressBookInteract addressBookInteract) {
+ this.addressBookInteract = addressBookInteract;
+ loadContacts();
+ }
+
+ // load contacts in list
+ public void loadContacts() {
+ addressBookInteract.getAllContacts()
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe( (list) -> {
+ Timber.d("loadContacts: list: %s", list);
+ contactsList.clear();
+ contactsList.addAll(list);
+ }, (throwable -> Timber.e(throwable, "loadContacts: error: ")))
+ .isDisposed();
+ }
+
+ public void onClickSave(String walletAddress, String name, String ensName) {
+ addressBookContactToSave.setWalletAddress(walletAddress);
+ addressBookContactToSave.setEthName(ensName);
+ addressBookContactToSave.setName(name);
+ checkAddress(walletAddress);
+ }
+
+ public void updateContact(AddressBookContact oldContact, AddressBookContact newContact) {
+ if (oldContact.getName().equalsIgnoreCase(newContact.getName()))
+ return;
+ // search by address
+ // if found, update the name
+ // else something went wrong.
+ Timber.d("New name: %s", newContact.getName());
+ Optional matchedContactOpt = findByAddress(oldContact.getWalletAddress());
+ if (matchedContactOpt.isPresent()) {
+ Optional matchedByName = findByName(newContact.getName());
+ if (matchedByName.isPresent()) {
+ contactName.postValue(matchedByName);
+ return;
+ }
+ addressBookInteract.updateContact(oldContact, newContact)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribeOn(Schedulers.io())
+ .subscribe( () -> updateContact.postValue(true), (throwable -> error.postValue(new RuntimeException("Failed to update contact"))))
+ .isDisposed();
+ } else {
+ // error
+ error.postValue(new RuntimeException("No match found"));
+ updateContact.postValue(false);
+ }
+ }
+
+ /**
+ * @param walletAddress wallet address to match with existing contacts.
+ * Finds address and posts result to addressError Live Data
+ */
+ public void checkAddress(String walletAddress) {
+ addressBookContactToSave.setWalletAddress(walletAddress); // required when called from editorAction done
+ Single.fromCallable( () -> findByAddress(walletAddress)).subscribeOn(Schedulers.computation())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(address::postValue, (throwable -> Timber.e(throwable, "checkAddress: ")))
+ .isDisposed();
+ }
+
+ public void checkName(String name) {
+ addressBookContactToSave.setName(name); // required when called from editorAction done
+ Single.fromCallable( () -> findByName(name)).subscribeOn(Schedulers.computation())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe( (contactName::postValue) , (throwable -> Timber.e(throwable, "checkName: ")))
+ .isDisposed();
+ }
+
+ private Optional findByAddress(String walletAddress) {
+ AddressBookContact matchedContact = null;
+ for (AddressBookContact addressBookContact : contactsList) {
+ if (addressBookContact.getWalletAddress().equalsIgnoreCase(walletAddress)) {
+ matchedContact = addressBookContact;
+ break;
+ }
+ }
+ if (matchedContact == null) {
+ return Optional.empty();
+ } else {
+ return Optional.of(matchedContact);
+ }
+ }
+
+ private Optional findByName(String name) {
+ AddressBookContact matchedContact = null;
+ for (AddressBookContact addressBookContact : contactsList) {
+ if (addressBookContact.getName().equalsIgnoreCase(name)) {
+ matchedContact = addressBookContact;
+ break;
+ }
+ }
+ if (matchedContact == null) {
+ return Optional.empty();
+ } else {
+ return Optional.of(matchedContact);
+ }
+ }
+
+ public void addContact() {
+
+ addressBookInteract.addContact(addressBookContactToSave)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe( () -> saveContact.postValue(true), (throwable -> saveContact.postValue(false)) )
+ .isDisposed();
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/AddressBookViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/AddressBookViewModel.java
new file mode 100644
index 0000000000..4196842043
--- /dev/null
+++ b/app/src/main/java/com/alphawallet/app/viewmodel/AddressBookViewModel.java
@@ -0,0 +1,100 @@
+package com.alphawallet.app.viewmodel;
+
+import androidx.lifecycle.MutableLiveData;
+
+import com.alphawallet.app.entity.NetworkInfo;
+import com.alphawallet.app.entity.AddressBookContact;
+import com.alphawallet.app.interact.AddressBookInteract;
+import com.alphawallet.app.interact.GenericWalletInteract;
+import com.alphawallet.app.service.RealmManager;
+import com.alphawallet.app.service.TokensService;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import dagger.hilt.android.lifecycle.HiltViewModel;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.schedulers.Schedulers;
+import timber.log.Timber;
+
+@HiltViewModel
+public class AddressBookViewModel extends BaseViewModel {
+ private static final String TAG = "AddressBookViewModel";
+
+ private final RealmManager realmManager;
+ private final TokensService tokensService;
+ private final AddressBookInteract addressBookInteract;
+ private final GenericWalletInteract genericWalletInteract;
+
+ public MutableLiveData> contactsLD = new MutableLiveData<>();
+ public MutableLiveData> filteredContacts = new MutableLiveData<>();
+ private final MutableLiveData defaultNetwork = new MutableLiveData<>();
+
+ private List contactsList;
+
+ @Inject
+ AddressBookViewModel(TokensService tokensService,
+ RealmManager realmManager,
+ AddressBookInteract addressBookInteract,
+ GenericWalletInteract genericWalletInteract
+ ) {
+ this.tokensService = tokensService;
+ this.realmManager = realmManager;
+ this.addressBookInteract = addressBookInteract;
+ this.genericWalletInteract = genericWalletInteract;
+ loadContacts();
+ }
+
+ public void loadContacts() {
+ addressBookInteract.getAllContacts()
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(this::postContacts, this::onError)
+ .isDisposed();
+ }
+
+ public void addContact(AddressBookContact addressBookContact) {
+ addressBookInteract.addContact(addressBookContact)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(this::onComplete, this::onError)
+ .isDisposed();
+ }
+
+ public void removeContact(AddressBookContact addressBookContact) {
+ addressBookInteract.removeContact(addressBookContact)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(this::onComplete, this::onError)
+ .isDisposed();
+ }
+
+ private void onComplete() {
+ loadContacts();
+ }
+
+ private void postContacts(List contacts) {
+ this.contactsList = contacts;
+ contactsLD.postValue(contacts);
+ }
+
+ @Override
+ protected void onError(Throwable throwable) {
+ super.onError(throwable);
+ Timber.d("onError: %s", throwable.getMessage());
+ }
+
+ public void filterByName(String nameText) {
+ String nameToSearch = nameText.toLowerCase();
+ ArrayList filtered = new ArrayList<>();
+ for (AddressBookContact u : contactsList) {
+ if (u.getName().toLowerCase().startsWith(nameToSearch)) {
+ filtered.add(u);
+ }
+ }
+ // post
+ filteredContacts.postValue(filtered);
+ }
+}
diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/Erc20DetailViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/Erc20DetailViewModel.java
index 0a38dcd149..4300784d14 100644
--- a/app/src/main/java/com/alphawallet/app/viewmodel/Erc20DetailViewModel.java
+++ b/app/src/main/java/com/alphawallet/app/viewmodel/Erc20DetailViewModel.java
@@ -13,6 +13,7 @@
import com.alphawallet.app.entity.OnRampContract;
import com.alphawallet.app.entity.Wallet;
import com.alphawallet.app.entity.tokens.Token;
+import com.alphawallet.app.interact.AddressBookInteract;
import com.alphawallet.app.interact.FetchTransactionsInteract;
import com.alphawallet.app.repository.OnRampRepository;
import com.alphawallet.app.repository.OnRampRepositoryType;
@@ -41,19 +42,22 @@ public class Erc20DetailViewModel extends BaseViewModel {
private final AssetDefinitionService assetDefinitionService;
private final TokensService tokensService;
private final OnRampRepositoryType onRampRepository;
+ private final AddressBookInteract addressBookInteract;
@Inject
public Erc20DetailViewModel(MyAddressRouter myAddressRouter,
FetchTransactionsInteract fetchTransactionsInteract,
AssetDefinitionService assetDefinitionService,
TokensService tokensService,
- OnRampRepositoryType onRampRepository)
+ OnRampRepositoryType onRampRepository,
+ AddressBookInteract addressBookInteract)
{
this.myAddressRouter = myAddressRouter;
this.fetchTransactionsInteract = fetchTransactionsInteract;
this.assetDefinitionService = assetDefinitionService;
this.tokensService = tokensService;
this.onRampRepository = onRampRepository;
+ this.addressBookInteract = addressBookInteract;
}
public LiveData sig()
@@ -91,6 +95,10 @@ public AssetDefinitionService getAssetDefinitionService()
return this.assetDefinitionService;
}
+ public AddressBookInteract getAddressBookInteract() {
+ return addressBookInteract;
+ }
+
public void showSendToken(Activity act, Wallet wallet, Token token)
{
if (token != null)
diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/TokenActivityViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/TokenActivityViewModel.java
index 52f3087f1d..9d6ae202eb 100644
--- a/app/src/main/java/com/alphawallet/app/viewmodel/TokenActivityViewModel.java
+++ b/app/src/main/java/com/alphawallet/app/viewmodel/TokenActivityViewModel.java
@@ -4,6 +4,7 @@
import com.alphawallet.app.entity.ActivityMeta;
import com.alphawallet.app.entity.Wallet;
+import com.alphawallet.app.interact.AddressBookInteract;
import com.alphawallet.app.interact.FetchTransactionsInteract;
import com.alphawallet.app.service.AssetDefinitionService;
import com.alphawallet.app.service.TokensService;
@@ -20,15 +21,18 @@ public class TokenActivityViewModel extends BaseViewModel {
private final AssetDefinitionService assetDefinitionService;
private final TokensService tokensService;
private final FetchTransactionsInteract fetchTransactionsInteract;
+ private final AddressBookInteract addressBookInteract;
@Inject
public TokenActivityViewModel(AssetDefinitionService assetDefinitionService,
FetchTransactionsInteract fetchTransactionsInteract,
- TokensService tokensService)
+ TokensService tokensService,
+ AddressBookInteract addressBookInteract)
{
this.assetDefinitionService = assetDefinitionService;
this.fetchTransactionsInteract = fetchTransactionsInteract;
this.tokensService = tokensService;
+ this.addressBookInteract = addressBookInteract;
}
public TokensService getTokensService()
@@ -46,6 +50,10 @@ public AssetDefinitionService getAssetDefinitionService()
return this.assetDefinitionService;
}
+ public AddressBookInteract getAddressBookInteract() {
+ return this.addressBookInteract;
+ }
+
public Realm getRealmInstance(Wallet wallet)
{
return tokensService.getRealmInstance(wallet);
diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/TokenFunctionViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/TokenFunctionViewModel.java
index 1cda35d059..2856c63ce1 100644
--- a/app/src/main/java/com/alphawallet/app/viewmodel/TokenFunctionViewModel.java
+++ b/app/src/main/java/com/alphawallet/app/viewmodel/TokenFunctionViewModel.java
@@ -21,6 +21,7 @@
import com.alphawallet.app.entity.WalletType;
import com.alphawallet.app.entity.nftassets.NFTAsset;
import com.alphawallet.app.entity.tokens.Token;
+import com.alphawallet.app.interact.AddressBookInteract;
import com.alphawallet.app.interact.CreateTransactionInteract;
import com.alphawallet.app.interact.FetchTransactionsInteract;
import com.alphawallet.app.interact.GenericWalletInteract;
@@ -95,6 +96,7 @@ public class TokenFunctionViewModel extends BaseViewModel
private final GenericWalletInteract genericWalletInteract;
private final OpenSeaService openseaService;
private final FetchTransactionsInteract fetchTransactionsInteract;
+ private final AddressBookInteract addressBookInteract;
private final AnalyticsServiceType analyticsService;
private Wallet wallet;
@@ -121,6 +123,7 @@ public class TokenFunctionViewModel extends BaseViewModel
GenericWalletInteract genericWalletInteract,
OpenSeaService openseaService,
FetchTransactionsInteract fetchTransactionsInteract,
+ AddressBookInteract addressBookInteract,
AnalyticsServiceType analyticsService)
{
this.assetDefinitionService = assetDefinitionService;
@@ -132,6 +135,7 @@ public class TokenFunctionViewModel extends BaseViewModel
this.genericWalletInteract = genericWalletInteract;
this.openseaService = openseaService;
this.fetchTransactionsInteract = fetchTransactionsInteract;
+ this.addressBookInteract = addressBookInteract;
this.analyticsService = analyticsService;
}
@@ -139,6 +143,10 @@ public AssetDefinitionService getAssetDefinitionService()
{
return assetDefinitionService;
}
+ public AddressBookInteract getAddressBookInteract() {
+ return addressBookInteract;
+ }
+
public LiveData insufficientFunds() { return insufficientFunds; }
public LiveData invalidAddress() { return invalidAddress; }
public LiveData sig() { return sig; }
diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/TransactionDetailViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/TransactionDetailViewModel.java
index 555e824fda..801486d616 100644
--- a/app/src/main/java/com/alphawallet/app/viewmodel/TransactionDetailViewModel.java
+++ b/app/src/main/java/com/alphawallet/app/viewmodel/TransactionDetailViewModel.java
@@ -8,6 +8,7 @@
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.Observer;
import com.alphawallet.app.C;
import com.alphawallet.app.R;
@@ -17,9 +18,11 @@
import com.alphawallet.app.entity.SignAuthenticationCallback;
import com.alphawallet.app.entity.Transaction;
import com.alphawallet.app.entity.TransactionData;
+import com.alphawallet.app.entity.AddressBookContact;
import com.alphawallet.app.entity.Wallet;
import com.alphawallet.app.entity.tokens.Token;
import com.alphawallet.app.entity.tokenscript.EventUtils;
+import com.alphawallet.app.interact.AddressBookInteract;
import com.alphawallet.app.interact.CreateTransactionInteract;
import com.alphawallet.app.interact.FetchTransactionsInteract;
import com.alphawallet.app.interact.FindDefaultNetworkInteract;
@@ -41,6 +44,7 @@
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
+import java.util.List;
import java.util.concurrent.TimeUnit;
import dagger.hilt.android.lifecycle.HiltViewModel;
@@ -49,6 +53,7 @@
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import io.realm.Realm;
+import timber.log.Timber;
import static com.alphawallet.app.repository.TokenRepository.getWeb3jService;
@@ -62,6 +67,7 @@ public class TransactionDetailViewModel extends BaseViewModel {
private final TokenRepositoryType tokenRepository;
private final FetchTransactionsInteract fetchTransactionsInteract;
private final CreateTransactionInteract createTransactionInteract;
+ private final AddressBookInteract addressBookInteract;
private final KeyService keyService;
private final TokensService tokenService;
@@ -73,13 +79,20 @@ public class TransactionDetailViewModel extends BaseViewModel {
private final MutableLiveData transaction = new MutableLiveData<>();
private final MutableLiveData transactionFinalised = new MutableLiveData<>();
private final MutableLiveData transactionError = new MutableLiveData<>();
+ private final MutableLiveData senderAddressContact = new MutableLiveData<>();
+ private final MutableLiveData receiverAddressContact = new MutableLiveData<>();
+ private final MutableLiveData> addressBook = new MutableLiveData<>();
public LiveData latestBlock() {
return latestBlock;
}
public LiveData latestTx() { return latestTx; }
public LiveData onTransaction() { return transaction; }
+ public LiveData senderAddressContact() { return senderAddressContact; }
+ public LiveData receiverAddressContact() { return receiverAddressContact; }
private String walletAddress;
+ private Observer> senderAddressContactObserver;
+ private Observer> receiverAddressContactObserver;
@Nullable
private Disposable pendingUpdateDisposable;
@@ -97,6 +110,7 @@ public LiveData latestBlock() {
KeyService keyService,
GasService gasService,
CreateTransactionInteract createTransactionInteract,
+ AddressBookInteract addressBookInteract,
AnalyticsServiceType analyticsService)
{
this.networkInteract = findDefaultNetworkInteract;
@@ -107,7 +121,10 @@ public LiveData latestBlock() {
this.keyService = keyService;
this.gasService = gasService;
this.createTransactionInteract = createTransactionInteract;
+ this.addressBookInteract = addressBookInteract;
this.analyticsService = analyticsService;
+
+ loadAddressBook();
}
public MutableLiveData transactionFinalised()
@@ -319,4 +336,58 @@ public void actionSheetConfirm(String mode)
analyticsService.track(C.AN_CALL_ACTIONSHEET, analyticsProperties);
}
+
+ public void loadAddressBook() {
+ Timber.d("loadAddressBook: ");
+ addressBookInteract.getAllContacts()
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(this::onLoadAddressBook, (throwable -> Timber.e(throwable, "Failed to load address book")))
+ .isDisposed();
+ }
+
+ private void onLoadAddressBook(List addressBook) {
+ Timber.d("onLoadAddressBook: ");
+ this.addressBook.postValue(addressBook);
+ }
+
+ public void resolveSenderAddress(String walletAddress) {
+ // observer to react on loading address book
+ senderAddressContactObserver = new Observer>() {
+ @Override
+ public void onChanged(List addressBookContacts) {
+ for (AddressBookContact addressBookContact : addressBookContacts) {
+ if (addressBookContact.getWalletAddress().equalsIgnoreCase(walletAddress)) {
+ senderAddressContact.postValue(addressBookContact);
+ break;
+ }
+ }
+ }
+ };
+ this.addressBook.observeForever(senderAddressContactObserver);
+
+ }
+
+ public void resolveReceiverAddress(String walletAddress) {
+ // observer to react on loading address book
+ receiverAddressContactObserver = new Observer>() {
+ @Override
+ public void onChanged(List addressBookContacts) {
+ for (AddressBookContact addressBookContact : addressBookContacts) {
+ if (addressBookContact.getWalletAddress().equalsIgnoreCase(walletAddress)) {
+ receiverAddressContact.postValue(addressBookContact);
+ break;
+ }
+ }
+ }
+ };
+ this.addressBook.observeForever(receiverAddressContactObserver);
+ }
+
+ @Override
+ protected void onCleared() {
+ addressBook.removeObserver(senderAddressContactObserver);
+ addressBook.removeObserver(receiverAddressContactObserver);
+ super.onCleared();
+ }
}
diff --git a/app/src/main/java/com/alphawallet/app/widget/CopyTextView.java b/app/src/main/java/com/alphawallet/app/widget/CopyTextView.java
index 3697e7cabb..713c8d2dbf 100644
--- a/app/src/main/java/com/alphawallet/app/widget/CopyTextView.java
+++ b/app/src/main/java/com/alphawallet/app/widget/CopyTextView.java
@@ -12,7 +12,9 @@
import android.widget.TextView;
import android.widget.Toast;
+import com.alphawallet.app.C;
import com.alphawallet.app.R;
+import com.alphawallet.app.router.AddEditAddressRouter;
import org.web3j.crypto.WalletUtils;
@@ -24,6 +26,7 @@ public class CopyTextView extends LinearLayout {
private ImageView copy;
private TextView text;
private LinearLayout layout;
+ private ImageView addressBook;
private int textResId;
private int textColor;
@@ -33,6 +36,7 @@ public class CopyTextView extends LinearLayout {
private boolean removePadding;
private String rawAddress;
private float marginRight;
+ private boolean showAddressBook = false;
public CopyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -60,6 +64,7 @@ private void getAttrs(Context context, AttributeSet attrs) {
boldFont = a.getBoolean(R.styleable.CopyTextView_bold, false);
removePadding = a.getBoolean(R.styleable.CopyTextView_removePadding, false);
marginRight = a.getDimension(R.styleable.CopyTextView_marginRight, 0.0f);
+ showAddressBook = a.getBoolean(R.styleable.CopyTextView_showAddressBook, false);
} finally {
a.recycle();
}
@@ -72,6 +77,7 @@ private void bindViews() {
text.setText(textResId);
text.setTextColor(textColor);
text.setGravity(gravity);
+ addressBook = findViewById(R.id.img_address_book);
LayoutParams layoutParams = (LayoutParams) text.getLayoutParams();
layoutParams.rightMargin = (int) marginRight;
@@ -89,6 +95,18 @@ private void bindViews() {
layout.setOnClickListener(v -> copyToClipboard());
copy.setOnClickListener(v -> copyToClipboard());
+
+ if (showAddressBook) {
+ copy.setVisibility(GONE);
+ addressBook.setVisibility(VISIBLE);
+ addressBook.setOnClickListener( v -> {
+ new AddEditAddressRouter().open(
+ getContext(),
+ C.ADD_ADDRESS_REQUEST_CODE,
+ text.getText().toString()
+ );
+ });
+ }
}
public String getText() {
@@ -116,4 +134,10 @@ private void copyToClipboard()
if(showToast) Toast.makeText(context, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show();
}
+
+ public void setAddressName(String name) {
+ text.setText(name);
+ addressBook.setVisibility(GONE);
+ copy.setVisibility(VISIBLE);
+ }
}
diff --git a/app/src/main/java/com/alphawallet/app/widget/InputAddress.java b/app/src/main/java/com/alphawallet/app/widget/InputAddress.java
index 8d258297eb..19b07ad67c 100644
--- a/app/src/main/java/com/alphawallet/app/widget/InputAddress.java
+++ b/app/src/main/java/com/alphawallet/app/widget/InputAddress.java
@@ -14,20 +14,20 @@
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.AutoCompleteTextView;
import android.widget.ImageButton;
-import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.alphawallet.app.C;
import com.alphawallet.app.R;
import com.alphawallet.app.entity.ENSCallback;
+import com.alphawallet.app.entity.AddressBookContact;
import com.alphawallet.app.entity.Wallet;
+import com.alphawallet.app.router.AddressBookRouter;
import com.alphawallet.app.ui.QRScanning.QRScanner;
import com.alphawallet.app.ui.widget.adapter.AutoCompleteAddressAdapter;
import com.alphawallet.app.ui.widget.entity.AddressReadyCallback;
@@ -47,6 +47,7 @@
*/
public class InputAddress extends RelativeLayout implements ItemClickListener, ENSCallback, TextWatcher
{
+ private final View layout;
private final AutoCompleteTextView editText;
private final TextView labelText;
private final TextView pasteItem;
@@ -69,11 +70,13 @@ public class InputAddress extends RelativeLayout implements ItemClickListener, E
private long chainOverride;
private final Pattern findAddress = Pattern.compile("^(\\s?)+(0x)([0-9a-fA-F]{40})(\\s?)+\\z");
private final float standardTextSize;
+ private boolean showAddressBook = false;
+ private TextView addressBook;
public InputAddress(Context context, AttributeSet attrs)
{
super(context, attrs);
- inflate(context, R.layout.item_input_address, this);
+ layout = inflate(context, R.layout.item_input_address, this);
getAttrs(context, attrs);
this.context = context;
@@ -140,6 +143,12 @@ private void getAttrs(Context context, AttributeSet attrs)
findViewById(R.id.layout_header).setVisibility(showHeader ? View.VISIBLE : View.GONE);
TextView headerText = findViewById(R.id.text_header);
headerText.setText(headerTextId);
+ showAddressBook = a.getBoolean(R.styleable.InputView_showAddressBook, false);
+ addressBook = findViewById(R.id.text_address_book);
+ addressBook.setVisibility(showAddressBook ? View.VISIBLE : View.GONE);
+ addressBook.setOnClickListener(v -> {
+ new AddressBookRouter().openForContactSelection(getContext(), C.ADDRESS_BOOK_CONTACT_REQUEST_CODE);
+ });
}
finally
{
@@ -518,7 +527,32 @@ public void afterTextChanged(Editable s)
setStatus(null);
if (ensHandler != null && !TextUtils.isEmpty(getInputText()))
{
+ Timber.d("ensHandler.checkAddress: ");
ensHandler.checkAddress();
}
}
+
+ public void onContactSelected(AddressBookContact addressBookContact) {
+ if (!addressBookContact.getWalletAddress().isEmpty()) {
+ fullAddress = addressBookContact.getWalletAddress();
+ if (!addressBookContact.getEthName().isEmpty()) {
+ setAddress(addressBookContact.getEthName() + " | " + addressBookContact.getWalletAddress());
+ } else {
+ setAddress(addressBookContact.getWalletAddress());
+ }
+ }
+ }
+
+ /** Use to enable/disable ens resolver*/
+ public void setHandleENS(boolean handleENS) {
+ this.handleENS = handleENS;
+ }
+
+ public void setEditable(boolean enabled) {
+ layout.setClickable(enabled);
+ editText.setEnabled(enabled);
+ scanQrIcon.setVisibility(enabled ? VISIBLE : GONE);
+ pasteItem.setVisibility(enabled ? VISIBLE : GONE);
+ addressBook.setVisibility( (enabled && showAddressBook) ? VISIBLE : GONE);
+ }
}
diff --git a/app/src/main/java/com/alphawallet/app/widget/InputView.java b/app/src/main/java/com/alphawallet/app/widget/InputView.java
index e97901fa73..1bcfea5acd 100644
--- a/app/src/main/java/com/alphawallet/app/widget/InputView.java
+++ b/app/src/main/java/com/alphawallet/app/widget/InputView.java
@@ -184,6 +184,7 @@ public void setError(int resId) {
errorText.setText(resId);
errorText.setVisibility(View.VISIBLE);
}
+ setBoxColour(BoxStatus.ERROR);
}
public void setError(CharSequence message) {
@@ -196,6 +197,7 @@ public void setError(CharSequence message) {
errorText.setText(message);
errorText.setVisibility(View.VISIBLE);
}
+ setBoxColour(BoxStatus.ERROR);
}
public void setStatus(CharSequence statusTxt)
@@ -236,4 +238,17 @@ private void setBoxColour(BoxStatus status)
break;
}
}
+
+ /**
+ * Enables highlighting input box on selection & error.
+ */
+ public void enableFocusListener() {
+ editText.setOnFocusChangeListener( (v, hasFocus) -> {
+ if (hasFocus) {
+ setBoxColour(BoxStatus.SELECTED);
+ } else {
+ setBoxColour(BoxStatus.UNSELECTED);
+ }
+ });
+ }
}
diff --git a/app/src/main/res/drawable-anydpi/ic_address_book.xml b/app/src/main/res/drawable-anydpi/ic_address_book.xml
new file mode 100644
index 0000000000..826e843576
--- /dev/null
+++ b/app/src/main/res/drawable-anydpi/ic_address_book.xml
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/app/src/main/res/drawable-anydpi/illustration_empty_2.xml b/app/src/main/res/drawable-anydpi/illustration_empty_2.xml
new file mode 100644
index 0000000000..c4d7145df6
--- /dev/null
+++ b/app/src/main/res/drawable-anydpi/illustration_empty_2.xml
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_add_to_address_book.xml b/app/src/main/res/drawable/ic_add_to_address_book.xml
new file mode 100644
index 0000000000..bd610cea55
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add_to_address_book.xml
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_delete_contact.xml b/app/src/main/res/drawable/ic_delete_contact.xml
new file mode 100644
index 0000000000..b10dee49bd
--- /dev/null
+++ b/app/src/main/res/drawable/ic_delete_contact.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_add_address.xml b/app/src/main/res/layout/activity_add_address.xml
new file mode 100644
index 0000000000..0f65c87c75
--- /dev/null
+++ b/app/src/main/res/layout/activity_add_address.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_address_book.xml b/app/src/main/res/layout/activity_address_book.xml
new file mode 100644
index 0000000000..d146570de8
--- /dev/null
+++ b/app/src/main/res/layout/activity_address_book.xml
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_send.xml b/app/src/main/res/layout/activity_send.xml
index 092358eac7..e8d78b2fc9 100644
--- a/app/src/main/res/layout/activity_send.xml
+++ b/app/src/main/res/layout/activity_send.xml
@@ -59,6 +59,7 @@
custom:label="@string/recipient"
custom:hint="@string/recipient_address"
custom:show_header="true"
+ custom:showAddressBook="true"
custom:ens="true"/>
diff --git a/app/src/main/res/layout/activity_transaction_detail.xml b/app/src/main/res/layout/activity_transaction_detail.xml
index 50a480f30c..816a0056f5 100644
--- a/app/src/main/res/layout/activity_transaction_detail.xml
+++ b/app/src/main/res/layout/activity_transaction_detail.xml
@@ -210,7 +210,8 @@
custom:gravity="start"
custom:bold="false"
custom:textColor="@color/mine"
- custom:marginRight="@dimen/dp18" />
+ custom:marginRight="@dimen/dp18"
+ custom:showAddressBook="true"/>
+ custom:marginRight="@dimen/dp18"
+ custom:showAddressBook="true"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_copy_textview.xml b/app/src/main/res/layout/item_copy_textview.xml
index 98ce281561..073b6d5831 100644
--- a/app/src/main/res/layout/item_copy_textview.xml
+++ b/app/src/main/res/layout/item_copy_textview.xml
@@ -32,4 +32,18 @@
android:foreground="?android:attr/selectableItemBackground"
android:src="@drawable/ic_copy" />
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_input_address.xml b/app/src/main/res/layout/item_input_address.xml
index fe4a3b29bc..a1873832fd 100644
--- a/app/src/main/res/layout/item_input_address.xml
+++ b/app/src/main/res/layout/item_input_address.xml
@@ -86,6 +86,19 @@
android:textSize="@dimen/sp17"
android:visibility="visible" />
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 7e45a52fa1..07ca7f2ee9 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -848,6 +848,20 @@
No Active sessions
Connect Wallet
La frase de semillas solo puede contener palabras
+
+ Address Book
+ Add Address
+ Edit Address
+ That address already exists as: %s
+ The name is already taken. Try another one.
+ Address added successfully
+ Save Address
+ Add Contact
+ Contacts will appear here
+ Select Recipient
+ Address updated successfully
+ Address should not be empty
+
Token #
External Link
Description
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index ca5e88a664..ff8b27e101 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -874,6 +874,20 @@
No Active sessions
Connect Wallet
La phrase de graine ne peut contenir que des mots
+
+ Address Book
+ Add Address
+ Edit Address
+ That address already exists as: %s
+ The name is already taken. Try another one.
+ Address added successfully
+ Save Address
+ Add Contact
+ Contacts will appear here
+ Select Recipient
+ Address updated successfully
+ Address should not be empty
+
Token #
External Link
Description
diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml
index 97ec80a02a..7b09a46901 100644
--- a/app/src/main/res/values-my/strings.xml
+++ b/app/src/main/res/values-my/strings.xml
@@ -887,6 +887,20 @@
တိုကင်နာမည်
ဗားရှင်း
မျိုးစေ့ထားသောစကားစုများတွင်စကားလုံးများသာပါ 0 င်နိုင်သည်
+
+ Address Book
+ Add Address
+ Edit Address
+ That address already exists as: %s
+ The name is already taken. Try another one.
+ Address added successfully
+ Save Address
+ Add Contact
+ Contacts will appear here
+ Select Recipient
+ Address updated successfully
+ Address should not be empty
+
Token #
External Link
Description
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index 16e1c8e6b8..e9c52f1d85 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -876,6 +876,20 @@
No Active sessions
Connect Wallet
Cụm từ hạt giống chỉ có thể chứa từ
+
+ Address Book
+ Add Address
+ Edit Address
+ That address already exists as: %s
+ The name is already taken. Try another one.
+ Address added successfully
+ Save Address
+ Add Contact
+ Contacts will appear here
+ Select Recipient
+ Address updated successfully
+ Address should not be empty
+
Token #
External Link
Description
diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml
index aac50056b1..0f3cdc0216 100644
--- a/app/src/main/res/values-zh/strings.xml
+++ b/app/src/main/res/values-zh/strings.xml
@@ -848,6 +848,20 @@
No Active sessions
Connect Wallet
助记符只能包含单词
+
+ Address Book
+ Add Address
+ Edit Address
+ That address already exists as: %s
+ The name is already taken. Try another one.
+ Address added successfully
+ Save Address
+ Add Contact
+ Contacts will appear here
+ Select Recipient
+ Address updated successfully
+ Address should not be empty
+
Token #
External Link
Description
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 2d6de4a5c8..e12d439a14 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -1,5 +1,8 @@
+
+
+
@@ -55,6 +58,7 @@
+
@@ -64,6 +68,7 @@
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a832202a22..6526b95d06 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -886,6 +886,20 @@
No Active sessions
Connect Wallet
Seed phrase can only contain words
+
+ Address Book
+ Add Address
+ Edit Address
+ That address already exists as: %s
+ The name is already taken. Try another one.
+ Address added successfully
+ Save Address
+ Add Contact
+ Contacts will appear here
+ Select Recipient
+ Address updated successfully
+ Address should not be empty
+
Token #
External Link
Description