diff --git a/wallet/build.gradle b/wallet/build.gradle
index 01887d5e50..56e7d66178 100644
--- a/wallet/build.gradle
+++ b/wallet/build.gradle
@@ -78,8 +78,8 @@ dependencies {
implementation 'androidx.multidex:multidex:2.0.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- implementation "androidx.core:core-ktx:1.2.0"
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.4"
+ implementation "androidx.core:core-ktx:1.3.1"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8"
implementation 'com.google.firebase:firebase-analytics:17.2.0'
implementation 'com.github.bumptech.glide:glide:4.11.0'
diff --git a/wallet/res/layout/activity_dashpay_user.xml b/wallet/res/layout/activity_dashpay_user.xml
index 9cc43b9d7c..829b839612 100644
--- a/wallet/res/layout/activity_dashpay_user.xml
+++ b/wallet/res/layout/activity_dashpay_user.xml
@@ -237,4 +237,15 @@
+
+
\ No newline at end of file
diff --git a/wallet/res/layout/dialog_confirm_transaction.xml b/wallet/res/layout/dialog_confirm_transaction.xml
index 624b6dd8ba..ef98469a10 100644
--- a/wallet/res/layout/dialog_confirm_transaction.xml
+++ b/wallet/res/layout/dialog_confirm_transaction.xml
@@ -104,6 +104,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wallet/res/layout/transaction_result_content.xml b/wallet/res/layout/transaction_result_content.xml
index 289bc916cf..5d8af9ed0f 100644
--- a/wallet/res/layout/transaction_result_content.xml
+++ b/wallet/res/layout/transaction_result_content.xml
@@ -119,6 +119,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
Sent from
Sent to
Received at
+ Received from
Moved from
Internally moved to
@@ -408,4 +409,8 @@
You have accepted the contact request from %s
Your username %s has been successfully created on the Dash Network
+
+ Not a valid Dash Username or Identity\n\n%s
+ Sending to
+
\ No newline at end of file
diff --git a/wallet/src/de/schildbach/wallet/WalletApplication.java b/wallet/src/de/schildbach/wallet/WalletApplication.java
index eb9f823d3b..95c6e13a0e 100644
--- a/wallet/src/de/schildbach/wallet/WalletApplication.java
+++ b/wallet/src/de/schildbach/wallet/WalletApplication.java
@@ -66,7 +66,6 @@
import org.bitcoinj.wallet.Wallet;
import org.bitcoinj.wallet.WalletProtobufSerializer;
import org.dashevo.dashpay.BlockchainIdentity;
-import org.dashevo.platform.Platform;
import org.dash.wallet.common.Configuration;
import org.dash.wallet.common.ResetAutoLogoutTimerHandler;
import org.dash.wallet.integration.uphold.data.UpholdClient;
@@ -245,8 +244,6 @@ public void uncaughtException(final Thread thread, final Throwable throwable) {
activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
blockchainServiceIntent = new Intent(this, BlockchainServiceImpl.class);
-
- PlatformRepo.Companion.initPlatformRepo(this);
}
public void setWallet(Wallet newWallet) {
@@ -306,7 +303,7 @@ private void initUphold() {
}
private void initPlatform() {
- PlatformRepo.Companion.getInstance().startUpdateTimer();
+ PlatformRepo.initPlatformRepo(this);
}
public void maybeStartAutoLogoutTimer() {
diff --git a/wallet/src/de/schildbach/wallet/data/DashPayContactRequestDao.kt b/wallet/src/de/schildbach/wallet/data/DashPayContactRequestDao.kt
index 4b16c9114e..8e987d4639 100644
--- a/wallet/src/de/schildbach/wallet/data/DashPayContactRequestDao.kt
+++ b/wallet/src/de/schildbach/wallet/data/DashPayContactRequestDao.kt
@@ -14,6 +14,18 @@ interface DashPayContactRequestDao {
@Query("SELECT * FROM dashpay_contact_request")
fun loadAll(): LiveData
+ @Query("SELECT * FROM dashpay_contact_request WHERE userId = :userId")
+ fun loadToOthers(userId: String): LiveData
+
+ @Query("SELECT * FROM dashpay_contact_request WHERE toUserId = :toUserId")
+ fun loadFromOthers(toUserId: String): LiveData
+
+ fun loadDistinctToOthers(id: String):
+ LiveData = loadToOthers(id).getDistinct()
+
+ fun loadDistinctFromOthers(id: String):
+ LiveData = loadFromOthers(id).getDistinct()
+
@Query("DELETE FROM dashpay_contact_request")
fun clear()
}
\ No newline at end of file
diff --git a/wallet/src/de/schildbach/wallet/data/DashPayProfileDao.kt b/wallet/src/de/schildbach/wallet/data/DashPayProfileDao.kt
index f44c8f5735..a9d85828eb 100644
--- a/wallet/src/de/schildbach/wallet/data/DashPayProfileDao.kt
+++ b/wallet/src/de/schildbach/wallet/data/DashPayProfileDao.kt
@@ -14,6 +14,9 @@ interface DashPayProfileDao {
@Query("SELECT * FROM dashpay_profile where userId = :userId")
fun load(userId: String): LiveData
+ fun loadDistinct(userId: String):
+ LiveData = load(userId).getDistinct()
+
@Query("DELETE FROM dashpay_profile")
fun clear()
}
\ No newline at end of file
diff --git a/wallet/src/de/schildbach/wallet/data/NotificationItem.kt b/wallet/src/de/schildbach/wallet/data/NotificationItem.kt
new file mode 100644
index 0000000000..937cee328a
--- /dev/null
+++ b/wallet/src/de/schildbach/wallet/data/NotificationItem.kt
@@ -0,0 +1,31 @@
+package de.schildbach.wallet.data
+
+import org.bitcoinj.core.Transaction
+import java.util.*
+
+data class NotificationItem private constructor(val type: Type,
+ val usernameSearchResult: UsernameSearchResult? = null,
+ val tx: Transaction? = null) {
+
+ constructor(usernameSearchResult: UsernameSearchResult) : this(Type.CONTACT_REQUEST, usernameSearchResult = usernameSearchResult)
+
+ constructor(tx: Transaction) : this(Type.PAYMENT, tx = tx)
+
+ enum class Type {
+ CONTACT_REQUEST,
+ CONTACT,
+ PAYMENT
+ }
+
+ /* date is in milliseconds */
+ val date = when (type) {
+ Type.CONTACT_REQUEST, Type.CONTACT -> usernameSearchResult!!.date * 1000
+ else -> tx!!.updateTime.time * 1000
+ }
+
+ val id = when (type) {
+ Type.CONTACT_REQUEST -> usernameSearchResult!!.fromContactRequest!!.userId
+ Type.CONTACT -> usernameSearchResult!!.toContactRequest!!.toUserId
+ Type.PAYMENT -> tx!!.txId.toString()
+ }
+}
diff --git a/wallet/src/de/schildbach/wallet/data/PaymentIntent.java b/wallet/src/de/schildbach/wallet/data/PaymentIntent.java
index fb20136819..78ebf9aa41 100644
--- a/wallet/src/de/schildbach/wallet/data/PaymentIntent.java
+++ b/wallet/src/de/schildbach/wallet/data/PaymentIntent.java
@@ -21,6 +21,7 @@
import java.util.Arrays;
+import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.bitcoinj.core.Address;
@@ -163,18 +164,16 @@ private Output(final Parcel in) {
@Nullable
public final byte[] paymentRequestHash;
- public boolean useInstantX = false;
-
- public void setInstantX(boolean set) {
- useInstantX = set;
- }
+ @Nullable
+ public final String payeeUserId;
private static final Logger log = LoggerFactory.getLogger(PaymentIntent.class);
public PaymentIntent(@Nullable final Standard standard, @Nullable final String payeeName,
@Nullable final String payeeVerifiedBy, @Nullable final Output[] outputs, @Nullable final String memo,
@Nullable final String paymentUrl, @Nullable final byte[] payeeData,
- @Nullable final String paymentRequestUrl, @Nullable final byte[] paymentRequestHash) {
+ @Nullable final String paymentRequestUrl, @Nullable final byte[] paymentRequestHash,
+ @Nullable final String payeeUserId) {
this.standard = standard;
this.payeeName = payeeName;
this.payeeVerifiedBy = payeeVerifiedBy;
@@ -184,37 +183,40 @@ public PaymentIntent(@Nullable final Standard standard, @Nullable final String p
this.payeeData = payeeData;
this.paymentRequestUrl = paymentRequestUrl;
this.paymentRequestHash = paymentRequestHash;
- }
-
- public PaymentIntent(@Nullable final Standard standard, @Nullable final String payeeName, @Nullable final String payeeVerifiedBy,
- @Nullable final Output[] outputs, @Nullable final String memo, @Nullable final String paymentUrl, @Nullable final byte[] payeeData,
- @Nullable final String paymentRequestUrl, @Nullable final byte[] paymentRequestHash, boolean useInstantX) {
- this(standard, payeeName, payeeVerifiedBy, outputs, memo, paymentUrl, payeeData, paymentRequestUrl, paymentRequestHash);
- this.useInstantX = useInstantX;
+ this.payeeUserId = payeeUserId;
}
private PaymentIntent(final Address address, @Nullable final String addressLabel) {
- this(null, null, null, buildSimplePayTo(Coin.ZERO, address), addressLabel, null, null, null, null);
+ this(null, null, null, buildSimplePayTo(Coin.ZERO, address), addressLabel, null, null, null, null, null);
}
+
public static PaymentIntent blank() {
- return new PaymentIntent(null, null, null, null, null, null, null, null, null);
+ return new PaymentIntent(null, null, null, null, null, null, null, null, null, null);
}
public static PaymentIntent fromAddress(final Address address, @Nullable final String addressLabel) {
return new PaymentIntent(address, addressLabel);
}
+ public static PaymentIntent fromAddressWithIdentity(final Address address, @Nullable final String payeeUserId) {
+ return new PaymentIntent(null, null, null, buildSimplePayTo(Coin.ZERO, address), null, null, null, null, null, payeeUserId);
+ }
+
public static PaymentIntent fromAddress(final String address, @Nullable final String addressLabel)
throws AddressFormatException {
return new PaymentIntent(Address.fromString(Constants.NETWORK_PARAMETERS, address), addressLabel);
}
+ public static PaymentIntent fromUserId(final String payeeUserId) {
+ return new PaymentIntent(null, null, null, null, null, null, null, null, null, payeeUserId);
+ }
+
public static PaymentIntent from(final String address, @Nullable final String addressLabel,
@Nullable final Coin amount) throws AddressFormatException {
return new PaymentIntent(null, null, null,
buildSimplePayTo(amount, Address.fromString(Constants.NETWORK_PARAMETERS, address)), addressLabel, null,
- null, null, null);
+ null, null, null, null);
}
public static PaymentIntent fromBitcoinUri(final BitcoinURI bitcoinUri) {
@@ -223,11 +225,10 @@ public static PaymentIntent fromBitcoinUri(final BitcoinURI bitcoinUri) {
final String bluetoothMac = (String) bitcoinUri.getParameterByName(Bluetooth.MAC_URI_PARAM);
final String paymentRequestHashStr = (String) bitcoinUri.getParameterByName("h");
final byte[] paymentRequestHash = paymentRequestHashStr != null ? base64UrlDecode(paymentRequestHashStr) : null;
- boolean useInstantSend = bitcoinUri.getRequestInstantSend();
return new PaymentIntent(PaymentIntent.Standard.BIP21, null, null, outputs, bitcoinUri.getLabel(),
bluetoothMac != null ? "bt:" + bluetoothMac : null, null, bitcoinUri.getPaymentRequestUrl(),
- paymentRequestHash, useInstantSend);
+ paymentRequestHash, null);
}
private static final BaseEncoding BASE64URL = BaseEncoding.base64Url().omitPadding();
@@ -263,7 +264,7 @@ public PaymentIntent mergeWithEditedValues(@Nullable final Coin editedAmount,
outputs = buildSimplePayTo(editedAmount, editedAddress);
}
- return new PaymentIntent(standard, payeeName, payeeVerifiedBy, outputs, memo, null, payeeData, null, null, useInstantX);
+ return new PaymentIntent(standard, payeeName, payeeVerifiedBy, outputs, memo, null, payeeData, null, null, null);
}
public SendRequest toSendRequest() {
@@ -366,6 +367,10 @@ public boolean isBluetoothPaymentRequestUrl() {
return Bluetooth.isBluetoothUrl(paymentRequestUrl);
}
+ public boolean isIdentityPaymentRequest() {
+ return payeeUserId != null && !payeeUserId.isEmpty();
+ }
+
/**
* Check if given payment intent is only extending on this one, that is it does not alter any of
* the fields. Address and amount fields must be equal, respectively (non-existence included).
@@ -475,7 +480,7 @@ public void writeToParcel(final Parcel dest, final int flags) {
} else {
dest.writeInt(0);
}
- dest.writeByte(useInstantX ? (byte) 1 : (byte) 0);
+ dest.writeString(payeeUserId);
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
@@ -525,10 +530,7 @@ private PaymentIntent(final Parcel in) {
} else {
paymentRequestHash = null;
}
- useInstantX = in.readByte() == 1;
- }
- public boolean getUseInstantSend() {
- return useInstantX;
+ payeeUserId = in.readString();
}
}
diff --git a/wallet/src/de/schildbach/wallet/data/RoomDatabaseExtentions.kt b/wallet/src/de/schildbach/wallet/data/RoomDatabaseExtentions.kt
new file mode 100644
index 0000000000..024c023016
--- /dev/null
+++ b/wallet/src/de/schildbach/wallet/data/RoomDatabaseExtentions.kt
@@ -0,0 +1,25 @@
+package de.schildbach.wallet.data
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.Observer
+
+fun LiveData.getDistinct(): LiveData {
+ val distinctLiveData = MediatorLiveData()
+ distinctLiveData.addSource(this, object : Observer {
+ private var initialized = false
+ private var lastObj: T? = null
+ override fun onChanged(obj: T?) {
+ if (!initialized) {
+ initialized = true
+ lastObj = obj
+ distinctLiveData.postValue(lastObj)
+ } else if ((obj == null && lastObj != null)
+ || obj != lastObj) {
+ lastObj = obj
+ distinctLiveData.postValue(lastObj)
+ }
+ }
+ })
+ return distinctLiveData
+}
\ No newline at end of file
diff --git a/wallet/src/de/schildbach/wallet/livedata/EncryptWalletLiveData.kt b/wallet/src/de/schildbach/wallet/livedata/EncryptWalletLiveData.kt
index 6cb8232da6..bee70b7647 100644
--- a/wallet/src/de/schildbach/wallet/livedata/EncryptWalletLiveData.kt
+++ b/wallet/src/de/schildbach/wallet/livedata/EncryptWalletLiveData.kt
@@ -95,10 +95,6 @@ class EncryptWalletLiveData(application: Application) : MutableLiveData getConnectedPeers();
diff --git a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java
index a3c3aff506..54b98232f5 100644
--- a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java
+++ b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java
@@ -875,6 +875,13 @@ public int onStartCommand(final Intent intent, final int flags, final int startI
log.info("peergroup not available, not broadcasting transaction " + tx.getHashAsString());
tx.getConfidence().setPeerInfo(0, 1);
}
+ } else if(BlockchainService.ACTION_RESET_BLOOMFILTERS.equals(action)) {
+ if (peerGroup != null) {
+ log.info("recalulating bloom filters");
+ peerGroup.recalculateFastCatchupAndFilter(PeerGroup.FilterRecalculateMode.SEND_IF_CHANGED);
+ } else {
+ log.info("peergroup not available, not resetting bloom filers");
+ }
}
} else {
log.warn("service restart, although it was started as non-sticky");
diff --git a/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt b/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt
index 903d09f0db..f2c532443c 100644
--- a/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt
+++ b/wallet/src/de/schildbach/wallet/ui/DashPayUserActivity.kt
@@ -17,7 +17,6 @@
package de.schildbach.wallet.ui
-import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.drawable.AnimationDrawable
@@ -26,22 +25,37 @@ import android.view.View
import android.widget.Toast
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
+import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide
-import de.schildbach.wallet.data.DashPayContactRequest
-import de.schildbach.wallet.data.DashPayProfile
+import de.schildbach.wallet.WalletApplication
+import de.schildbach.wallet.data.*
import de.schildbach.wallet.livedata.Resource
import de.schildbach.wallet.livedata.Status
import de.schildbach.wallet.ui.dashpay.DashPayViewModel
+import de.schildbach.wallet.ui.dashpay.NotificationsAdapter
+import de.schildbach.wallet.ui.dashpay.PlatformRepo
+import de.schildbach.wallet.ui.send.SendCoinsInternalActivity
import de.schildbach.wallet_test.R
import kotlinx.android.synthetic.main.activity_dashpay_user.*
+import kotlinx.coroutines.runBlocking
+import org.bitcoinj.core.PrefixedChecksummedBytes
+import org.bitcoinj.core.Transaction
+import org.bitcoinj.core.VerificationException
import org.dash.wallet.common.InteractionAwareActivity
+import kotlin.collections.ArrayList
-class DashPayUserActivity : InteractionAwareActivity() {
+class DashPayUserActivity : InteractionAwareActivity(),
+ NotificationsAdapter.OnItemClickListener,
+ NotificationsAdapter.OnContactRequestButtonClickListener {
private lateinit var dashPayViewModel: DashPayViewModel
private val username by lazy { intent.getStringExtra(USERNAME) }
private val profile: DashPayProfile by lazy { intent.getParcelableExtra(PROFILE) as DashPayProfile }
private val displayName by lazy { profile.displayName }
+ private val notificationsAdapter: NotificationsAdapter = NotificationsAdapter(this, WalletApplication.getInstance().wallet, this)
+ private var contactRequestReceived: Boolean = false
+ private var contactRequestSent: Boolean = false
+ private var sendingRequest: Boolean = true
companion object {
private const val USERNAME = "username"
@@ -70,6 +84,10 @@ class DashPayUserActivity : InteractionAwareActivity() {
setContentView(R.layout.activity_dashpay_user)
close.setOnClickListener { finish() }
+ contactRequestSent = intent.getBooleanExtra(CONTACT_REQUEST_SENT, false)
+ contactRequestReceived = intent.getBooleanExtra(CONTACT_REQUEST_RECEIVED, false)
+
+ dashPayViewModel = ViewModelProvider(this).get(DashPayViewModel::class.java)
val defaultAvatar = UserAvatarPlaceholderDrawable.getDrawable(this, username[0])
if (profile.avatarUrl.isNotEmpty()) {
@@ -86,10 +104,15 @@ class DashPayUserActivity : InteractionAwareActivity() {
}
updateContactRelationUi()
- dashPayViewModel = ViewModelProvider(this).get(DashPayViewModel::class.java)
-
- sendContactRequestBtn.setOnClickListener { sendContactRequest(profile.userId) }
- accept.setOnClickListener { sendContactRequest(profile.userId) }
+ sendContactRequestBtn.setOnClickListener {
+ sendingRequest = true
+ sendContactRequest(profile.userId)
+ }
+ accept.setOnClickListener {
+ sendingRequest = false
+ sendContactRequest(profile.userId)
+ }
+ payContactBtn.setOnClickListener { startPayActivity() }
dashPayViewModel.getContactRequestLiveData.observe(this, object : Observer> {
override fun onChanged(it: Resource?) {
@@ -104,7 +127,9 @@ class DashPayUserActivity : InteractionAwareActivity() {
}
Status.SUCCESS -> {
setResult(RESULT_CODE_CHANGED)
- intent.putExtra(CONTACT_REQUEST_SENT, true)
+ if (sendingRequest)
+ contactRequestSent = true
+ else contactRequestReceived = true
updateContactRelationUi()
dashPayViewModel.getContactRequestLiveData.removeObserver(this)
}
@@ -112,6 +137,20 @@ class DashPayUserActivity : InteractionAwareActivity() {
}
}
})
+
+ notifications_rv.layoutManager = LinearLayoutManager(this)
+ notifications_rv.adapter = this.notificationsAdapter
+ this.notificationsAdapter.itemClickListener = this
+
+ if (contactRequestReceived && contactRequestSent) {
+ dashPayViewModel.notificationsForUserLiveData.observe(this, Observer {
+ if (Status.SUCCESS == it.status) {
+ if (it.data != null) {
+ processResults(it.data)
+ }
+ }
+ })
+ }
}
private fun startLoading() {
@@ -126,9 +165,6 @@ class DashPayUserActivity : InteractionAwareActivity() {
}
private fun updateContactRelationUi() {
- val contactRequestSent = intent.getBooleanExtra(CONTACT_REQUEST_SENT, false)
- val contactRequestReceived = intent.getBooleanExtra(CONTACT_REQUEST_RECEIVED, false)
-
listOf(sendContactRequestBtn, sendingContactRequestBtn, contactRequestSentBtn,
contactRequestReceivedContainer, payContactBtn).forEach { it.visibility = View.GONE }
@@ -136,20 +172,25 @@ class DashPayUserActivity : InteractionAwareActivity() {
//No Relationship
false to false -> {
sendContactRequestBtn.visibility = View.VISIBLE
+ notifications_rv.visibility = View.GONE
}
//Contact Established
true to true -> {
payContactBtn.visibility = View.VISIBLE
+ notifications_rv.visibility = View.VISIBLE
+ dashPayViewModel.searchNotificationsForUser(profile.userId)
}
//Request Sent / Pending
true to false -> {
contactRequestSentBtn.visibility = View.VISIBLE
+ notifications_rv.visibility = View.GONE
}
//Request Received
false to true -> {
payContactBtn.visibility = View.VISIBLE
contactRequestReceivedContainer.visibility = View.VISIBLE
requestTitle.text = getString(R.string.contact_request_received_title, username)
+ notifications_rv.visibility = View.GONE
}
}
}
@@ -159,4 +200,71 @@ class DashPayUserActivity : InteractionAwareActivity() {
overridePendingTransition(R.anim.activity_stay, R.anim.slide_out_bottom)
}
+ private fun startPayActivity() {
+ handleString(profile.userId, true, R.string.scan_to_pay_username_dialog_message)
+ finish()
+ }
+
+ private fun handleString(input: String, fireAction: Boolean, errorDialogTitleResId: Int) {
+ object : InputParser.StringInputParser(input, true) {
+
+ override fun handlePaymentIntent(paymentIntent: PaymentIntent) {
+ if (fireAction) {
+ SendCoinsInternalActivity.start(this@DashPayUserActivity, paymentIntent, true)
+ }
+ }
+
+ override fun error(ex: Exception?, messageResId: Int, vararg messageArgs: Any) {
+ if (fireAction) {
+ dialog(this@DashPayUserActivity, null, errorDialogTitleResId, messageResId, *messageArgs)
+ }
+ }
+
+ override fun handlePrivateKey(key: PrefixedChecksummedBytes) {
+ // ignore
+ }
+
+ @Throws(VerificationException::class)
+ override fun handleDirectTransaction(tx: Transaction) {
+ // ignore
+ }
+ }.parse()
+ }
+
+ override fun onItemClicked(view: View, usernameSearchResult: UsernameSearchResult) {
+ //do nothing if an item is clicked for now
+ }
+
+ override fun onAcceptRequest(usernameSearchResult: UsernameSearchResult, position: Int) {
+ // do nothing
+ }
+
+ override fun onIgnoreRequest(usernameSearchResult: UsernameSearchResult, position: Int) {
+ //do nothing if an item is clicked for now
+ }
+
+ private fun processResults(data: List) {
+
+ val results = ArrayList()
+
+ data.forEach { results.add(NotificationsAdapter.ViewItem(it, getViewType(it), false)) }
+
+ notificationsAdapter.results = results
+ }
+
+ private fun getViewType(notificationItem: NotificationItem): Int {
+ return when (notificationItem.type) {
+ NotificationItem.Type.CONTACT_REQUEST,
+ NotificationItem.Type.CONTACT -> return when (notificationItem.usernameSearchResult!!.requestSent to notificationItem.usernameSearchResult.requestReceived) {
+ true to true -> {
+ NotificationsAdapter.NOTIFICATION_CONTACT_ADDED
+ }
+ false to true -> {
+ NotificationsAdapter.NOTIFICATION_CONTACT_REQUEST_RECEIVED
+ }
+ else -> throw IllegalArgumentException("View not supported")
+ }
+ NotificationItem.Type.PAYMENT -> NotificationsAdapter.NOTIFICATION_PAYMENT
+ }
+ }
}
\ No newline at end of file
diff --git a/wallet/src/de/schildbach/wallet/ui/EnterAmountFragment.kt b/wallet/src/de/schildbach/wallet/ui/EnterAmountFragment.kt
index fdc53ff6bf..53c5d11553 100644
--- a/wallet/src/de/schildbach/wallet/ui/EnterAmountFragment.kt
+++ b/wallet/src/de/schildbach/wallet/ui/EnterAmountFragment.kt
@@ -23,6 +23,7 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
+import com.bumptech.glide.Glide
import de.schildbach.wallet.Constants
import de.schildbach.wallet.ui.send.EnterAmountSharedViewModel
import de.schildbach.wallet.ui.widget.NumericKeyboardView
@@ -271,6 +272,24 @@ class EnterAmountFragment : Fragment() {
}
applyNewValue(it.toPlainString())
})
+ userinfo.visibility = View.GONE
+ sharedViewModel.dashPayProfileData.observe(viewLifecycleOwner, Observer {
+ userinfo.visibility = View.VISIBLE
+ displayname.text = if (it.displayName.isNotEmpty())
+ it.displayName
+ else
+ it.username
+
+ val defaultAvatar = UserAvatarPlaceholderDrawable.getDrawable(context!!,
+ it.username[0])
+
+ if(it.avatarUrl.isNotEmpty()) {
+ Glide.with(avatar).load(it.avatarUrl).circleCrop()
+ .placeholder(defaultAvatar).into(avatar)
+ } else {
+ avatar.background = defaultAvatar
+ }
+ })
}
private fun applyCurrencySymbol(symbol: String) {
diff --git a/wallet/src/de/schildbach/wallet/ui/InputParser.java b/wallet/src/de/schildbach/wallet/ui/InputParser.java
index 8090bba682..a611ee9354 100644
--- a/wallet/src/de/schildbach/wallet/ui/InputParser.java
+++ b/wallet/src/de/schildbach/wallet/ui/InputParser.java
@@ -130,6 +130,14 @@ public void parse() {
} catch (final AddressFormatException x) {
log.info("got invalid address", x);
+ error(x, R.string.input_parser_invalid_address);
+ }
+ } else if (PATTERN_DASH_IDENTITY.matcher(input).matches()) {
+ try {
+ handlePaymentIntent(PaymentIntent.fromUserId(input));
+ } catch (final AddressFormatException x) {
+ log.info("got invalid dash identity", x);
+
error(x, R.string.input_parser_invalid_address);
}
} else if (PATTERN_DUMPED_PRIVATE_KEY_UNCOMPRESSED.matcher(input).matches()
@@ -390,7 +398,7 @@ public static PaymentIntent parsePaymentRequest(final byte[] serializedPaymentRe
final PaymentIntent paymentIntent = new PaymentIntent(PaymentIntent.Standard.BIP70, pkiName, pkiCaName,
outputs.toArray(new PaymentIntent.Output[0]), memo, paymentUrl, merchantData, null,
- paymentRequestHash);
+ paymentRequestHash, null);
if (paymentIntent.hasPaymentUrl() && !paymentIntent.isSupportedPaymentUrl())
throw new PaymentProtocolException.InvalidPaymentURL(
@@ -447,4 +455,6 @@ public static void dialog(final Context context, @Nullable final OnClickListener
.compile("6P" + "[" + new String(Base58.ALPHABET) + "]{56}");
private static final Pattern PATTERN_TRANSACTION = Pattern
.compile("[0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$\\*\\+\\-\\.\\/\\:]{100,}");
+ private static final Pattern PATTERN_DASH_IDENTITY = Pattern
+ .compile("[" + new String(Base58.ALPHABET) + "]{43,44}");
}
diff --git a/wallet/src/de/schildbach/wallet/ui/SetPinViewModel.kt b/wallet/src/de/schildbach/wallet/ui/SetPinViewModel.kt
index f9eb497037..ad41f17e0e 100644
--- a/wallet/src/de/schildbach/wallet/ui/SetPinViewModel.kt
+++ b/wallet/src/de/schildbach/wallet/ui/SetPinViewModel.kt
@@ -76,6 +76,7 @@ class SetPinViewModel(application: Application) : AndroidViewModel(application)
}
fun initWallet() {
+ walletApplication.saveWalletAndFinalizeInitialization()
startNextActivity.call(walletApplication.configuration.getRemindBackupSeed())
}
diff --git a/wallet/src/de/schildbach/wallet/ui/TransactionDetailsDialogFragment.kt b/wallet/src/de/schildbach/wallet/ui/TransactionDetailsDialogFragment.kt
index 8ff2ef01f1..b2afa73736 100644
--- a/wallet/src/de/schildbach/wallet/ui/TransactionDetailsDialogFragment.kt
+++ b/wallet/src/de/schildbach/wallet/ui/TransactionDetailsDialogFragment.kt
@@ -9,13 +9,18 @@ import android.view.ViewGroup
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import androidx.fragment.app.DialogFragment
+import androidx.lifecycle.Observer
+import de.schildbach.wallet.AppDatabase
import de.schildbach.wallet.WalletApplication
+import de.schildbach.wallet.data.DashPayProfile
+import de.schildbach.wallet.ui.dashpay.PlatformRepo
import de.schildbach.wallet.util.WalletUtils
import de.schildbach.wallet_test.R
import kotlinx.android.synthetic.main.transaction_details_dialog.*
import kotlinx.android.synthetic.main.transaction_result_content.*
import org.bitcoinj.core.Sha256Hash
import org.bitcoinj.core.Transaction
+import org.dashevo.dashpay.BlockchainIdentity
import org.slf4j.LoggerFactory
/**
@@ -46,7 +51,31 @@ class TransactionDetailsDialogFragment : DialogFragment() {
super.onViewCreated(view, savedInstanceState)
tx = wallet.getTransaction(txId)
- val transactionResultViewBinder = TransactionResultViewBinder(transaction_result_container)
+ val blockchainIdentity: BlockchainIdentity? = PlatformRepo.getInstance().getBlockchainIdentity()
+
+ var profile: DashPayProfile? = null
+ var userId: String? = null
+ if (blockchainIdentity != null) {
+ userId = blockchainIdentity.getContactForTransaction(tx!!)
+ if (userId != null) {
+ AppDatabase.getAppDatabase().dashPayProfileDao().loadDistinct(userId).observe(viewLifecycleOwner, Observer {
+ if (it != null) {
+ profile = it
+ finishInitialization(profile)
+ }
+ })
+ }
+ }
+
+ if (blockchainIdentity == null || userId == null)
+ finishInitialization(null)
+
+ view_on_explorer.setOnClickListener { viewOnBlockExplorer() }
+ transaction_close_btn.setOnClickListener { dismissAnimation() }
+ }
+
+ private fun finishInitialization(dashPayProfile: DashPayProfile?) {
+ val transactionResultViewBinder = TransactionResultViewBinder(transaction_result_container, dashPayProfile)
if (tx != null) {
transactionResultViewBinder.bind(tx!!)
} else {
@@ -54,8 +83,6 @@ class TransactionDetailsDialogFragment : DialogFragment() {
dismiss()
return
}
- view_on_explorer.setOnClickListener { viewOnBlockExplorer() }
- transaction_close_btn.setOnClickListener { dismissAnimation() }
showAnimation()
}
diff --git a/wallet/src/de/schildbach/wallet/ui/TransactionResultActivity.kt b/wallet/src/de/schildbach/wallet/ui/TransactionResultActivity.kt
index a8a10fead7..f148b75981 100644
--- a/wallet/src/de/schildbach/wallet/ui/TransactionResultActivity.kt
+++ b/wallet/src/de/schildbach/wallet/ui/TransactionResultActivity.kt
@@ -23,13 +23,23 @@ import android.graphics.drawable.Animatable
import android.os.Bundle
import android.view.View
import androidx.core.content.ContextCompat
+import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModelProvider
+import de.schildbach.wallet.AppDatabase
import de.schildbach.wallet.WalletApplication
+import de.schildbach.wallet.data.DashPayProfile
+import de.schildbach.wallet.data.UsernameSearchResult
+import de.schildbach.wallet.livedata.Resource
+import de.schildbach.wallet.livedata.Status
+import de.schildbach.wallet.ui.dashpay.DashPayViewModel
+import de.schildbach.wallet.ui.dashpay.PlatformRepo
import de.schildbach.wallet.util.WalletUtils
import de.schildbach.wallet_test.R
import kotlinx.android.synthetic.main.activity_successful_transaction.*
import kotlinx.android.synthetic.main.transaction_result_content.*
import org.bitcoinj.core.Sha256Hash
import org.bitcoinj.core.Transaction
+import org.dashevo.dashpay.BlockchainIdentity
import org.slf4j.LoggerFactory
/**
@@ -44,10 +54,16 @@ class TransactionResultActivity : AbstractWalletActivity() {
const val EXTRA_USER_AUTHORIZED_RESULT_EXTRA = "user_authorized_result_extra"
private const val EXTRA_PAYMENT_MEMO = "payee_name"
private const val EXTRA_PAYEE_VERIFIED_BY = "payee_verified_by"
+ private const val EXTRA_USERID = "payee_userid"
@JvmStatic
fun createIntent(context: Context, action: String? = null, transaction: Transaction, userAuthorized: Boolean): Intent {
- return createIntent(context, action, transaction, userAuthorized, null, null)
+ return createIntent(context, action, transaction, userAuthorized, null, null, null)
+ }
+
+ @JvmStatic
+ fun createIntent(context: Context, action: String? = null, transaction: Transaction, userAuthorized: Boolean, userId: String? = null): Intent {
+ return createIntent(context, action, transaction, userAuthorized, null, null, userId)
}
@JvmStatic
@@ -57,17 +73,20 @@ class TransactionResultActivity : AbstractWalletActivity() {
}
fun createIntent(context: Context, action: String?, transaction: Transaction, userAuthorized: Boolean,
- paymentMemo: String? = null, payeeVerifiedBy: String? = null): Intent {
+ paymentMemo: String? = null, payeeVerifiedBy: String? = null, userId: String? = null): Intent {
return Intent(context, TransactionResultActivity::class.java).apply {
setAction(action)
putExtra(EXTRA_TX_ID, transaction.txId)
putExtra(EXTRA_USER_AUTHORIZED_RESULT_EXTRA, userAuthorized)
putExtra(EXTRA_PAYMENT_MEMO, paymentMemo)
putExtra(EXTRA_PAYEE_VERIFIED_BY, payeeVerifiedBy)
+ putExtra(EXTRA_USERID, userId)
}
}
}
+ lateinit var dashPayViewModel: DashPayViewModel
+
@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -75,8 +94,30 @@ class TransactionResultActivity : AbstractWalletActivity() {
val txId = intent.getSerializableExtra(EXTRA_TX_ID) as Sha256Hash
setContentView(R.layout.activity_successful_transaction)
- val transactionResultViewBinder = TransactionResultViewBinder(container)
+ val blockchainIdentity: BlockchainIdentity? = PlatformRepo.getInstance().getBlockchainIdentity()
+
val tx = WalletApplication.getInstance().wallet.getTransaction(txId)
+
+ var profile: DashPayProfile? = null
+ var userId: String? = null
+ if (blockchainIdentity != null) {
+ userId = blockchainIdentity.getContactForTransaction(tx!!)
+ if (userId != null) {
+ AppDatabase.getAppDatabase().dashPayProfileDao().loadDistinct(userId).observe(this, Observer {
+ if (it != null) {
+ profile = it
+ finishInitialization(txId, tx, profile)
+ }
+ })
+ }
+ }
+
+ if (blockchainIdentity == null || userId == null)
+ finishInitialization(txId, tx!!, null)
+ }
+
+ private fun finishInitialization(txId: Sha256Hash, tx: Transaction, dashPayProfile: DashPayProfile?) {
+ val transactionResultViewBinder = TransactionResultViewBinder(container, dashPayProfile)
if (tx != null) {
val payeeName = intent.getStringExtra(EXTRA_PAYMENT_MEMO)
val payeeVerifiedBy = intent.getStringExtra(EXTRA_PAYEE_VERIFIED_BY)
@@ -87,6 +128,17 @@ class TransactionResultActivity : AbstractWalletActivity() {
intent.action == Intent.ACTION_VIEW -> {
finish()
}
+ intent.getStringExtra(EXTRA_USERID) != null -> {
+ finish()
+ val userId = intent.getStringExtra(EXTRA_USERID)
+ dashPayViewModel.getContact(userId)
+ dashPayViewModel.getContactLiveData.observe(this, Observer> {
+ if (it != null && it.status == Status.SUCCESS && it.data != null) {
+ startActivity(DashPayUserActivity.createIntent(this@TransactionResultActivity,
+ it.data.username, it.data.dashPayProfile, it.data.requestSent, it.data.requestReceived))
+ }
+ })
+ }
intent.getBooleanExtra(EXTRA_USER_AUTHORIZED_RESULT_EXTRA, false) -> {
startActivity(MainActivity.createIntent(this))
}
@@ -95,6 +147,7 @@ class TransactionResultActivity : AbstractWalletActivity() {
}
}
}
+
} else {
log.error("Transaction not found. TxId:", txId)
finish()
@@ -107,6 +160,8 @@ class TransactionResultActivity : AbstractWalletActivity() {
check_icon.visibility = View.VISIBLE
(check_icon.drawable as Animatable).start()
}, 400)
+
+ dashPayViewModel = ViewModelProvider(this).get(DashPayViewModel::class.java)
}
private fun viewOnExplorer(tx: Transaction) {
diff --git a/wallet/src/de/schildbach/wallet/ui/TransactionResultViewBinder.kt b/wallet/src/de/schildbach/wallet/ui/TransactionResultViewBinder.kt
index 5a3f91d75a..c1e5f368f6 100644
--- a/wallet/src/de/schildbach/wallet/ui/TransactionResultViewBinder.kt
+++ b/wallet/src/de/schildbach/wallet/ui/TransactionResultViewBinder.kt
@@ -22,9 +22,12 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
+import com.bumptech.glide.Glide
import de.schildbach.wallet.Constants
import de.schildbach.wallet.WalletApplication
+import de.schildbach.wallet.data.DashPayProfile
import de.schildbach.wallet.util.*
import de.schildbach.wallet_test.R
import org.bitcoinj.core.Address
@@ -34,7 +37,7 @@ import org.dash.wallet.common.ui.CurrencyTextView
/**
* @author Samuel Barbosa
*/
-class TransactionResultViewBinder(private val containerView: View) {
+class TransactionResultViewBinder(private val containerView: View, private val profile: DashPayProfile?) {
private val ctx by lazy { containerView.context }
private val checkIcon by lazy { containerView.findViewById(R.id.check_icon) }
@@ -65,6 +68,10 @@ class TransactionResultViewBinder(private val containerView: View) {
private val paymentMemoContainer by lazy { containerView.findViewById(R.id.payment_memo_container) }
private val payeeSecuredByContainer by lazy { containerView.findViewById(R.id.payee_verified_by_container) }
private val payeeSecuredBy by lazy { containerView.findViewById(R.id.payee_secured_by) }
+ private val sendToUserContainer by lazy { containerView.findViewById(R.id.user_container) }
+ private val userLabel by lazy { containerView.findViewById(R.id.user_label) }
+ private val userAvatar by lazy { containerView.findViewById(R.id.avatar) }
+ private val userDisplayName by lazy { containerView.findViewById(R.id.displayname) }
fun bind(tx: Transaction, payeeName: String? = null, payeeSecuredBy: String? = null) {
val noCodeFormat = WalletApplication.getInstance().configuration.format.noCode()
@@ -187,6 +194,31 @@ class TransactionResultViewBinder(private val containerView: View) {
}
}
+ // handle dashpay
+
+ if (profile != null) {
+ sendToUserContainer.visibility = View.VISIBLE
+ inputsContainer.visibility = View.GONE
+ outputsContainer.visibility = View.GONE
+ userLabel.text = ctx.getString(if (tx.getValue(wallet).isNegative) R.string.transaction_details_sent_to else R.string.transaction_details_received_from)
+
+ userDisplayName.text = if (profile.displayName.isNotEmpty())
+ profile.displayName
+ else
+ profile.username
+
+ val defaultAvatar = UserAvatarPlaceholderDrawable.getDrawable(ctx!!, profile.username[0])
+
+ if (profile.avatarUrl.isNotEmpty()) {
+ Glide.with(userAvatar).load(profile.avatarUrl).circleCrop()
+ .placeholder(defaultAvatar).into(userAvatar)
+ } else {
+ userAvatar.background = defaultAvatar
+ }
+ } else {
+ sendToUserContainer.visibility = View.GONE
+ }
+
setTransactionDirection(tx)
}
diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/ContactsFragment.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/ContactsFragment.kt
index f7561ef701..3b19c29809 100644
--- a/wallet/src/de/schildbach/wallet/ui/dashpay/ContactsFragment.kt
+++ b/wallet/src/de/schildbach/wallet/ui/dashpay/ContactsFragment.kt
@@ -33,13 +33,19 @@ import androidx.recyclerview.widget.LinearLayoutManager
import de.schildbach.wallet.AppDatabase
import de.schildbach.wallet.WalletApplication
import de.schildbach.wallet.data.DashPayContactRequest
+import de.schildbach.wallet.data.PaymentIntent
import de.schildbach.wallet.data.UsernameSearchResult
import de.schildbach.wallet.data.UsernameSortOrderBy
import de.schildbach.wallet.livedata.Resource
import de.schildbach.wallet.livedata.Status
import de.schildbach.wallet.ui.DashPayUserActivity
-import de.schildbach.wallet.ui.SearchUserActivity
+import de.schildbach.wallet.ui.InputParser
+import de.schildbach.wallet.ui.send.SendCoinsInternalActivity
import de.schildbach.wallet.ui.setupActionBarWithTitle
+import org.bitcoinj.core.PrefixedChecksummedBytes
+import org.bitcoinj.core.Transaction
+import org.bitcoinj.core.VerificationException
+import de.schildbach.wallet.ui.SearchUserActivity
import de.schildbach.wallet_test.R
import kotlinx.android.synthetic.main.contacts_empty_state_layout.*
import kotlinx.android.synthetic.main.contacts_list_layout.*
@@ -73,7 +79,6 @@ class ContactsFragment : Fragment(R.layout.fragment_contacts_root), TextWatcher,
private lateinit var searchContactsRunnable: Runnable
private lateinit var contactsAdapter: ContactSearchResultsAdapter
private var query = ""
- private var blockchainIdentityId: String? = null
private var direction = UsernameSortOrderBy.USERNAME
private val mode by lazy { requireArguments().getInt(EXTRA_MODE, MODE_SEARCH_CONTACTS) }
private var currentPosition = -1
@@ -139,14 +144,6 @@ class ContactsFragment : Fragment(R.layout.fragment_contacts_root), TextWatcher,
}
}
})
- AppDatabase.getAppDatabase().blockchainIdentityDataDao().load().observe(viewLifecycleOwner, Observer {
- if (it != null) {
- //TODO: we don't have an easy way of getting the identity id (userId)
- val tx = walletApplication.wallet.getTransaction(it.creditFundingTxId)
- val cftx = walletApplication.wallet.getCreditFundingTransaction(tx)
- blockchainIdentityId = cftx.creditBurnIdentityIdentifier.toStringBase58()
- }
- })
dashPayViewModel.getContactRequestLiveData.observe(viewLifecycleOwner, Observer> { it ->
if (it != null && currentPosition != -1) {
@@ -204,9 +201,11 @@ class ContactsFragment : Fragment(R.layout.fragment_contacts_root), TextWatcher,
private fun processResults(data: List) {
val results = ArrayList()
// process the requests
- val requests = if (mode != MODE_SELECT_CONTACT)
+ val requests = if (mode != MODE_SELECT_CONTACT) {
data.filter { r -> r.isPendingRequest }.toMutableList()
- else ArrayList()
+ } else {
+ ArrayList()
+ }
val requestCount = requests.size
if (mode != MODE_VIEW_REQUESTS) {
@@ -215,7 +214,7 @@ class ContactsFragment : Fragment(R.layout.fragment_contacts_root), TextWatcher,
}
}
- if (requests.isNotEmpty() && mode != MODE_VIEW_REQUESTS)
+ if (requests.isNotEmpty() && mode == MODE_SEARCH_CONTACTS)
results.add(ContactSearchResultsAdapter.ViewItem(null, ContactSearchResultsAdapter.CONTACT_REQUEST_HEADER, requestCount = requestCount))
requests.forEach { r -> results.add(ContactSearchResultsAdapter.ViewItem(r, ContactSearchResultsAdapter.CONTACT_REQUEST)) }
@@ -224,7 +223,7 @@ class ContactsFragment : Fragment(R.layout.fragment_contacts_root), TextWatcher,
data.filter { r -> r.requestSent && r.requestReceived }
else ArrayList()
- if (contacts.isNotEmpty())
+ if (contacts.isNotEmpty() && mode != MODE_VIEW_REQUESTS)
results.add(ContactSearchResultsAdapter.ViewItem(null, ContactSearchResultsAdapter.CONTACT_HEADER))
contacts.forEach { r -> results.add(ContactSearchResultsAdapter.ViewItem(r, ContactSearchResultsAdapter.CONTACT)) }
@@ -269,19 +268,15 @@ class ContactsFragment : Fragment(R.layout.fragment_contacts_root), TextWatcher,
}
override fun onItemClicked(view: View, usernameSearchResult: UsernameSearchResult) {
- when {
- usernameSearchResult.isPendingRequest -> {
- startActivity(DashPayUserActivity.createIntent(requireContext(),
- usernameSearchResult.username, usernameSearchResult.dashPayProfile, contactRequestSent = false,
- contactRequestReceived = true))
-
- }
- !usernameSearchResult.isPendingRequest -> {
- // How do we handle if this activity was started from the Payments Screen?
+ when (mode) {
+ MODE_SEARCH_CONTACTS, MODE_VIEW_REQUESTS -> {
startActivity(DashPayUserActivity.createIntent(requireContext(),
usernameSearchResult.username, usernameSearchResult.dashPayProfile, contactRequestSent = usernameSearchResult.requestSent,
contactRequestReceived = usernameSearchResult.requestReceived))
}
+ MODE_SELECT_CONTACT -> {
+ handleString(usernameSearchResult.toContactRequest!!.toUserId, true, R.string.scan_to_pay_username_dialog_message)
+ }
}
}
@@ -302,4 +297,34 @@ class ContactsFragment : Fragment(R.layout.fragment_contacts_root), TextWatcher,
}
}
+ private fun handleString(input: String, fireAction: Boolean, errorDialogTitleResId: Int) {
+ object : InputParser.StringInputParser(input, true) {
+
+ override fun handlePaymentIntent(paymentIntent: PaymentIntent) {
+ if (fireAction) {
+ SendCoinsInternalActivity.start(context, paymentIntent, true)
+ } else {
+
+ }
+ }
+
+ override fun error(ex: Exception?, messageResId: Int, vararg messageArgs: Any) {
+ if (fireAction) {
+ dialog(context, null, errorDialogTitleResId, messageResId, *messageArgs)
+ } else {
+
+ }
+ }
+
+ override fun handlePrivateKey(key: PrefixedChecksummedBytes) {
+ // ignore
+ }
+
+ @Throws(VerificationException::class)
+ override fun handleDirectTransaction(tx: Transaction) {
+ // ignore
+ }
+ }.parse()
+ }
+
}
diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt
index 864a54ce2d..835cb59864 100644
--- a/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt
+++ b/wallet/src/de/schildbach/wallet/ui/dashpay/CreateIdentityService.kt
@@ -317,6 +317,8 @@ class CreateIdentityService : LifecycleService() {
platformRepo.updateCreationState(blockchainIdentityData, CreationState.DONE)
}
+ PlatformRepo.getInstance().init()
+
// aaaand we're done :)
log.info("username registration complete")
}
@@ -392,6 +394,7 @@ class CreateIdentityService : LifecycleService() {
// Complete the entire process
platformRepo.updateCreationState(blockchainIdentityData, CreationState.DONE_AND_DISMISS)
+ PlatformRepo.getInstance().init()
}
/**
diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/DashPayViewModel.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/DashPayViewModel.kt
index 1137d0e28a..46b8459bab 100644
--- a/wallet/src/de/schildbach/wallet/ui/dashpay/DashPayViewModel.kt
+++ b/wallet/src/de/schildbach/wallet/ui/dashpay/DashPayViewModel.kt
@@ -21,6 +21,7 @@ import android.os.Process
import androidx.lifecycle.*
import de.schildbach.wallet.WalletApplication
import de.schildbach.wallet.data.UsernameSearch
+import de.schildbach.wallet.data.UsernameSearchResult
import de.schildbach.wallet.data.UsernameSortOrderBy
import de.schildbach.wallet.livedata.Resource
import de.schildbach.wallet.ui.security.SecurityGuard
@@ -28,6 +29,8 @@ import de.schildbach.wallet.ui.send.DeriveKeyTask
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.bitcoinj.core.Address
import org.bitcoinj.crypto.KeyCrypterException
import org.bouncycastle.crypto.params.KeyParameter
import java.lang.Exception
@@ -40,8 +43,11 @@ class DashPayViewModel(application: Application) : AndroidViewModel(application)
private val usernameLiveData = MutableLiveData()
private val userSearchLiveData = MutableLiveData()
private val contactsLiveData = MutableLiveData()
+ private val contactUserIdLiveData = MutableLiveData()
+
val notificationCountLiveData = NotificationCountLiveData(walletApplication, platformRepo)
val notificationsLiveData = NotificationsLiveData(walletApplication, platformRepo)
+ val notificationsForUserLiveData = NotificationsForUserLiveData(walletApplication, platformRepo)
val contactsUpdatedLiveData = ContactsUpdatedLiveData(walletApplication, platformRepo)
private val contactRequestLiveData = MutableLiveData>()
@@ -50,6 +56,7 @@ class DashPayViewModel(application: Application) : AndroidViewModel(application)
private var searchUsernamesJob = Job()
private var searchContactsJob = Job()
private var contactRequestJob = Job()
+ private var getContactJob = Job()
val getUsernameLiveData = Transformations.switchMap(usernameLiveData) { username ->
getUsernameJob.cancel()
@@ -116,7 +123,11 @@ class DashPayViewModel(application: Application) : AndroidViewModel(application)
notificationsLiveData.searchNotifications(text)
}
- fun getNotificationCount() {
+ fun searchNotificationsForUser(userId: String) {
+ notificationsForUserLiveData.searchNotifications(userId)
+ }
+
+ fun getNotificationCount() {
notificationCountLiveData.getNotificationCount()
}
@@ -132,6 +143,10 @@ class DashPayViewModel(application: Application) : AndroidViewModel(application)
}
}
+ fun getNextContactAddress(userId: String): Address {
+ return platformRepo.getNextContactAddress(userId)
+ }
+
//TODO: this can probably be simplified using coroutines
private fun deriveEncryptionKey(onSuccess: (KeyParameter) -> Unit, onError: (Exception) -> Unit) {
val walletApplication = WalletApplication.getInstance()
@@ -170,4 +185,21 @@ class DashPayViewModel(application: Application) : AndroidViewModel(application)
}
}
+ val getContactLiveData = Transformations.switchMap(contactUserIdLiveData) { userId ->
+ getContactJob.cancel()
+ getContactJob = Job()
+ liveData(context = getContactJob + Dispatchers.IO) {
+ if (userId != null) {
+ emit(Resource.loading(null))
+ emit(Resource.success(platformRepo.getLocalUsernameSearchResult(userId)))
+ } else {
+ emit(Resource.canceled())
+ }
+ }
+ }
+
+ fun getContact(username: String?) {
+ contactUserIdLiveData.value = username
+ }
+
}
\ No newline at end of file
diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsActivity.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsActivity.kt
index 4db3c461c5..0135cafd74 100644
--- a/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsActivity.kt
+++ b/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsActivity.kt
@@ -18,6 +18,7 @@ package de.schildbach.wallet.ui.dashpay
import android.content.Context
import android.content.Intent
+import android.content.res.Resources
import android.os.Bundle
import android.os.Handler
import android.text.Editable
@@ -33,6 +34,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import de.schildbach.wallet.AppDatabase
import de.schildbach.wallet.WalletApplication
import de.schildbach.wallet.data.DashPayContactRequest
+import de.schildbach.wallet.data.NotificationItem
import de.schildbach.wallet.data.UsernameSearchResult
import de.schildbach.wallet.data.UsernameSortOrderBy
import de.schildbach.wallet.livedata.Resource
@@ -70,9 +72,8 @@ class NotificationsActivity : InteractionAwareActivity(), TextWatcher,
private lateinit var walletApplication: WalletApplication
private var handler: Handler = Handler()
private lateinit var searchContactsRunnable: Runnable
- protected val notificationsAdapter: NotificationsAdapter = NotificationsAdapter(this)
+ private lateinit var notificationsAdapter: NotificationsAdapter
private var query = ""
- private var blockchainIdentityId: String? = null
private var direction = UsernameSortOrderBy.DATE_ADDED
private var mode = MODE_NOTIFICATIONS
private var lastSeenNotificationTime = 0L
@@ -84,6 +85,8 @@ class NotificationsActivity : InteractionAwareActivity(), TextWatcher,
walletApplication = application as WalletApplication
lastSeenNotificationTime = walletApplication.configuration.lastSeenNotificationTime
+ notificationsAdapter = NotificationsAdapter(this, walletApplication.wallet, this)
+
if (intent.extras != null && intent.extras!!.containsKey(EXTRA_MODE)) {
mode = intent.extras.getInt(EXTRA_MODE)
}
@@ -127,15 +130,6 @@ class NotificationsActivity : InteractionAwareActivity(), TextWatcher,
}
}
})
- //This is not used
- AppDatabase.getAppDatabase().blockchainIdentityDataDao().load().observe(this, Observer {
- if (it != null) {
- //TODO: we don't have an easy way of getting the identity id (userId)
- val tx = walletApplication.wallet.getTransaction(it.creditFundingTxId)
- val cftx = walletApplication.wallet.getCreditFundingTransaction(tx)
- blockchainIdentityId = cftx.creditBurnIdentityIdentifier.toStringBase58()
- }
- })
dashPayViewModel.getContactRequestLiveData.observe(this, object : Observer> {
override fun onChanged(it: Resource?) {
@@ -153,7 +147,7 @@ class NotificationsActivity : InteractionAwareActivity(), TextWatcher,
}
Status.SUCCESS -> {
// update the data
- notificationsAdapter.results[currentPosition].usernameSearchResult!!.toContactRequest = it.data!!
+ notificationsAdapter.results[currentPosition].notificationItem!!.usernameSearchResult!!.toContactRequest = it.data!!
notificationsAdapter.notifyItemChanged(currentPosition)
currentPosition = -1
lastSeenNotificationTime = it.data.timestamp.toLong() * 1000
@@ -164,19 +158,23 @@ class NotificationsActivity : InteractionAwareActivity(), TextWatcher,
})
}
- private fun getViewType(usernameSearchResult: UsernameSearchResult): Int {
- return when (usernameSearchResult.requestSent to usernameSearchResult.requestReceived) {
- true to true -> {
- NotificationsAdapter.NOTIFICATION_CONTACT_ADDED
- }
- false to true -> {
- NotificationsAdapter.NOTIFICATION_CONTACT_REQUEST_RECEIVED
+ private fun getViewType(notificationItem: NotificationItem): Int {
+ when (notificationItem.type) {
+ NotificationItem.Type.CONTACT_REQUEST,
+ NotificationItem.Type.CONTACT -> return when (notificationItem.usernameSearchResult!!.requestSent to notificationItem.usernameSearchResult.requestReceived) {
+ true to true -> {
+ NotificationsAdapter.NOTIFICATION_CONTACT_ADDED
+ }
+ false to true -> {
+ NotificationsAdapter.NOTIFICATION_CONTACT_REQUEST_RECEIVED
+ }
+ else -> throw IllegalArgumentException("View not supported")
}
- else -> throw IllegalArgumentException("View not supported")
+ NotificationItem.Type.PAYMENT -> throw IllegalStateException()
}
}
- private fun processResults(data: List) {
+ private fun processResults(data: List) {
val results = ArrayList()
diff --git a/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsAdapter.kt b/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsAdapter.kt
index b584bba6b0..3398ca698c 100644
--- a/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsAdapter.kt
+++ b/wallet/src/de/schildbach/wallet/ui/dashpay/NotificationsAdapter.kt
@@ -16,24 +16,41 @@
*/
package de.schildbach.wallet.ui.dashpay
+import android.content.Context
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
+import androidx.cardview.widget.CardView
import androidx.constraintlayout.widget.Guideline
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
+import de.schildbach.wallet.WalletApplication
+import de.schildbach.wallet.data.AddressBookProvider
+import de.schildbach.wallet.data.NotificationItem
import de.schildbach.wallet.data.UsernameSearchResult
import de.schildbach.wallet.ui.UserAvatarPlaceholderDrawable
+import de.schildbach.wallet.util.TransactionUtil
+import de.schildbach.wallet.util.WalletUtils
import de.schildbach.wallet_test.R
import kotlinx.android.synthetic.main.contact_request_row.view.*
+import org.bitcoinj.core.Address
+import org.bitcoinj.core.Coin
+import org.bitcoinj.core.Sha256Hash
+import org.bitcoinj.core.Transaction
+import org.bitcoinj.utils.MonetaryFormat
+import org.bitcoinj.wallet.Wallet
+import org.dash.wallet.common.Constants
+import org.dash.wallet.common.ui.CurrencyTextView
+import org.dash.wallet.common.util.GenericUtils
import org.dashevo.dpp.util.HashUtils
import java.math.BigInteger
+import java.util.*
import kotlin.math.max
-class NotificationsAdapter(val onContactRequestButtonClickListener: OnContactRequestButtonClickListener) : RecyclerView.Adapter() {
+class NotificationsAdapter(val context: Context, val wallet: Wallet, val onContactRequestButtonClickListener: OnContactRequestButtonClickListener) : RecyclerView.Adapter() {
companion object {
const val NOTIFICATION_NEW_HEADER = 4
@@ -41,9 +58,10 @@ class NotificationsAdapter(val onContactRequestButtonClickListener: OnContactReq
const val NOTIFICATION_EARLIER_HEADER = 6
const val NOTIFICATION_CONTACT_ADDED = 7
const val NOTIFICATION_CONTACT_REQUEST_RECEIVED = 8
+ const val NOTIFICATION_PAYMENT = 9
}
- class ViewItem(val usernameSearchResult: UsernameSearchResult?,
+ class ViewItem(val notificationItem: NotificationItem?,
val viewType: Int,
val isNew: Boolean = false)
@@ -51,8 +69,20 @@ class NotificationsAdapter(val onContactRequestButtonClickListener: OnContactReq
fun onItemClicked(view: View, usernameSearchResult: UsernameSearchResult)
}
+ // TransactionViewHolder related items
+ val colorBackground: Int by lazy { context.resources.getColor(R.color.bg_bright) }
+ val colorBackgroundSelected: Int by lazy { context.resources.getColor(R.color.bg_panel) }
+ val colorPrimaryStatus: Int by lazy { context.resources.getColor(R.color.primary_status) }
+ val colorSecondaryStatus: Int by lazy { context.resources.getColor(R.color.secondary_status) }
+ val colorInsignificant: Int by lazy { context.resources.getColor(R.color.fg_insignificant) }
+ val colorValuePositve: Int by lazy { context.resources.getColor(R.color.colorPrimary) }
+ val colorValueNegative: Int by lazy { context.resources.getColor(android.R.color.black) }
+ val colorError: Int by lazy { context.resources.getColor(R.color.fg_error) }
+ private var format: MonetaryFormat? = null
+
init {
setHasStableIds(true)
+ format = WalletApplication.getInstance().configuration.format.noCode()
}
var itemClickListener: OnItemClickListener? = null
@@ -69,6 +99,7 @@ class NotificationsAdapter(val onContactRequestButtonClickListener: OnContactReq
NOTIFICATION_CONTACT_REQUEST_RECEIVED -> ContactRequestViewHolder(LayoutInflater.from(parent.context), parent)
NOTIFICATION_EARLIER_HEADER -> HeaderViewHolder(LayoutInflater.from(parent.context), parent)
NOTIFICATION_CONTACT_ADDED -> ContactViewHolder(LayoutInflater.from(parent.context), parent)
+ NOTIFICATION_PAYMENT -> TransactionViewHolder(LayoutInflater.from(parent.context), parent)
else -> throw IllegalArgumentException("Invalid viewType $viewType")
}
}
@@ -87,9 +118,10 @@ class NotificationsAdapter(val onContactRequestButtonClickListener: OnContactReq
return when (results[position].viewType) {
NOTIFICATION_NEW_HEADER -> 1L
NOTIFICATION_NEW_EMPTY -> 2L
- NOTIFICATION_CONTACT_REQUEST_RECEIVED -> getLongValue(results[position].usernameSearchResult!!.fromContactRequest!!.userId)
+ NOTIFICATION_CONTACT_REQUEST_RECEIVED -> getLongValue(results[position].notificationItem!!.id)
NOTIFICATION_EARLIER_HEADER -> 3L
- NOTIFICATION_CONTACT_ADDED -> getLongValue(results[position].usernameSearchResult!!.toContactRequest!!.toUserId)
+ NOTIFICATION_CONTACT_ADDED -> getLongValue(results[position].notificationItem!!.id)
+ NOTIFICATION_PAYMENT -> getLongValue(results[position].notificationItem!!.id)
else -> throw IllegalArgumentException("Invalid viewType ${results[position].viewType}")
}
}
@@ -97,10 +129,11 @@ class NotificationsAdapter(val onContactRequestButtonClickListener: OnContactReq
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
when (results[position].viewType) {
NOTIFICATION_CONTACT_REQUEST_RECEIVED,
- NOTIFICATION_CONTACT_ADDED -> holder.bind(results[position].usernameSearchResult!!, results[position].isNew)
+ NOTIFICATION_CONTACT_ADDED -> holder.bind(results[position].notificationItem!!.usernameSearchResult!!, results[position].isNew)
NOTIFICATION_NEW_HEADER -> (holder as HeaderViewHolder).bind(R.string.notifications_new)
NOTIFICATION_EARLIER_HEADER -> (holder as HeaderViewHolder).bind(R.string.notifications_earlier)
NOTIFICATION_NEW_EMPTY -> (holder as ImageViewHolder).bind(R.drawable.ic_notification_new_empty, R.string.notifications_none_new)
+ NOTIFICATION_PAYMENT -> (holder as TransactionViewHolder).bind(results[position].notificationItem!!.tx!!)
else -> throw IllegalArgumentException("Invalid viewType ${results[position].viewType}")
}
}
@@ -117,12 +150,9 @@ class NotificationsAdapter(val onContactRequestButtonClickListener: OnContactReq
return results[position].viewType
}
- fun getItemPosition(usernameSearchResult: UsernameSearchResult): Int {
- val viewItem = results.find {
- val usernameSearchResult = it.usernameSearchResult ?: false
- usernameSearchResult == it.usernameSearchResult
- }
- return results.indexOf(viewItem)
+ fun setFormat(format: MonetaryFormat) {
+ this.format = format.noCode()
+ notifyDataSetChanged()
}
open inner class ViewHolder(resId: Int, inflater: LayoutInflater, parent: ViewGroup) :
@@ -132,7 +162,7 @@ class NotificationsAdapter(val onContactRequestButtonClickListener: OnContactReq
private val date by lazy { itemView.findViewById(R.id.date) }
private val displayName by lazy { itemView.findViewById(R.id.displayName) }
private val contactAdded by lazy { itemView.findViewById(R.id.contact_added) }
- private val guildline by lazy { itemView.findViewById(R.id.center_guideline)}
+ private val guildline by lazy { itemView.findViewById(R.id.center_guideline) }
private val dateFormat by lazy { itemView.context.getString(R.string.transaction_row_time_text) }
private fun formatDate(timeStamp: Long): String {
@@ -200,7 +230,6 @@ class NotificationsAdapter(val onContactRequestButtonClickListener: OnContactReq
}
}
-
inner class ContactViewHolder(inflater: LayoutInflater, parent: ViewGroup) :
ViewHolder(R.layout.notification_contact_added_row, inflater, parent) {
@@ -268,4 +297,139 @@ class NotificationsAdapter(val onContactRequestButtonClickListener: OnContactReq
fun onAcceptRequest(usernameSearchResult: UsernameSearchResult, position: Int)
fun onIgnoreRequest(usernameSearchResult: UsernameSearchResult, position: Int)
}
+
+ private val transactionCache: HashMap = HashMap()
+
+ private class TransactionCacheEntry constructor(val value: Coin, val sent: Boolean, val self: Boolean, val showFee: Boolean, val address: Address?,
+ val addressLabel: String?, val type: Transaction.Type)
+
+ inner class TransactionViewHolder(inflater: LayoutInflater, parent: ViewGroup) : ViewHolder(R.layout.notification_transaction_row, inflater, parent) {
+ private val primaryStatusView: TextView = itemView.findViewById(R.id.transaction_row_primary_status) as TextView
+ private val secondaryStatusView: TextView = itemView.findViewById(R.id.transaction_row_secondary_status) as TextView
+ private val timeView: TextView = itemView.findViewById(R.id.transaction_row_time) as TextView
+ private val dashSymbolView: ImageView = itemView.findViewById(R.id.dash_amount_symbol) as ImageView
+ private val valueView: CurrencyTextView = itemView.findViewById(R.id.transaction_row_value) as CurrencyTextView
+ private val signalView: TextView = itemView.findViewById(R.id.transaction_amount_signal) as TextView
+ private val fiatView: CurrencyTextView = itemView.findViewById(R.id.transaction_row_fiat) as CurrencyTextView
+ private val rateNotAvailableView: TextView
+
+ fun bind(tx: Transaction) {
+ if (itemView is CardView) {
+ itemView.setCardBackgroundColor(if (itemView.isActivated()) colorBackgroundSelected else colorBackground)
+ }
+ val confidence = tx.confidence
+ val fee = tx.fee
+ var txCache: TransactionCacheEntry? = transactionCache[tx.txId]
+ if (txCache == null) {
+ val value = tx.getValue(wallet)
+ val sent = value.signum() < 0
+ val self = WalletUtils.isEntirelySelf(tx, wallet)
+ val showFee = sent && fee != null && !fee.isZero
+ val address: Address?
+ address = if (sent) {
+ val addresses = WalletUtils.getToAddressOfSent(tx, wallet)
+ if (addresses.isEmpty()) null else addresses[0]
+ } else {
+ WalletUtils.getWalletAddressOfReceived(tx, wallet)
+ }
+ val addressLabel = if (address != null) AddressBookProvider.resolveLabel(context, address.toBase58()) else null
+ val txType = tx.type
+ txCache = TransactionCacheEntry(value, sent, self, showFee, address, addressLabel, txType)
+ transactionCache.put(tx.txId, txCache)
+ }
+
+ //
+ // Assign the colors of text and values
+ //
+ val primaryStatusColor: Int
+ val secondaryStatusColor: Int
+ val valueColor: Int
+ if (confidence.hasErrors()) {
+ primaryStatusColor = colorError
+ secondaryStatusColor = colorError
+ valueColor = colorError
+ } else {
+ primaryStatusColor = colorPrimaryStatus
+ secondaryStatusColor = colorSecondaryStatus
+ valueColor = if (txCache.sent) colorValueNegative else colorValuePositve
+ }
+
+ //
+ // Set the time. eg. "On at