Created a method that is always listening to changes in the database and finds out when at least one UserAccount involved in a transaction is not present in the user_accounts db table. When such accounts are found they are retrieved in a form of a list and their information is requested to the BitShares nodes through graphenej's NetworkService and saved into the db once a response is received. The updated user account information is automatically displayed in the transactions list because of AAC's ViewModel, LiveData and Room.
This commit is contained in:
parent
496e0ac21f
commit
628b30ce54
10 changed files with 145 additions and 91 deletions
|
@ -10,9 +10,16 @@ import android.os.IBinder
|
|||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||
import cy.agorise.bitsybitshareswallet.viewmodels.UserAccountViewModel
|
||||
import cy.agorise.graphenej.UserAccount
|
||||
import cy.agorise.graphenej.api.ConnectionStatusUpdate
|
||||
import cy.agorise.graphenej.api.android.NetworkService
|
||||
import cy.agorise.graphenej.api.android.RxBus
|
||||
import cy.agorise.graphenej.api.calls.GetAccounts
|
||||
import cy.agorise.graphenej.models.AccountProperties
|
||||
import cy.agorise.graphenej.models.FullAccountDetails
|
||||
import cy.agorise.graphenej.models.HistoryOperationDetail
|
||||
import cy.agorise.graphenej.models.JsonRpcResponse
|
||||
|
@ -23,8 +30,9 @@ import io.reactivex.disposables.Disposable
|
|||
* Class in charge of managing the connection to graphenej's NetworkService
|
||||
*/
|
||||
abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
||||
private val TAG = this.javaClass.simpleName
|
||||
|
||||
private val TAG = "ConnectedActivity"
|
||||
private lateinit var mUserAccountViewModel: UserAccountViewModel
|
||||
|
||||
private val mHandler = Handler()
|
||||
|
||||
|
@ -33,6 +41,8 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
|
||||
private var storedOpCount: Long = -1
|
||||
|
||||
private var missingUserAccounts = ArrayList<UserAccount>()
|
||||
|
||||
/* Network service connection */
|
||||
protected var mNetworkService: NetworkService? = null
|
||||
|
||||
|
@ -52,7 +62,10 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
handleJsonRpcResponse(message)
|
||||
// Payment detection focused responses
|
||||
if (message.error == null) {
|
||||
// if (message.result is List<*> && (message.result as List<*>).size > 0) {
|
||||
if (message.result is List<*> && (message.result as List<*>).size > 0) {
|
||||
if ((message.result as List<*>)[0] is AccountProperties) {
|
||||
handleAccountProperties(message.result as List<AccountProperties>)
|
||||
}
|
||||
// if ((message.result as List<*>)[0] is FullAccountDetails) {
|
||||
// if (message.id == recurrentAccountUpdateId) {
|
||||
// handleAccountDetails((message.result as List<*>)[0] as FullAccountDetails)
|
||||
|
@ -62,7 +75,7 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
// }
|
||||
// } else if (message.result is HistoryOperationDetail && message.id == accountOpRequestId) {
|
||||
// handleNewOperations(message.result as HistoryOperationDetail)
|
||||
// }
|
||||
}
|
||||
} else {
|
||||
// In case of error
|
||||
Log.e(TAG, "Got error message from full node. Msg: " + message.error.message)
|
||||
|
@ -81,36 +94,47 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configure UserAccountViewModel to show the current account
|
||||
mUserAccountViewModel = ViewModelProviders.of(this).get(UserAccountViewModel::class.java)
|
||||
|
||||
mUserAccountViewModel.getMissingUserAccountIds().observe(this, Observer<List<String>>{ userAccountIds ->
|
||||
if (!userAccountIds.isEmpty()) {
|
||||
for (userAccountId in userAccountIds)
|
||||
missingUserAccounts.add(UserAccount(userAccountId))
|
||||
|
||||
mHandler.postDelayed(mRequestMissingUserAccountsTask, Constants.NETWORK_SERVICE_RETRY_PERIOD)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (!mDisposable!!.isDisposed) mDisposable!!.dispose()
|
||||
}
|
||||
private fun handleAccountProperties(accountPropertiesList: List<AccountProperties>) {
|
||||
val userAccounts = ArrayList<cy.agorise.bitsybitshareswallet.database.entities.UserAccount>()
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
for (accountProperties in accountPropertiesList) {
|
||||
val userAccount = cy.agorise.bitsybitshareswallet.database.entities.UserAccount(
|
||||
accountProperties.id,
|
||||
accountProperties.name,
|
||||
accountProperties.membership_expiration_date == Constants.LIFETIME_EXPIRATION_DATE
|
||||
)
|
||||
|
||||
val intent = Intent(this, NetworkService::class.java)
|
||||
if (bindService(intent, this, Context.BIND_AUTO_CREATE)) {
|
||||
mShouldUnbindNetwork = true
|
||||
} else {
|
||||
Log.e(TAG, "Binding to the network service failed.")
|
||||
userAccounts.add(userAccount)
|
||||
}
|
||||
// mHandler.postDelayed(mCheckMissingPaymentsTask, Constants.MISSING_PAYMENT_CHECK_PERIOD)
|
||||
|
||||
// storedOpCount = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
// .getLong(Constants.KEY_ACCOUNT_OPERATION_COUNT, -1)
|
||||
mUserAccountViewModel.insertAll(userAccounts)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
// Unbinding from network service
|
||||
if (mShouldUnbindNetwork) {
|
||||
unbindService(this)
|
||||
mShouldUnbindNetwork = false
|
||||
/**
|
||||
* Task used to obtain the missing UserAccounts.
|
||||
*/
|
||||
private val mRequestMissingUserAccountsTask = object : Runnable {
|
||||
override fun run() {
|
||||
if (mNetworkService!!.isConnected) {
|
||||
mNetworkService!!.sendMessage(GetAccounts(missingUserAccounts), GetAccounts.REQUIRED_API)
|
||||
} else {
|
||||
mHandler.postDelayed(this, Constants.NETWORK_SERVICE_RETRY_PERIOD)
|
||||
}
|
||||
}
|
||||
// mHandler.removeCallbacks(mCheckMissingPaymentsTask)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -146,7 +170,37 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
mNetworkService = binder.service
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
override fun onServiceDisconnected(name: ComponentName?) { }
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
// Unbinding from network service
|
||||
if (mShouldUnbindNetwork) {
|
||||
unbindService(this)
|
||||
mShouldUnbindNetwork = false
|
||||
}
|
||||
// mHandler.removeCallbacks(mCheckMissingPaymentsTask)
|
||||
mHandler.removeCallbacks(mRequestMissingUserAccountsTask)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
val intent = Intent(this, NetworkService::class.java)
|
||||
if (bindService(intent, this, Context.BIND_AUTO_CREATE)) {
|
||||
mShouldUnbindNetwork = true
|
||||
} else {
|
||||
Log.e(TAG, "Binding to the network service failed.")
|
||||
}
|
||||
// mHandler.postDelayed(mCheckMissingPaymentsTask, Constants.MISSING_PAYMENT_CHECK_PERIOD)
|
||||
|
||||
// storedOpCount = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
// .getLong(Constants.KEY_ACCOUNT_OPERATION_COUNT, -1)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (!mDisposable!!.isDisposed) mDisposable!!.dispose()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -88,8 +88,8 @@ class TransfersDetailsAdapter(private val context: Context) :
|
|||
if(transferDetail.direction) R.color.colorReceive else R.color.colorSend
|
||||
))
|
||||
|
||||
viewHolder.tvFrom.text = transferDetail.from
|
||||
viewHolder.tvTo.text = transferDetail.to
|
||||
viewHolder.tvFrom.text = transferDetail.from ?: ""
|
||||
viewHolder.tvTo.text = transferDetail.to ?: ""
|
||||
|
||||
viewHolder.tvDate.text = "02 Oct"
|
||||
viewHolder.tvTime.text = "15:01:18 CET"
|
||||
|
@ -104,7 +104,7 @@ class TransfersDetailsAdapter(private val context: Context) :
|
|||
Math.pow(10.toDouble(), transferDetail.cryptoPrecision.toDouble())
|
||||
val cryptoAmount = "${df.format(amount)} ${transferDetail.cryptoSymbol}"
|
||||
viewHolder.tvCryptoAmount.text = cryptoAmount
|
||||
|
||||
|
||||
viewHolder.tvFiatEquivalent.text = "$4119.75"
|
||||
|
||||
viewHolder.ivDirectionArrow.setImageDrawable(context.getDrawable(
|
||||
|
|
|
@ -3,6 +3,7 @@ package cy.agorise.bitsybitshareswallet.database.daos
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import cy.agorise.bitsybitshareswallet.database.entities.UserAccount
|
||||
|
||||
|
@ -11,9 +12,16 @@ interface UserAccountDao {
|
|||
@Insert
|
||||
fun insert(userAccount: UserAccount)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertAll(userAccounts: List<UserAccount>)
|
||||
|
||||
@Query("SELECT * FROM user_accounts WHERE user_accounts.id = :id")
|
||||
fun getUserAccount(id: String): LiveData<UserAccount>
|
||||
|
||||
@Query("SELECT * FROM user_accounts")
|
||||
fun getAllUserAccounts(): LiveData<List<UserAccount>>
|
||||
fun getAll(): LiveData<List<UserAccount>>
|
||||
|
||||
// TODO not sure if this is the best place for this query as it involves two entities
|
||||
@Query("SELECT DISTINCT destination FROM transfers WHERE destination NOT IN (SELECT id FROM user_accounts) UNION SELECT DISTINCT source FROM transfers WHERE source NOT IN (SELECT id FROM user_accounts)")
|
||||
fun getMissingAccountIds(): LiveData<List<String>>
|
||||
}
|
|
@ -2,8 +2,8 @@ package cy.agorise.bitsybitshareswallet.database.joins
|
|||
|
||||
data class TransferDetail(
|
||||
val id: String,
|
||||
val from: String,
|
||||
val to: String,
|
||||
val from: String?,
|
||||
val to: String?,
|
||||
val direction: Boolean, // True -> Received, False -> Sent
|
||||
// val date: Long,
|
||||
val cryptoAmount: Long,
|
||||
|
|
|
@ -7,6 +7,6 @@ import androidx.room.Query
|
|||
@Dao
|
||||
interface TransferDetailDao {
|
||||
|
||||
@Query("SELECT transfers.id, IFNULL((SELECT name FROM user_accounts WHERE user_accounts.id=transfers.source), '') AS `from`, IFNULL((SELECT name FROM user_accounts WHERE user_accounts.id=transfers.destination), '') AS `to`, (CASE WHEN destination=:userId THEN 1 ELSE 0 END) AS `direction`, transfers.transfer_amount AS `cryptoAmount`, assets.precision AS `cryptoPrecision`, assets.symbol AS cryptoSymbol FROM transfers INNER JOIN assets WHERE transfers.transfer_asset_id = assets.id")
|
||||
@Query("SELECT transfers.id, (SELECT name FROM user_accounts WHERE user_accounts.id=transfers.source) AS `from`, (SELECT name FROM user_accounts WHERE user_accounts.id=transfers.destination) AS `to`, (CASE WHEN destination=:userId THEN 1 ELSE 0 END) AS `direction`, transfers.transfer_amount AS `cryptoAmount`, assets.precision AS `cryptoPrecision`, assets.symbol AS cryptoSymbol FROM transfers INNER JOIN assets WHERE transfers.transfer_asset_id = assets.id")
|
||||
fun getAll(userId: String): LiveData<List<TransferDetail>>
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
//package cy.agorise.bitsybitshareswallet.processors
|
||||
//
|
||||
//import android.content.Context
|
||||
//import androidx.lifecycle.Lifecycle
|
||||
//import cy.agorise.graphenej.api.android.RxBus
|
||||
//import cy.agorise.graphenej.api.calls.GetAccounts
|
||||
//import cy.agorise.graphenej.entities.AccountProperties
|
||||
//import cy.agorise.graphenej.entities.JsonRpcResponse
|
||||
//import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
//
|
||||
///**
|
||||
// * Loader used to fetch the missing accounts and update the database
|
||||
// */
|
||||
//class MissingAccountsLoader(context: Context, lifecycle: Lifecycle) : BaseDataLoader(context, lifecycle) {
|
||||
//
|
||||
// init {
|
||||
// mDisposable = RxBus.getBusInstance()
|
||||
// .asFlowable()
|
||||
// .observeOn(AndroidSchedulers.mainThread())
|
||||
// .subscribe { message ->
|
||||
// if (message is JsonRpcResponse<*>) {
|
||||
// if (message.result is List<*> &&
|
||||
// (message.result as List<*>).size > 0 &&
|
||||
// (message.result as List<*>)[0] is AccountProperties
|
||||
// ) {
|
||||
// database.putUserAccounts(message.result as List<AccountProperties>)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// protected fun onNetworkReady() {
|
||||
// requestAccountInfo()
|
||||
// }
|
||||
//
|
||||
// fun requestAccountInfo() {
|
||||
// val missingAccountNames = database.getMissingAccountNames()
|
||||
// if (missingAccountNames.size > 0) {
|
||||
// mNetworkService.sendMessage(GetAccounts(missingAccountNames), GetAccounts.REQUIRED_API)
|
||||
// }
|
||||
// }
|
||||
//}
|
|
@ -187,18 +187,21 @@ class TransfersLoader(private var mContext: Context?, private val mLifeCycle: Li
|
|||
// If we are in debug mode, we first erase all entries in the 'transfer' table
|
||||
transferRepository!!.deleteAll()
|
||||
}
|
||||
mDisposables.add(transferRepository!!.getCount()
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { transferCount ->
|
||||
if (transferCount > 0) {
|
||||
// If we already have some transfers in the database, we might want to skip the request
|
||||
// straight to the last batch
|
||||
historicalTransferCount = Math.floor((transferCount / HISTORICAL_TRANSFER_BATCH_SIZE).toDouble()).toInt()
|
||||
mDisposables.add(
|
||||
transferRepository!!.getCount()
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { transferCount ->
|
||||
if (transferCount > 0) {
|
||||
// If we already have some transfers in the database, we might want to skip the request
|
||||
// straight to the last batch
|
||||
historicalTransferCount = Math.floor((transferCount /
|
||||
HISTORICAL_TRANSFER_BATCH_SIZE).toDouble()).toInt()
|
||||
}
|
||||
// Retrieving account transactions
|
||||
loadNextOperationsBatch()
|
||||
}
|
||||
// Retrieving account transactions
|
||||
loadNextOperationsBatch()
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -213,6 +216,7 @@ class TransfersLoader(private var mContext: Context?, private val mLifeCycle: Li
|
|||
historicalTransferCount++
|
||||
|
||||
val insertedCount = transferRepository!!.insertAll(processOperationList(operationHistoryList))
|
||||
// TODO return number of inserted rows
|
||||
// Log.d(TAG, String.format("Inserted count: %d, list size: %d", insertedCount, operationHistoryList.size))
|
||||
if (/* insertedCount == 0 && */ operationHistoryList.isEmpty()) {
|
||||
// TODO Terminate process and obtain MissingTimes and MissingEquivalentValues
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
package cy.agorise.bitsybitshareswallet.repositories
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.os.AsyncTask
|
||||
import androidx.lifecycle.LiveData
|
||||
import cy.agorise.bitsybitshareswallet.database.BitsyDatabase
|
||||
import cy.agorise.bitsybitshareswallet.database.daos.UserAccountDao
|
||||
import cy.agorise.bitsybitshareswallet.database.entities.UserAccount
|
||||
|
||||
class UserAccountRepository internal constructor(application: Application) {
|
||||
class UserAccountRepository internal constructor(context: Context) {
|
||||
|
||||
private val mUserAccountDao: UserAccountDao
|
||||
|
||||
init {
|
||||
val db = BitsyDatabase.getDatabase(application)
|
||||
val db = BitsyDatabase.getDatabase(context)
|
||||
mUserAccountDao = db!!.userAccountDao()
|
||||
}
|
||||
|
||||
|
@ -20,10 +20,18 @@ class UserAccountRepository internal constructor(application: Application) {
|
|||
insertAsyncTask(mUserAccountDao).execute(userAccount)
|
||||
}
|
||||
|
||||
fun insertAll(userAccounts: List<UserAccount>) {
|
||||
insertAllAsyncTask(mUserAccountDao).execute(userAccounts)
|
||||
}
|
||||
|
||||
fun getUserAccount(id: String): LiveData<UserAccount> {
|
||||
return mUserAccountDao.getUserAccount(id)
|
||||
}
|
||||
|
||||
fun getMissingUserAccountIds(): LiveData<List<String>> {
|
||||
return mUserAccountDao.getMissingAccountIds()
|
||||
}
|
||||
|
||||
private class insertAsyncTask internal constructor(private val mAsyncTaskDao: UserAccountDao) :
|
||||
AsyncTask<UserAccount, Void, Void>() {
|
||||
|
||||
|
@ -32,4 +40,13 @@ class UserAccountRepository internal constructor(application: Application) {
|
|||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private class insertAllAsyncTask internal constructor(private val mAsyncTaskDao: UserAccountDao) :
|
||||
AsyncTask<List<UserAccount>, Void, Void>() {
|
||||
|
||||
override fun doInBackground(vararg userAccounts: List<UserAccount>): Void? {
|
||||
mAsyncTaskDao.insertAll(userAccounts[0])
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -39,10 +39,15 @@ object Constants {
|
|||
const val LIFETIME_EXPIRATION_DATE = "1969-12-31T23:59:59"
|
||||
|
||||
/**
|
||||
* Smartcoin options for output
|
||||
* Time period between two consecutive requests to the full node performed whenever we have
|
||||
* open payment requests as a matter of redundancy.
|
||||
*/
|
||||
val BTS = Asset("1.3.0")
|
||||
val bitUSD = Asset("1.3.121")
|
||||
const val MISSING_PAYMENT_CHECK_PERIOD: Long = 5000
|
||||
|
||||
/**
|
||||
* Time period to wait to send a request to the NetworkService, and retry in case it is still not connected
|
||||
*/
|
||||
const val NETWORK_SERVICE_RETRY_PERIOD: Long = 5000
|
||||
|
||||
/** Key used to store the night mode setting into the shared preferences */
|
||||
const val KEY_NIGHT_MODE_ACTIVATED = "key_night_mode_activated"
|
||||
|
|
|
@ -13,7 +13,15 @@ class UserAccountViewModel(application: Application) : AndroidViewModel(applicat
|
|||
return mRepository.getUserAccount(id)
|
||||
}
|
||||
|
||||
internal fun getMissingUserAccountIds(): LiveData<List<String>> {
|
||||
return mRepository.getMissingUserAccountIds()
|
||||
}
|
||||
|
||||
// fun insert(userAccount: UserAccount) {
|
||||
// mRepository.insert(userAccount)
|
||||
// }
|
||||
|
||||
fun insertAll(userAccounts: List<UserAccount>) {
|
||||
mRepository.insertAll(userAccounts)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue