Created a DatabaseLoadActivity which loads basic information of all the BitShares assets and stores them in the database. This process only occurs once during the initial setup.

This commit is contained in:
Severiano Jaramillo 2018-11-26 15:11:34 -06:00
parent d42050afa1
commit c63388155a
11 changed files with 347 additions and 14 deletions

View file

@ -21,14 +21,15 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".activities.LicenseActivity"/>
<activity android:name=".activities.DatabaseLoadActivity"/>
<activity android:name=".activities.ImportBrainkeyActivity"/>
<activity android:name=".activities.MainActivity"/>
<activity
android:name=".activities.SettingsActivity"
android:label="@string/title_settings"/>
<activity android:name=".activities.SendTransactionActivity"/>
<activity android:name=".activities.ReceiveTransactionActivity"/>
<activity android:name=".activities.MainActivity"/>
<activity android:name=".activities.LicenseActivity"/>
<activity android:name=".activities.ImportBrainkeyActivity"/>
</application>
</manifest>

View file

@ -72,13 +72,13 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
Toast.LENGTH_LONG
).show()
}
// } else if (message is ConnectionStatusUpdate) {
// handleConnectionStatusUpdate(message)
// if (message.updateCode == ConnectionStatusUpdate.DISCONNECTED) {
} else if (message is ConnectionStatusUpdate) {
handleConnectionStatusUpdate(message)
if (message.updateCode == ConnectionStatusUpdate.DISCONNECTED) {
// recurrentAccountUpdateId = -1
// accountOpRequestId = -1
// isProcessingTx = false
// }
}
}
}
}

View file

@ -0,0 +1,181 @@
package cy.agorise.bitsybitshareswallet.activities
import android.content.ComponentName
import android.content.Intent
import android.os.Bundle
import android.os.CountDownTimer
import android.os.Handler
import android.os.IBinder
import android.preference.PreferenceManager
import android.util.Log
import android.view.View
import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.repositories.AssetRepository
import cy.agorise.bitsybitshareswallet.utils.Constants
import cy.agorise.graphenej.Asset
import cy.agorise.graphenej.api.ApiAccess
import cy.agorise.graphenej.api.ConnectionStatusUpdate
import cy.agorise.graphenej.api.calls.ListAssets
import cy.agorise.graphenej.models.JsonRpcResponse
import kotlinx.android.synthetic.main.activity_database_load.*
class DatabaseLoadActivity: ConnectedActivity() {
private val TAG = "DatabaseLoadActivity"
/** Time in milliseconds to wait before re-trying to access the full node */
private val NETWORK_RETRY_PERIOD: Long = 1000
/** Handler instance used to schedule tasks back to the main thread */
private var mHandler: Handler? = null
/** Timer used to avoid multiple instances of the following activity */
private var countDownTimer: CountDownTimer? = null
/** Repository used as the single point of truth for Assets */
private var mAssetRepository: AssetRepository? = null
// Variable used to keep track of the last lower bound used int asset batch loading
private var lastLowerBound: String? = null
// Variable used to keep track of the possession state of the database api id
private var hasDatabaseApiId: Boolean = false
// Variable used to count the number of assets already loaded
private var loadedAssetsCounter: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_database_load)
mHandler = Handler()
mAssetRepository = AssetRepository(application)
btnNext.setOnClickListener { onNext() }
}
override fun handleJsonRpcResponse(response: JsonRpcResponse<*>) {
if (response.result is List<*> &&
(response.result as List<*>).size > 0 &&
(response.result as List<*>)[0] is Asset) {
handlePlatformAssetBatch(response.result as List<Asset>)
}
}
override fun handleConnectionStatusUpdate(connectionStatusUpdate: ConnectionStatusUpdate) {
if (connectionStatusUpdate.updateCode == ConnectionStatusUpdate.API_UPDATE &&
connectionStatusUpdate.api and ApiAccess.API_DATABASE == ApiAccess.API_DATABASE) {
hasDatabaseApiId = true
this.lastLowerBound = ""
sendAssetBatchRequest()
}
}
/**
* Method that issues a request for the next 100 known assets, starting from the last known
* lower bound for the asset symbol.
*/
private fun sendAssetBatchRequest() {
if (mNetworkService != null && mNetworkService!!.isConnected) {
mNetworkService!!.sendMessage(ListAssets(lastLowerBound, ListAssets.LIST_ALL), ListAssets.REQUIRED_API)
} else {
Handler().postDelayed({ sendAssetBatchRequest() }, NETWORK_RETRY_PERIOD)
}
}
/**
* Method that loads a new batch of platform assets into the database and decides whether to finish
* the procedure or to keep requesting for more assets.
*
* @param assetList The list of assets obtained in the last 'list_assets' API call.
*/
private fun handlePlatformAssetBatch(assetList: List<Asset>) {
val assets = mutableListOf<cy.agorise.bitsybitshareswallet.models.Asset>()
// TODO find if there is a better way to convert to Bitsy Asset instances
for (_asset in assetList) {
val asset = cy.agorise.bitsybitshareswallet.models.Asset(
_asset.objectId,
_asset.symbol,
_asset.precision,
_asset.description ?: "",
_asset.bitassetId ?: ""
)
assets.add(asset)
}
mAssetRepository!!.insertAll(assets)
loadedAssetsCounter += assetList.size
tvLoadMessage.text = getString(R.string.text__loading_assets, loadedAssetsCounter)
if (assetList.size < ListAssets.MAX_BATCH_SIZE) {
// We might have reached the end of the asset list
Log.d(TAG, "We might have reached the end!")
// Storing the last asset update time and setting the database as loaded
PreferenceManager.getDefaultSharedPreferences(applicationContext)
.edit()
.putLong(Constants.KEY_LAST_ASSET_LIST_UPDATE, System.currentTimeMillis())
.apply()
onAssetsReady()
} else {
// Using the last asset symbol in the list as the new lower bound.
lastLowerBound = assetList[assetList.size - 1].symbol
sendAssetBatchRequest()
}
}
private fun onAssetsReady() {
// Storing the last asset update time and setting the database as loaded
PreferenceManager.getDefaultSharedPreferences(applicationContext)
.edit()
.putBoolean(Constants.KEY_DATABASE_LOADED, true)
.apply()
mHandler!!.post {
progressBar.visibility = View.INVISIBLE
btnNext.isEnabled = true
tvLoadTitle.setText(R.string.title__assets_loaded)
tvLoadMessage.setText(R.string.text__assets_loaded)
// Timer to automatically take user to the next activity
countDownTimer = object : CountDownTimer(5000, 1000) {
override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() {
onNext()
}
}.start()
}
}
/**
* Called whenever the user clicks on the 'next' button_light. This button_light will only be visible when
* the database loading procedure is done, OR if there was an error in it.
*/
fun onNext() {
// Cancel timer to avoid starting InitialSetupActivity twice
countDownTimer!!.cancel()
val intent = Intent(applicationContext, ImportBrainkeyActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
// overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left)
finish()
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
super.onServiceConnected(name, service)
hasDatabaseApiId = mNetworkService!!.hasApiId(ApiAccess.API_DATABASE)
if (hasDatabaseApiId) {
this.lastLowerBound = ""
sendAssetBatchRequest()
}
}
}

View file

@ -32,7 +32,8 @@ class LicenseActivity : AppCompatActivity() {
/**
* This function stores the version of the current accepted license version into the Shared Preferences and
* sends the user to either import/create account if there is no active account or to the MainActivity otherwise
* sends the user to load the assets database if they have not been loaded, import/create account if there is no
* active account or to the MainActivity otherwise.
*/
private fun agree() {
PreferenceManager.getDefaultSharedPreferences(this).edit()
@ -40,13 +41,19 @@ class LicenseActivity : AppCompatActivity() {
val intent : Intent?
val isDatabaseLoaded = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(Constants.KEY_DATABASE_LOADED, false)
val initialSetupDone = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(Constants.KEY_INITIAL_SETUP_DONE, false)
intent = if (initialSetupDone)
Intent(this, MainActivity::class.java)
else
intent = if (!isDatabaseLoaded)
Intent(this, DatabaseLoadActivity::class.java)
else if (!initialSetupDone)
Intent(this, ImportBrainkeyActivity::class.java)
else
Intent(this, MainActivity::class.java)
startActivity(intent)
finish()

View file

@ -3,6 +3,7 @@ package cy.agorise.bitsybitshareswallet.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.models.Asset
@ -11,6 +12,9 @@ interface AssetDao {
@Insert
fun insert(asset: Asset)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(assets: List<Asset>)
@Query("SELECT * FROM assets")
fun getAllAssets(): LiveData<List<Asset>>
}

View file

@ -0,0 +1,30 @@
package cy.agorise.bitsybitshareswallet.repositories
import android.app.Application
import android.os.AsyncTask
import cy.agorise.bitsybitshareswallet.daos.AssetDao
import cy.agorise.bitsybitshareswallet.daos.BitsyDatabase
import cy.agorise.bitsybitshareswallet.models.Asset
class AssetRepository internal constructor(application: Application) {
private val mAssetDao: AssetDao
init {
val db = BitsyDatabase.getDatabase(application)
mAssetDao = db!!.assetDao()
}
fun insertAll(assets: List<Asset>) {
insertAllAsyncTask(mAssetDao).execute(assets)
}
private class insertAllAsyncTask internal constructor(private val mAsyncTaskDao: AssetDao) :
AsyncTask<List<Asset>, Void, Void>() {
override fun doInBackground(vararg assets: List<Asset>): Void? {
mAsyncTaskDao.insertAll(assets[0])
return null
}
}
}

View file

@ -9,6 +9,18 @@ object Constants {
/** Version of the currently used license */
const val CURRENT_LICENSE_VERSION = 1
/** Key used to store if the assets database has been loaded or not */
const val KEY_DATABASE_LOADED = "key_database_loaded"
/**
* Key used to store a preference value used to keep track of the last time the assets in
* database were updated.
*/
const val KEY_LAST_ASSET_LIST_UPDATE = "key_last_assets_update"
/** Key used to store if the initial setup is already done or not */
const val KEY_INITIAL_SETUP_DONE = "key_initial_setup_done"
/** Key used to store the id value of the currently active account in the shared preferences */
const val KEY_CURRENT_ACCOUNT_ID = "key_current_account_id"
@ -24,9 +36,6 @@ object Constants {
*/
const val LIFETIME_EXPIRATION_DATE = "1969-12-31T23:59:59"
/** Key used to store if the initial setup is already done or not */
const val KEY_INITIAL_SETUP_DONE = "key_initial_setup_done"
/** Key used to store the night mode setting into the shared preferences */
const val KEY_NIGHT_MODE_ACTIVATED = "key_night_mode_activated"
}

View file

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_database_load"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingStart="@dimen/activity_horizontal_margin"
android:paddingEnd="@dimen/activity_horizontal_margin"
tools:context=".activities.DatabaseLoadActivity">
<ImageView
android:id="@+id/imageView"
android:layout_width="@dimen/logo_size"
android:layout_height="@dimen/logo_size"
android:layout_gravity="center"
android:layout_marginBottom="25dp"
android:src="@drawable/bts_logo"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/tvLoadTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:gravity="center"
android:text="@string/title__loading_assets"
android:textAppearance="@style/TextAppearance.Bitsy.Headline5"
app:layout_constraintBottom_toTopOf="@+id/tvLoadMessage"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/imageView" />
<TextView
android:id="@+id/tvLoadMessage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:text="@string/text__performing_requests"
android:textAppearance="@style/TextAppearance.Bitsy.Body1"
app:layout_constraintBottom_toTopOf="@+id/guideline"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvLoadTitle" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnNext"
style="@style/Widget.PalmPay.Button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="@string/button__next"
android:enabled="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<ProgressBar
android:id="@+id/progressBar"
style="@style/Base.Widget.AppCompat.ProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/imageView"
android:layout_centerHorizontal="true"
android:layout_gravity="center"
android:layout_margin="15dp"
android:indeterminate="true"
android:indeterminateOnly="false"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/guideline" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.7" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -12,4 +12,7 @@
<dimen name="spacing_different_topic">24dp</dimen>
<dimen name="spacing_different_section">40dp</dimen>
<!-- Initial setup -->
<dimen name="logo_size">180dp</dimen>
</resources>

View file

@ -7,6 +7,14 @@
<string name="button__agree">Agree</string>
<string name="button__disagree">Disagree</string>
<!-- Database Load Activity-->
<string name="title__loading_assets">Loading Assets…</string>
<string name="text__performing_requests">Performing a series of requests in order to get the complete list of all existing assets</string>
<string name="text__loading_assets">Loaded %1$d assets into the database</string>
<string name="title__assets_loaded">Assets Loaded</string>
<string name="text__assets_loaded">Next, please setup your account…</string>
<string name="button__next">Next</string>
<!-- Import Brainkey Activity -->
<string name="text_field__6_digit_pin">6+ digits PIN</string>
<string name="error__pin_too_short">PIN too short</string>

View file

@ -42,6 +42,7 @@
</style>
<!-- Text styles -->
<style name="TextAppearance.Bitsy.Headline5" parent="TextAppearance.MaterialComponents.Headline5" />
<style name="TextAppearance.Bitsy.Headline6" parent="TextAppearance.MaterialComponents.Headline6" />
<style name="TextAppearance.Bitsy.Subtitle1" parent="TextAppearance.MaterialComponents.Subtitle1" />
<style name="TextAppearance.Bitsy.Body1" parent="TextAppearance.MaterialComponents.Body1" />