diff --git a/app/schemas/cy.agorise.crystalwallet.dao.CrystalDatabase/2.json b/app/schemas/cy.agorise.crystalwallet.dao.CrystalDatabase/2.json index dfdbc9d..26c2578 100644 --- a/app/schemas/cy.agorise.crystalwallet.dao.CrystalDatabase/2.json +++ b/app/schemas/cy.agorise.crystalwallet.dao.CrystalDatabase/2.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 2, - "identityHash": "6dd1ca39bdde9af2cba2b35413c4975e", + "identityHash": "6a2b148b120a70c3faee581a13c96cf5", "entities": [ { "tableName": "account_seed", @@ -227,6 +227,55 @@ } ] }, + { + "tableName": "contact", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `gravatar` TEXT)", + "fields": [ + { + "fieldPath": "mId", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mName", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "mGravatar", + "columnName": "gravatar", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_contact_id", + "unique": false, + "columnNames": [ + "id" + ], + "createSql": "CREATE INDEX `index_contact_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_contact_name", + "unique": true, + "columnNames": [ + "name" + ], + "createSql": "CREATE UNIQUE INDEX `index_contact_name` ON `${TABLE_NAME}` (`name`)" + } + ], + "foreignKeys": [] + }, { "tableName": "crypto_currency", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `crypto_net` TEXT, `precision` INTEGER NOT NULL)", @@ -567,7 +616,7 @@ ], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"6dd1ca39bdde9af2cba2b35413c4975e\")" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"6a2b148b120a70c3faee581a13c96cf5\")" ] } } \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/GrapheneApiGenerator.java b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/GrapheneApiGenerator.java index e41a15a..424d919 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/GrapheneApiGenerator.java +++ b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/GrapheneApiGenerator.java @@ -61,7 +61,7 @@ public abstract class GrapheneApiGenerator { public static String faucetUrl = "http://185.208.208.147:5010"; private static String equivalentUrl = "http://185.208.208.147:8090"; //public static String url = "wss://bitshares.openledger.info/ws"; - //private static String equivalentUrl = "wss://bitshares.openledger.info/ws"; + //private static Str ing equivalentUrl = "wss://bitshares.openledger.info/ws"; // The message broker for bitshares diff --git a/app/src/main/java/cy/agorise/crystalwallet/dao/ContactDao.java b/app/src/main/java/cy/agorise/crystalwallet/dao/ContactDao.java new file mode 100644 index 0000000..77dd3fd --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/dao/ContactDao.java @@ -0,0 +1,29 @@ +package cy.agorise.crystalwallet.dao; + +import android.arch.lifecycle.LiveData; +import android.arch.paging.LivePagedListProvider; +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.OnConflictStrategy; +import android.arch.persistence.room.Query; + +import java.util.List; + +import cy.agorise.crystalwallet.models.Contact; +import cy.agorise.crystalwallet.models.CryptoCoinTransaction; + +/** + * Created by Henry Varona on 1/17/2018. + */ +@Dao +public interface ContactDao { + + @Query("SELECT * FROM contact") + LiveData> getAll(); + + @Query("SELECT * FROM contact ORDER BY name ASC") + LivePagedListProvider contactsByName(); + + @Query("SELECT * FROM contact WHERE id = :id") + LiveData getById(long id); +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/dao/CrystalDatabase.java b/app/src/main/java/cy/agorise/crystalwallet/dao/CrystalDatabase.java index 073ddfe..dc8b951 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/dao/CrystalDatabase.java +++ b/app/src/main/java/cy/agorise/crystalwallet/dao/CrystalDatabase.java @@ -9,6 +9,7 @@ import android.content.Context; import cy.agorise.crystalwallet.dao.converters.Converters; import cy.agorise.crystalwallet.models.AccountSeed; import cy.agorise.crystalwallet.models.BitsharesAssetInfo; +import cy.agorise.crystalwallet.models.Contact; import cy.agorise.crystalwallet.models.CryptoCoinBalance; import cy.agorise.crystalwallet.models.CryptoCoinTransaction; import cy.agorise.crystalwallet.models.CryptoCurrency; @@ -26,6 +27,7 @@ import cy.agorise.crystalwallet.models.GrapheneAccountInfo; AccountSeed.class, CryptoNetAccount.class, CryptoCoinTransaction.class, + Contact.class, CryptoCurrency.class, CryptoCoinBalance.class, GrapheneAccountInfo.class, @@ -42,6 +44,7 @@ public abstract class CrystalDatabase extends RoomDatabase { public abstract CryptoNetAccountDao cryptoNetAccountDao(); public abstract GrapheneAccountInfoDao grapheneAccountInfoDao(); public abstract TransactionDao transactionDao(); + public abstract ContactDao contactDao(); public abstract CryptoCoinBalanceDao cryptoCoinBalanceDao(); public abstract CryptoCurrencyDao cryptoCurrencyDao(); public abstract BitsharesAssetDao bitsharesAssetDao(); @@ -54,21 +57,8 @@ public abstract class CrystalDatabase extends RoomDatabase { Room.databaseBuilder(context, CrystalDatabase.class, "CrystalWallet.db") .allowMainThreadQueries() - //.addMigrations(MIGRATION_1_2) .build(); } return instance; } - - - - /*static final Migration MIGRATION_1_2 = new Migration(1, 2) { - @Override - public void migrate(SupportSQLiteDatabase database) { - database.execSQL("UPDATE TABLE 'crypto_net_account' ADD " - + "'subclass' INT, " - + "'bitshares_account_name' STRING, " - + "'bitshares_account_id' STRING "); - } - };*/ } diff --git a/app/src/main/java/cy/agorise/crystalwallet/models/Contact.java b/app/src/main/java/cy/agorise/crystalwallet/models/Contact.java new file mode 100644 index 0000000..1cc3d6e --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/models/Contact.java @@ -0,0 +1,103 @@ +package cy.agorise.crystalwallet.models; + + +import android.arch.persistence.room.ColumnInfo; +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.ForeignKey; +import android.arch.persistence.room.Ignore; +import android.arch.persistence.room.Index; +import android.arch.persistence.room.PrimaryKey; +import android.support.annotation.NonNull; +import android.support.v7.recyclerview.extensions.DiffCallback; + +import java.util.List; + +/** + * Represents a user contact + * + * Created by Henry Varona on 1/16/2018. + */ + +@Entity(tableName="contact", + indices = {@Index("id"),@Index(value = {"name"}, unique=true)}) +public class Contact { + + /** + * The id on the database + */ + @PrimaryKey(autoGenerate = true) + @ColumnInfo(name = "id") + private long mId; + + @ColumnInfo(name="name") + private String mName; + + @ColumnInfo(name = "gravatar") + private String mGravatar; + + @Ignore + public List mAddresses; + + public long getId() { + return mId; + } + + public void setId(long id) { + this.mId = id; + } + + public String getName() { + return mName; + } + + public void setName(String name) { + this.mName = name; + } + + public String getGravatar() { + return mGravatar; + } + + public void setGravatar(String gravatar) { + this.mGravatar = gravatar; + } + + public int addressesCount(){ + return this.mAddresses.size(); + } + + public ContactAddress getAddress(int index){ + return this.mAddresses.get(index); + } + + public void addAddress(ContactAddress address){ + this.mAddresses.add(address); + } + + public static final DiffCallback DIFF_CALLBACK = new DiffCallback() { + @Override + public boolean areItemsTheSame( + @NonNull Contact oldContact, @NonNull Contact newContact) { + return oldContact.getId() == newContact.getId(); + } + @Override + public boolean areContentsTheSame( + @NonNull Contact oldContact, @NonNull Contact newContact) { + return oldContact.equals(newContact); + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Contact contact = (Contact) o; + + if (mId != contact.mId) return false; + if (!mName.equals(contact.mName)) return false; + if (mGravatar != null ? !mGravatar.equals(contact.mGravatar) : contact.mGravatar != null) + return false; + return mAddresses != null ? mAddresses.equals(contact.mAddresses) : contact.mAddresses == null; + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/models/ContactAddress.java b/app/src/main/java/cy/agorise/crystalwallet/models/ContactAddress.java new file mode 100644 index 0000000..ae247dd --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/models/ContactAddress.java @@ -0,0 +1,79 @@ +package cy.agorise.crystalwallet.models; + + +import android.arch.persistence.room.ColumnInfo; +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.Index; +import android.support.annotation.NonNull; +import android.support.v7.recyclerview.extensions.DiffCallback; + +/** + * Represents a user contact address + * + * Created by Henry Varona on 1/16/2018. + */ + +@Entity(tableName="contact_address", + primaryKeys = {"contact_id","crypto_currency_id"}, + indices = {@Index(value = {"contact_id","crypto_currency_id"}, unique=true)}) +public class ContactAddress { + + @ColumnInfo(name = "contact_id") + private long mContactId; + + @ColumnInfo(name = "crypto_currency_id") + private long mCryptoCurrencyId; + + @ColumnInfo(name="address") + private String mAddress; + + public long getContactId() { + return mContactId; + } + + public void setContactId(long contactId) { + this.mContactId = contactId; + } + + public long getCryptoCurrencyId() { + return mCryptoCurrencyId; + } + + public void setCryptoCurrencyId(long cryptoCurrencyId) { + this.mCryptoCurrencyId = cryptoCurrencyId; + } + + public String getAddress() { + return mAddress; + } + + public void setAddress(String address) { + this.mAddress = address; + } + + public static final DiffCallback DIFF_CALLBACK = new DiffCallback() { + @Override + public boolean areItemsTheSame( + @NonNull ContactAddress oldContactAddress, @NonNull ContactAddress newContactAddress) { + return (oldContactAddress.getContactId() == newContactAddress.getContactId()) + && (oldContactAddress.getCryptoCurrencyId() == newContactAddress.getCryptoCurrencyId()); + } + @Override + public boolean areContentsTheSame( + @NonNull ContactAddress oldContactAddress, @NonNull ContactAddress newContactAddress) { + return oldContactAddress.equals(newContactAddress); + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ContactAddress that = (ContactAddress) o; + + if (mContactId != that.mContactId) return false; + if (mCryptoCurrencyId != that.mCryptoCurrencyId) return false; + return mAddress.equals(that.mAddress); + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/ContactListViewModel.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/ContactListViewModel.java new file mode 100644 index 0000000..ec076d9 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/ContactListViewModel.java @@ -0,0 +1,36 @@ +package cy.agorise.crystalwallet.viewmodels; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.LiveData; +import android.arch.paging.PagedList; + +import cy.agorise.crystalwallet.dao.CrystalDatabase; +import cy.agorise.crystalwallet.models.Contact; +import cy.agorise.crystalwallet.models.CryptoCoinTransaction; + +/** + * Created by Henry Varona on 1/17/2018. + */ + +public class ContactListViewModel extends AndroidViewModel { + + private LiveData> contactList; + private CrystalDatabase db; + + public ContactListViewModel(Application application) { + super(application); + this.db = CrystalDatabase.getAppDatabase(application.getApplicationContext()); + contactList = this.db.contactDao().contactsByName().create(0, + new PagedList.Config.Builder() + .setEnablePlaceholders(true) + .setPageSize(10) + .setPrefetchDistance(10) + .build() + ); + } + + public LiveData> getContactList(){ + return this.contactList; + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/ContactListAdapter.java b/app/src/main/java/cy/agorise/crystalwallet/views/ContactListAdapter.java new file mode 100644 index 0000000..bc502a3 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/views/ContactListAdapter.java @@ -0,0 +1,41 @@ +package cy.agorise.crystalwallet.views; + + +import android.support.v7.recyclerview.extensions.ListAdapter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.models.Contact; + +/** + * Created by Henry Varona on 1/16/2018. + * + * An adapter to show the elements of a list of contacts + */ + +public class ContactListAdapter extends ListAdapter { + + public ContactListAdapter() { + super(Contact.DIFF_CALLBACK); + } + + @Override + public ContactViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.contact_list_item,parent,false); + + + return new ContactViewHolder(v); + } + + @Override + public void onBindViewHolder(ContactViewHolder holder, int position) { + Contact contact = getItem(position); + if (contact != null) { + holder.bindTo(contact); + } else { + holder.clear(); + } + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/ContactListView.java b/app/src/main/java/cy/agorise/crystalwallet/views/ContactListView.java new file mode 100644 index 0000000..e9efce9 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/views/ContactListView.java @@ -0,0 +1,113 @@ +package cy.agorise.crystalwallet.views; + +import android.arch.paging.PagedList; +import android.content.Context; +import android.support.v4.app.Fragment; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.RelativeLayout; + +import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.models.Contact; +import cy.agorise.crystalwallet.models.CryptoCoinTransaction; +import cy.agorise.crystalwallet.viewmodels.ContactListViewModel; +import cy.agorise.crystalwallet.viewmodels.TransactionListViewModel; + +/** + * Created by Henry Varona on 1/15/2018. + * + * A list view showing the user contacts + */ + +public class ContactListView extends RelativeLayout { + + LayoutInflater mInflater; + + /* + * The root view of this view + */ + View rootView; + /* + * The list view that holds every user contact item + */ + RecyclerView listView; + /* + * The adapter for the previous list view + */ + ContactListAdapter listAdapter; + + ContactListViewModel contactListViewModel; + + /* + * how much contacts will remain to show before the list loads more + */ + private int visibleThreshold = 5; + /* + * if true, the contact list will be loading new data + */ + private boolean loading = true; + + /* + * One of three constructors needed to be inflated from a layout + */ + public ContactListView(Context context){ + super(context); + this.mInflater = LayoutInflater.from(context); + init(); + } + + /* + * One of three constructors needed to be inflated from a layout + */ + public ContactListView(Context context, AttributeSet attrs) { + super(context, attrs); + this.mInflater = LayoutInflater.from(context); + init(); + } + + /* + * One of three constructors needed to be inflated from a layout + */ + public ContactListView(Context context, AttributeSet attrs, int defStyle){ + super(context, attrs, defStyle); + this.mInflater = LayoutInflater.from(context); + init(); + } + + /* + * Initializes this view + */ + public void init(){ + rootView = mInflater.inflate(R.layout.contact_list, this, true); + this.listView = rootView.findViewById(R.id.contactListView); + + final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this.getContext()); + this.listView.setLayoutManager(linearLayoutManager); + //Prevents the list to start again when scrolling to the end + this.listView.setNestedScrollingEnabled(false); + + } + + /* + * Sets the elements data of this view + * + * @param data the contacts that will be showed to the user + */ + public void setData(PagedList data){ + //Initializes the adapter of the contact list + if (this.listAdapter == null) { + this.listAdapter = new ContactListAdapter(); + this.listView.setAdapter(this.listAdapter); + } + + //Sets the data of the transaction list + if (data != null) { + this.listAdapter.setList(data); + } + } + + +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/ContactViewHolder.java b/app/src/main/java/cy/agorise/crystalwallet/views/ContactViewHolder.java new file mode 100644 index 0000000..429acf0 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/views/ContactViewHolder.java @@ -0,0 +1,54 @@ +package cy.agorise.crystalwallet.views; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.models.Contact; + +/** + * Created by Henry Varona on 1/17/2017. + * + * Represents an element view from the Contact List + */ + +public class ContactViewHolder extends RecyclerView.ViewHolder { + private TextView tvName; + private ImageView ivThumbnail; + private TextView tvLastPaid; + private Context context; + + public ContactViewHolder(View itemView) { + super(itemView); + //TODO: use ButterKnife to load this + tvName = (TextView) itemView.findViewById(R.id.tvContactName); + ivThumbnail = (ImageView) itemView.findViewById(R.id.ivContactThumbnail); + tvLastPaid = (TextView) itemView.findViewById(R.id.tvLastPaid); + this.context = itemView.getContext(); + + } + + /* + * Clears the information in this element view + */ + public void clear(){ + tvName.setText(""); + ivThumbnail.setImageResource(android.R.color.transparent); + tvLastPaid.setText(""); + } + + /* + * Binds this view with the data of an element of the list + */ + public void bindTo(final Contact contact) { + if (contact == null){ + this.clear(); + } else { + tvName.setText(contact.getName()); + tvLastPaid.setText("Paid: 1 Jan, 2001 01:01"); + } + } +} diff --git a/app/src/main/res/layout/contact_list.xml b/app/src/main/res/layout/contact_list.xml new file mode 100644 index 0000000..fc6edac --- /dev/null +++ b/app/src/main/res/layout/contact_list.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/contact_list_item.xml b/app/src/main/res/layout/contact_list_item.xml new file mode 100644 index 0000000..121112f --- /dev/null +++ b/app/src/main/res/layout/contact_list_item.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_contacts.xml b/app/src/main/res/layout/fragment_contacts.xml index 2612646..95a3da7 100644 --- a/app/src/main/res/layout/fragment_contacts.xml +++ b/app/src/main/res/layout/fragment_contacts.xml @@ -4,10 +4,9 @@ android:layout_height="match_parent" tools:context="cy.agorise.crystalwallet.fragments.ContactsFragment"> - - + android:layout_height="wrap_content" + android:id="@+id/vContactListView" />