Merge branch 'develop'
This commit is contained in:
commit
de68a3e0a2
140 changed files with 1057 additions and 16382 deletions
2
.gitmodules
vendored
2
.gitmodules
vendored
|
@ -1,3 +1,3 @@
|
|||
[submodule "graphenej"]
|
||||
path = graphenejlib
|
||||
url = git@github.com:Agorise/graphenej.git
|
||||
url = git@git.agorise.net:agorise/graphenej.git
|
||||
|
|
|
@ -7,13 +7,14 @@ apply plugin: 'com.google.gms.google-services'
|
|||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
compileSdk 33
|
||||
|
||||
defaultConfig {
|
||||
applicationId "cy.agorise.bitsybitshareswallet"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 15
|
||||
versionName "0.17.2-beta"
|
||||
minSdk 21
|
||||
targetSdk 33
|
||||
versionCode 16
|
||||
versionName "0.18.0-beta"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
javaCompileOptions {
|
||||
|
@ -55,17 +56,13 @@ android {
|
|||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
// Gradle automatically adds 'android.test.runner' as a dependency.
|
||||
useLibrary 'android.test.runner'
|
||||
useLibrary 'android.test.base'
|
||||
useLibrary 'android.test.mock'
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
def lifecycle_version = "2.2.0"
|
||||
def arch_version = "2.1.0"
|
||||
def room_version = "2.2.6"
|
||||
def lifecycle_version = "2.3.1"
|
||||
def preference_version = "1.1.1"
|
||||
def room_version = "2.4.3"
|
||||
def rx_bindings_version = '3.0.0'
|
||||
def version_coroutine = '1.4.1'
|
||||
|
||||
|
@ -74,17 +71,20 @@ dependencies {
|
|||
implementation project(':PDFJet')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
// AndroidX
|
||||
implementation 'androidx.activity:activity-ktx:1.2.4'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
implementation "androidx.fragment:fragment-ktx:1.3.2"
|
||||
implementation "androidx.preference:preference-ktx:$preference_version"
|
||||
// Google
|
||||
implementation 'com.google.zxing:core:3.4.0'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'com.google.android.material:material:1.1.0-alpha04'
|
||||
implementation 'com.google.android.material:material:1.3.0'
|
||||
implementation 'com.google.android.gms:play-services-maps:17.0.0'
|
||||
implementation 'com.google.maps.android:android-maps-utils:0.5'
|
||||
// AAC Lifecycle
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" // viewModelScope
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||
// AAC Room
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
|
@ -108,8 +108,10 @@ dependencies {
|
|||
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.0'
|
||||
implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
|
||||
//Firebase
|
||||
implementation 'com.google.firebase:firebase-analytics:18.0.2'
|
||||
implementation 'com.google.firebase:firebase-crashlytics:17.3.1'
|
||||
implementation platform('com.google.firebase:firebase-bom:26.7.0') // Import the BoM for the Firebase platform
|
||||
implementation 'com.google.firebase:firebase-crashlytics-ktx'
|
||||
implementation 'com.google.firebase:firebase-analytics-ktx'
|
||||
|
||||
// CSV generation
|
||||
implementation 'com.opencsv:opencsv:3.7'
|
||||
// Others
|
||||
|
@ -130,7 +132,6 @@ dependencies {
|
|||
androidTestImplementation "androidx.room:room-testing:$room_version"
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
androidTestImplementation 'com.jraska.livedata:testing-ktx:1.0.0'
|
||||
}
|
||||
|
||||
// Added to avoid the compilation problem due to a duplicate ListenableFuture library
|
||||
|
|
|
@ -23,20 +23,18 @@ import java.util.concurrent.CountDownLatch
|
|||
import java.util.concurrent.TimeUnit
|
||||
|
||||
object LiveDataTestUtil {
|
||||
fun <T> getValue(liveData: LiveData<T>): T {
|
||||
val data = arrayOfNulls<Any>(1)
|
||||
fun <T> LiveData<T>.blockingObserve(): T? {
|
||||
var value: T? = null
|
||||
val latch = CountDownLatch(1)
|
||||
val observer = object : Observer<T> {
|
||||
override fun onChanged(o: T?) {
|
||||
data[0] = o
|
||||
latch.countDown()
|
||||
liveData.removeObserver(this)
|
||||
}
|
||||
}
|
||||
liveData.observeForever(observer)
|
||||
latch.await(2, TimeUnit.SECONDS)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return data[0] as T
|
||||
val observer = Observer<T> { t ->
|
||||
value = t
|
||||
latch.countDown()
|
||||
}
|
||||
|
||||
observeForever(observer)
|
||||
|
||||
latch.await(2, TimeUnit.SECONDS)
|
||||
return value
|
||||
}
|
||||
}
|
|
@ -6,14 +6,11 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
|||
import androidx.room.Room
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.jraska.livedata.test
|
||||
import cy.agorise.bitsybitshareswallet.LiveDataTestUtil.blockingObserve
|
||||
import cy.agorise.bitsybitshareswallet.database.BitsyDatabase
|
||||
import cy.agorise.bitsybitshareswallet.database.entities.EquivalentValue
|
||||
import cy.agorise.bitsybitshareswallet.database.entities.Transfer
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.*
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.IOException
|
||||
|
||||
|
@ -111,13 +108,12 @@ class TransfersTests {
|
|||
"1.2.1029856",
|
||||
98,
|
||||
"1.3.120",
|
||||
"",
|
||||
1000)
|
||||
"")
|
||||
db.transferDao().insert(t1)
|
||||
db.transferDao().insert(t2)
|
||||
db.transferDao().getTransfersWithMissingBtsValue()
|
||||
.test()
|
||||
.assertHasValue()
|
||||
.assertValue { transfer -> transfer.id == "1.11.702181910" }
|
||||
|
||||
// transfer should be t2
|
||||
val transfer = db.transferDao().getTransfersWithMissingBtsValue().blockingObserve()
|
||||
Assert.assertEquals("1.11.684483739", transfer?.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,45 +1,53 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="cy.agorise.bitsybitshareswallet">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="cy.agorise.bitsybitshareswallet">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
<application
|
||||
android:name=".utils.BitsyApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Bitsy"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.geo.API_KEY"
|
||||
android:value="@string/google_maps_key"/>
|
||||
android:value="@string/google_maps_key" />
|
||||
<!-- Avoid Crashlytics crash collection for all users/builds -->
|
||||
<meta-data
|
||||
android:name="firebase_crashlytics_collection_enabled"
|
||||
android:value="false" />
|
||||
<!-- Avoid crashes with Google maps in SDK 28 (Android 9 [Pie]) -->
|
||||
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
|
||||
<uses-library
|
||||
android:name="org.apache.http.legacy"
|
||||
android:required="false" />
|
||||
|
||||
<activity
|
||||
android:name=".activities.SplashActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/SplashTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activities.MainActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/Theme.Bitsy"
|
||||
android:windowSoftInputMode="adjustPan">
|
||||
|
@ -50,12 +58,11 @@
|
|||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="cy.agorise.bitsybitshareswallet.FileProvider"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false">
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/tmp_image_path" />
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
|
|
@ -4,13 +4,13 @@ import android.content.pm.PackageManager
|
|||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import cy.agorise.bitsybitshareswallet.database.entities.Balance
|
||||
import cy.agorise.bitsybitshareswallet.database.entities.Transfer
|
||||
|
@ -69,10 +69,11 @@ abstract class ConnectedActivity : AppCompatActivity() {
|
|||
private const val RESPONSE_GET_MARKET_HISTORY = 6
|
||||
}
|
||||
|
||||
private lateinit var mUserAccountViewModel: UserAccountViewModel
|
||||
private lateinit var mBalanceViewModel: BalanceViewModel
|
||||
private lateinit var mTransferViewModel: TransferViewModel
|
||||
private lateinit var mConnectedActivityViewModel: ConnectedActivityViewModel
|
||||
// TODO consolidate ViewModels
|
||||
private val userAccountViewModel: UserAccountViewModel by viewModels()
|
||||
private val balanceViewModel: BalanceViewModel by viewModels()
|
||||
private val transferViewModel: TransferViewModel by viewModels()
|
||||
private val connectedActivityViewModel: ConnectedActivityViewModel by viewModels()
|
||||
|
||||
private lateinit var mAssetRepository: AssetRepository
|
||||
|
||||
|
@ -114,42 +115,38 @@ abstract class ConnectedActivity : AppCompatActivity() {
|
|||
mAssetRepository = AssetRepository(this)
|
||||
|
||||
// Configure ConnectedActivityViewModel to obtain missing equivalent values
|
||||
mConnectedActivityViewModel = ViewModelProviders.of(this).get(ConnectedActivityViewModel::class.java)
|
||||
|
||||
val currencyCode = Helper.getCoingeckoSupportedCurrency(Locale.getDefault())
|
||||
Log.d(TAG, "Using currency: ${currencyCode.toUpperCase(Locale.ROOT)}")
|
||||
mConnectedActivityViewModel.observeMissingEquivalentValuesIn(currencyCode)
|
||||
connectedActivityViewModel.observeMissingEquivalentValuesIn(currencyCode)
|
||||
|
||||
// Configure UserAccountViewModel to obtain the missing account ids
|
||||
mUserAccountViewModel = ViewModelProviders.of(this).get(UserAccountViewModel::class.java)
|
||||
|
||||
mUserAccountViewModel.getMissingUserAccountIds().observe(this, Observer<List<String>>{ userAccountIds ->
|
||||
userAccountViewModel.getMissingUserAccountIds().observe(this, { userAccountIds ->
|
||||
if (userAccountIds.isNotEmpty()) {
|
||||
missingUserAccounts.clear()
|
||||
for (userAccountId in userAccountIds)
|
||||
missingUserAccounts.add(UserAccount(userAccountId))
|
||||
|
||||
mHandler.postDelayed(mRequestMissingUserAccountsTask, Constants.NETWORK_SERVICE_RETRY_PERIOD)
|
||||
mHandler.postDelayed(
|
||||
mRequestMissingUserAccountsTask,
|
||||
Constants.NETWORK_SERVICE_RETRY_PERIOD
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// Configure UserAccountViewModel to obtain the missing account ids
|
||||
mBalanceViewModel = ViewModelProviders.of(this).get(BalanceViewModel::class.java)
|
||||
|
||||
mBalanceViewModel.getMissingAssetIds().observe(this, Observer<List<String>>{ assetIds ->
|
||||
balanceViewModel.getMissingAssetIds().observe(this, { assetIds ->
|
||||
if (assetIds.isNotEmpty()) {
|
||||
missingAssets.clear()
|
||||
for (assetId in assetIds)
|
||||
missingAssets.add(Asset(assetId))
|
||||
|
||||
mHandler.postDelayed(mRequestMissingAssetsTask, Constants.NETWORK_SERVICE_RETRY_PERIOD)
|
||||
mHandler
|
||||
.postDelayed(mRequestMissingAssetsTask, Constants.NETWORK_SERVICE_RETRY_PERIOD)
|
||||
}
|
||||
})
|
||||
|
||||
//Configure TransferViewModel to obtain the Transfer's block numbers with missing time information, one by one
|
||||
mTransferViewModel = ViewModelProviders.of(this).get(TransferViewModel::class.java)
|
||||
|
||||
mTransferViewModel.getTransferBlockNumberWithMissingTime().observe(this, Observer<Long>{ blockNumber ->
|
||||
transferViewModel.getTransferBlockNumberWithMissingTime().observe(this, { blockNumber ->
|
||||
if (blockNumber != null && blockNumber != blockNumberWithMissingTime) {
|
||||
blockNumberWithMissingTime = blockNumber
|
||||
mHandler.post(mRequestBlockMissingTimeTask)
|
||||
|
@ -167,17 +164,17 @@ abstract class ConnectedActivity : AppCompatActivity() {
|
|||
mCompositeDisposable.add(disposable)
|
||||
|
||||
|
||||
val info = this.packageManager.getPackageInfo(this.packageName, PackageManager.GET_ACTIVITIES)
|
||||
val info =
|
||||
this.packageManager.getPackageInfo(this.packageName, PackageManager.GET_ACTIVITIES)
|
||||
val versionCode = PackageInfoCompat.getLongVersionCode(info)
|
||||
val hasPurgedEquivalentValues = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getBoolean(Constants.KEY_HAS_PURGED_EQUIVALENT_VALUES, false)
|
||||
if(versionCode > 11 && !hasPurgedEquivalentValues) {
|
||||
if (versionCode > 11 && !hasPurgedEquivalentValues) {
|
||||
thread {
|
||||
mConnectedActivityViewModel.purgeEquivalentValues()
|
||||
PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.edit()
|
||||
.putBoolean(Constants.KEY_HAS_PURGED_EQUIVALENT_VALUES, true)
|
||||
.apply()
|
||||
connectedActivityViewModel.purgeEquivalentValues()
|
||||
PreferenceManager.getDefaultSharedPreferences(this).edit {
|
||||
putBoolean(Constants.KEY_HAS_PURGED_EQUIVALENT_VALUES, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -201,7 +198,7 @@ abstract class ConnectedActivity : AppCompatActivity() {
|
|||
* Error consumer used to handle potential errors caused by the NetworkService while processing
|
||||
* incoming data.
|
||||
*/
|
||||
private fun handleError(throwable: Throwable){
|
||||
private fun handleError(throwable: Throwable) {
|
||||
Log.e(TAG, "Error while processing received message. Msg: " + throwable.message)
|
||||
val stack = throwable.stackTrace
|
||||
for (e in stack) {
|
||||
|
@ -217,24 +214,24 @@ abstract class ConnectedActivity : AppCompatActivity() {
|
|||
if (message.error == null) {
|
||||
if (responseMap.containsKey(message.id)) {
|
||||
when (responseMap[message.id]) {
|
||||
RESPONSE_GET_FULL_ACCOUNTS ->
|
||||
RESPONSE_GET_FULL_ACCOUNTS ->
|
||||
handleAccountDetails((message.result as List<*>)[0] as FullAccountDetails)
|
||||
|
||||
RESPONSE_GET_ACCOUNTS ->
|
||||
RESPONSE_GET_ACCOUNTS ->
|
||||
handleAccountProperties(message.result as List<AccountProperties>)
|
||||
|
||||
RESPONSE_GET_ACCOUNT_BALANCES ->
|
||||
RESPONSE_GET_ACCOUNT_BALANCES ->
|
||||
handleBalanceUpdate(message.result as List<AssetAmount>)
|
||||
|
||||
RESPONSE_GET_ASSETS ->
|
||||
RESPONSE_GET_ASSETS ->
|
||||
handleAssets(message.result as List<Asset>)
|
||||
|
||||
RESPONSE_GET_BLOCK_HEADER -> {
|
||||
RESPONSE_GET_BLOCK_HEADER -> {
|
||||
val blockNumber = requestIdToBlockNumberMap[message.id] ?: 0L
|
||||
handleBlockHeader(message.result as BlockHeader, blockNumber)
|
||||
requestIdToBlockNumberMap.remove(message.id)
|
||||
}
|
||||
RESPONSE_GET_MARKET_HISTORY -> handleMarketData(message.result as List<BucketObject>)
|
||||
RESPONSE_GET_MARKET_HISTORY -> handleMarketData(message.result as List<BucketObject>)
|
||||
}
|
||||
responseMap.remove(message.id)
|
||||
}
|
||||
|
@ -259,11 +256,11 @@ abstract class ConnectedActivity : AppCompatActivity() {
|
|||
responseMap.clear()
|
||||
} else if (message.updateCode == ConnectionStatusUpdate.API_UPDATE) {
|
||||
// If we got an API update
|
||||
if(message.api == ApiAccess.API_HISTORY) {
|
||||
if (message.api == ApiAccess.API_HISTORY) {
|
||||
// Starts the procedure that will obtain the missing equivalent values
|
||||
mTransferViewModel
|
||||
.getTransfersWithMissingBtsValue().observe(this, Observer<Transfer> {
|
||||
if(it != null) handleTransfersWithMissingBtsValue(it)
|
||||
transferViewModel
|
||||
.getTransfersWithMissingBtsValue().observe(this, {
|
||||
if (it != null) handleTransfersWithMissingBtsValue(it)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -274,13 +271,16 @@ abstract class ConnectedActivity : AppCompatActivity() {
|
|||
* Method called whenever we get a list of transfers with their bts value missing.
|
||||
*/
|
||||
private fun handleTransfersWithMissingBtsValue(transfer: Transfer) {
|
||||
if(mNetworkService?.isConnected == true){
|
||||
if (mNetworkService?.isConnected == true) {
|
||||
val base = Asset(transfer.transferAssetId)
|
||||
val quote = Asset("1.3.0")
|
||||
val bucket: Long = TimeUnit.SECONDS.convert(1, TimeUnit.DAYS)
|
||||
val end: Long = transfer.timestamp * 1000L
|
||||
val start: Long = (transfer.timestamp - bucket) * 1000L
|
||||
val id = mNetworkService!!.sendMessage(GetMarketHistory(base, quote, bucket, start, end), GetMarketHistory.REQUIRED_API)
|
||||
val id = mNetworkService!!.sendMessage(
|
||||
GetMarketHistory(base, quote, bucket, start, end),
|
||||
GetMarketHistory.REQUIRED_API
|
||||
)
|
||||
responseMap[id] = RESPONSE_GET_MARKET_HISTORY
|
||||
this.transfer = transfer
|
||||
}
|
||||
|
@ -292,11 +292,17 @@ abstract class ConnectedActivity : AppCompatActivity() {
|
|||
*/
|
||||
private fun handleAccountDetails(accountDetails: FullAccountDetails) {
|
||||
val latestOpCount = accountDetails.statistics.total_ops
|
||||
Log.d(TAG, "handleAccountDetails. prev count: $storedOpCount, current count: $latestOpCount")
|
||||
Log.d(
|
||||
TAG,
|
||||
"handleAccountDetails. prev count: $storedOpCount, current count: $latestOpCount"
|
||||
)
|
||||
|
||||
if (latestOpCount == 0L) {
|
||||
Log.d(TAG, "The node returned 0 total_ops for current account and may not have installed the history plugin. " +
|
||||
"\nAsk the NetworkService to remove the node from the list and connect to another one.")
|
||||
Log.d(
|
||||
TAG,
|
||||
"The node returned 0 total_ops for current account and may not have installed the history plugin. " +
|
||||
"\nAsk the NetworkService to remove the node from the list and connect to another one."
|
||||
)
|
||||
mNetworkService?.reconnectNode()
|
||||
} else if (storedOpCount == -1L) {
|
||||
// Initial case when the app starts
|
||||
|
@ -317,7 +323,8 @@ abstract class ConnectedActivity : AppCompatActivity() {
|
|||
* create a list of BiTSy's UserAccount objects and stores them into the database
|
||||
*/
|
||||
private fun handleAccountProperties(accountPropertiesList: List<AccountProperties>) {
|
||||
val userAccounts = ArrayList<cy.agorise.bitsybitshareswallet.database.entities.UserAccount>()
|
||||
val userAccounts =
|
||||
ArrayList<cy.agorise.bitsybitshareswallet.database.entities.UserAccount>()
|
||||
|
||||
for (accountProperties in accountPropertiesList) {
|
||||
val userAccount = cy.agorise.bitsybitshareswallet.database.entities.UserAccount(
|
||||
|
@ -329,7 +336,7 @@ abstract class ConnectedActivity : AppCompatActivity() {
|
|||
userAccounts.add(userAccount)
|
||||
}
|
||||
|
||||
mUserAccountViewModel.insertAll(userAccounts)
|
||||
userAccountViewModel.insertAll(userAccounts)
|
||||
missingUserAccounts.clear()
|
||||
}
|
||||
|
||||
|
@ -346,7 +353,7 @@ abstract class ConnectedActivity : AppCompatActivity() {
|
|||
balances.add(balance)
|
||||
}
|
||||
|
||||
mBalanceViewModel.insertAll(balances)
|
||||
balanceViewModel.insertAll(balances)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -381,36 +388,40 @@ abstract class ConnectedActivity : AppCompatActivity() {
|
|||
dateFormat.timeZone = TimeZone.getTimeZone("GMT")
|
||||
|
||||
try {
|
||||
val date = dateFormat.parse(blockHeader.timestamp)
|
||||
mTransferViewModel.setBlockTime(blockNumber, date.time / 1000)
|
||||
dateFormat.parse(blockHeader.timestamp)?.let { date ->
|
||||
transferViewModel.setBlockTime(blockNumber, date.time / 1000)
|
||||
}
|
||||
} catch (e: ParseException) {
|
||||
Log.e(TAG, "ParseException. Msg: " + e.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMarketData(buckets: List<BucketObject>) {
|
||||
if(buckets.isNotEmpty()){
|
||||
Log.d(TAG,"handleMarketData. Bucket is not empty")
|
||||
if (buckets.isNotEmpty()) {
|
||||
Log.d(TAG, "handleMarketData. Bucket is not empty")
|
||||
val bucket = buckets[0]
|
||||
val pair = Pair(transfer, bucket)
|
||||
val disposable = Observable.just(pair)
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.map { mTransferViewModel.updateBtsValue(it.first!!, it.second) }
|
||||
.subscribe({},{
|
||||
Log.e(TAG,"Error at updateBtsValue. Msg: ${it.message}")
|
||||
for(line in it.stackTrace) Log.e(TAG, "${line.className}#${line.methodName}:${line.lineNumber}")
|
||||
.map { transferViewModel.updateBtsValue(it.first!!, it.second) }
|
||||
.subscribe({}, {
|
||||
Log.e(TAG, "Error at updateBtsValue. Msg: ${it.message}")
|
||||
for (line in it.stackTrace)
|
||||
Log.e(TAG, "${line.className}#${line.methodName}:${line.lineNumber}")
|
||||
})
|
||||
mCompositeDisposable.add(disposable)
|
||||
}else{
|
||||
Log.i(TAG,"handleMarketData. Bucket IS empty")
|
||||
AsyncTask.execute { mTransferViewModel.updateBtsValue(transfer!!, Transfer.ERROR) }
|
||||
} else {
|
||||
Log.i(TAG, "handleMarketData. Bucket IS empty")
|
||||
AsyncTask.execute { transferViewModel.updateBtsValue(transfer!!, Transfer.ERROR) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateBalances() {
|
||||
if (mNetworkService?.isConnected == true) {
|
||||
val id = mNetworkService!!.sendMessage(GetAccountBalances(mCurrentAccount, ArrayList()),
|
||||
GetAccountBalances.REQUIRED_API)
|
||||
val id = mNetworkService!!.sendMessage(
|
||||
GetAccountBalances(mCurrentAccount, ArrayList()),
|
||||
GetAccountBalances.REQUIRED_API
|
||||
)
|
||||
|
||||
responseMap[id] = RESPONSE_GET_ACCOUNT_BALANCES
|
||||
}
|
||||
|
@ -447,10 +458,13 @@ abstract class ConnectedActivity : AppCompatActivity() {
|
|||
private val mRequestMissingUserAccountsTask = object : Runnable {
|
||||
override fun run() {
|
||||
if (mNetworkService?.isConnected == true) {
|
||||
val id = mNetworkService!!.sendMessage(GetAccounts(missingUserAccounts), GetAccounts.REQUIRED_API)
|
||||
val id = mNetworkService!!.sendMessage(
|
||||
GetAccounts(missingUserAccounts),
|
||||
GetAccounts.REQUIRED_API
|
||||
)
|
||||
|
||||
responseMap[id] = RESPONSE_GET_ACCOUNTS
|
||||
} else if (missingUserAccounts.isNotEmpty()){
|
||||
} else if (missingUserAccounts.isNotEmpty()) {
|
||||
mHandler.postDelayed(this, Constants.NETWORK_SERVICE_RETRY_PERIOD)
|
||||
}
|
||||
}
|
||||
|
@ -462,10 +476,11 @@ abstract class ConnectedActivity : AppCompatActivity() {
|
|||
private val mRequestMissingAssetsTask = object : Runnable {
|
||||
override fun run() {
|
||||
if (mNetworkService?.isConnected == true) {
|
||||
val id = mNetworkService!!.sendMessage(GetAssets(missingAssets), GetAssets.REQUIRED_API)
|
||||
val id =
|
||||
mNetworkService!!.sendMessage(GetAssets(missingAssets), GetAssets.REQUIRED_API)
|
||||
|
||||
responseMap[id] = RESPONSE_GET_ASSETS
|
||||
} else if (missingAssets.isNotEmpty()){
|
||||
} else if (missingAssets.isNotEmpty()) {
|
||||
mHandler.postDelayed(this, Constants.NETWORK_SERVICE_RETRY_PERIOD)
|
||||
}
|
||||
}
|
||||
|
@ -480,13 +495,17 @@ abstract class ConnectedActivity : AppCompatActivity() {
|
|||
if (mCurrentAccount != null) {
|
||||
val userAccounts = ArrayList<String>()
|
||||
userAccounts.add(mCurrentAccount!!.objectId)
|
||||
val id = mNetworkService!!.sendMessage(GetFullAccounts(userAccounts, false),
|
||||
GetFullAccounts.REQUIRED_API)
|
||||
val id = mNetworkService!!.sendMessage(
|
||||
GetFullAccounts(userAccounts, false),
|
||||
GetFullAccounts.REQUIRED_API
|
||||
)
|
||||
|
||||
responseMap[id] = RESPONSE_GET_FULL_ACCOUNTS
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "NetworkService is null or is not connected. mNetworkService: $mNetworkService")
|
||||
val msg = "NetworkService is null or is not connected. " +
|
||||
"mNetworkService: $mNetworkService"
|
||||
Log.w(TAG, msg)
|
||||
}
|
||||
mHandler.postDelayed(this, Constants.MISSING_PAYMENT_CHECK_PERIOD)
|
||||
|
||||
|
@ -500,8 +519,10 @@ abstract class ConnectedActivity : AppCompatActivity() {
|
|||
override fun run() {
|
||||
|
||||
if (mNetworkService?.isConnected == true) {
|
||||
val id = mNetworkService!!.sendMessage(GetBlockHeader(blockNumberWithMissingTime),
|
||||
GetBlockHeader.REQUIRED_API)
|
||||
val id = mNetworkService!!.sendMessage(
|
||||
GetBlockHeader(blockNumberWithMissingTime),
|
||||
GetBlockHeader.REQUIRED_API
|
||||
)
|
||||
|
||||
responseMap[id] = RESPONSE_GET_BLOCK_HEADER
|
||||
requestIdToBlockNumberMap[id] = blockNumberWithMissingTime
|
||||
|
@ -521,7 +542,7 @@ abstract class ConnectedActivity : AppCompatActivity() {
|
|||
override fun onPause() {
|
||||
super.onPause()
|
||||
mNetworkService?.nodeLatencyVerifier?.nodeList?.let { nodes ->
|
||||
mConnectedActivityViewModel.updateNodeLatencies(nodes as List<FullNode>)
|
||||
connectedActivityViewModel.updateNodeLatencies(nodes as List<FullNode>)
|
||||
}
|
||||
|
||||
mHandler.removeCallbacks(mCheckMissingPaymentsTask)
|
||||
|
@ -532,6 +553,6 @@ abstract class ConnectedActivity : AppCompatActivity() {
|
|||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if(!mCompositeDisposable.isDisposed) mCompositeDisposable.dispose()
|
||||
if (!mCompositeDisposable.isDisposed) mCompositeDisposable.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package cy.agorise.bitsybitshareswallet.activities
|
|||
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import androidx.navigation.findNavController
|
||||
|
@ -11,6 +10,7 @@ import androidx.navigation.ui.AppBarConfiguration
|
|||
import androidx.navigation.ui.navigateUp
|
||||
import androidx.navigation.ui.onNavDestinationSelected
|
||||
import androidx.navigation.ui.setupActionBarWithNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import cy.agorise.bitsybitshareswallet.R
|
||||
import cy.agorise.bitsybitshareswallet.databinding.ActivityMainBinding
|
||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||
|
@ -36,6 +36,7 @@ class MainActivity : ConnectedActivity() {
|
|||
) {
|
||||
setTheme(R.style.Theme_Bitsy_Dark)
|
||||
}
|
||||
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
|
|
|
@ -19,16 +19,18 @@ data class Transfer (
|
|||
@ColumnInfo(name = "memo") val memo: String,
|
||||
@ColumnInfo(name = "bts_value") var btsValue: Long? = NOT_CALCULATED
|
||||
){
|
||||
companion object {
|
||||
// Constant used to specify an uninitialized BTS equivalent value
|
||||
val NOT_CALCULATED: Long? = -1L
|
||||
// Constant used to specify a BTS equivalent value whose calculation returned an error
|
||||
val ERROR: Long? = -2L
|
||||
}
|
||||
|
||||
init {
|
||||
if(transferAssetId == "1.3.0"){
|
||||
// If the transferred asset is BTS, we can fill the btsValue field immediately
|
||||
btsValue = transferAmount
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Constant used to specify an uninitialized BTS equivalent value
|
||||
const val NOT_CALCULATED: Long = -1L
|
||||
// Constant used to specify a BTS equivalent value whose calculation returned an error
|
||||
const val ERROR: Long = -2L
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package cy.agorise.bitsybitshareswallet.domain.usecase
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import com.opencsv.CSVWriter
|
||||
import cy.agorise.bitsybitshareswallet.R
|
||||
import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.OutputStreamWriter
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.math.pow
|
||||
|
||||
class ExportTransactionsToCsvUseCase(private val applicationContext: Context) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
transactions: List<TransferDetail>,
|
||||
folderDocumentFile: DocumentFile
|
||||
) = withContext(Dispatchers.IO) { // TODO Inject Dispatcher
|
||||
// Create the PDF file name
|
||||
val fileName = applicationContext.resources.let {
|
||||
"${it.getString(R.string.app_name)}-${it.getString(R.string.title_transactions)}.csv"
|
||||
}
|
||||
|
||||
// Obtains the path to store the PDF
|
||||
val csvDocUri = folderDocumentFile.createFile("text/csv", fileName)?.uri
|
||||
val contentResolver = applicationContext.contentResolver
|
||||
val csvOutputStream = contentResolver.openOutputStream(csvDocUri!!, "w")
|
||||
val csvWriter = CSVWriter(OutputStreamWriter(csvOutputStream))
|
||||
|
||||
// Add the table header
|
||||
csvWriter.writeNext(
|
||||
arrayOf(
|
||||
R.string.title_from, R.string.title_to, R.string.title_memo, R.string.title_date,
|
||||
R.string.title_time, R.string.title_amount, R.string.title_equivalent_value
|
||||
).map { columnNameId -> applicationContext.getString(columnNameId) }.toTypedArray()
|
||||
)
|
||||
|
||||
// Configure date and time formats to reuse in all the transfers
|
||||
val locale = ConfigurationCompat.getLocales(applicationContext.resources.configuration)[0]
|
||||
val dateFormat = SimpleDateFormat("MM-dd-yyyy", locale)
|
||||
val timeFormat = SimpleDateFormat("HH:mm:ss", locale)
|
||||
|
||||
// Save all the transfers information
|
||||
val row = Array(7) { "" } // Array initialized with empty strings
|
||||
for ((index, transferDetail) in transactions.withIndex()) {
|
||||
val date = Date(transferDetail.date * 1000)
|
||||
|
||||
row[0] = transferDetail.from ?: "" // From
|
||||
row[1] = transferDetail.to ?: "" // To
|
||||
row[2] = transferDetail.memo // Memo
|
||||
row[3] = dateFormat.format(date) // Date
|
||||
row[4] = timeFormat.format(date) // Time
|
||||
|
||||
// Asset Amount
|
||||
val assetPrecision = transferDetail.assetPrecision
|
||||
val assetAmount = transferDetail.assetAmount / 10.0.pow(assetPrecision)
|
||||
row[5] =
|
||||
String.format("%.${assetPrecision}f %s", assetAmount, transferDetail.assetSymbol)
|
||||
|
||||
// Fiat Equivalent
|
||||
row[6] = if (transferDetail.fiatAmount != null && transferDetail.fiatSymbol != null) {
|
||||
val currency = Currency.getInstance(transferDetail.fiatSymbol)
|
||||
val fiatAmount = transferDetail.fiatAmount / 10.0.pow(currency.defaultFractionDigits)
|
||||
String.format(
|
||||
"%.${currency.defaultFractionDigits}f %s",
|
||||
fiatAmount,
|
||||
currency.currencyCode
|
||||
)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
csvWriter.writeNext(row)
|
||||
|
||||
// TODO update progress
|
||||
}
|
||||
|
||||
csvWriter.close()
|
||||
|
||||
Log.d("ExportTransactionsToCsv", "CSV generated and saved")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
package cy.agorise.bitsybitshareswallet.domain.usecase
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import com.pdfjet.*
|
||||
import cy.agorise.bitsybitshareswallet.R
|
||||
import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.BufferedOutputStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.math.pow
|
||||
|
||||
class ExportTransactionsToPdfUseCase(private val applicationContext: Context) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
transactions: List<TransferDetail>,
|
||||
folderDocumentFile: DocumentFile
|
||||
) = withContext(Dispatchers.IO) { // TODO Inject Dispatcher
|
||||
// Create the PDF file name
|
||||
val fileName = applicationContext.resources.let {
|
||||
"${it.getString(R.string.app_name)}-${it.getString(R.string.title_transactions)}.pdf"
|
||||
}
|
||||
|
||||
// Obtains the path to store the PDF
|
||||
val pdfDocUri = folderDocumentFile.createFile("application/pdf", fileName)?.uri
|
||||
val contentResolver = applicationContext.contentResolver
|
||||
val pdfOutputStream = contentResolver.openOutputStream(pdfDocUri!!, "w")
|
||||
|
||||
// Creates a new PDF object
|
||||
val pdf = PDF(BufferedOutputStream(pdfOutputStream), Compliance.PDF_A_1B)
|
||||
|
||||
// Font used for the table headers
|
||||
val f1 = Font(pdf, CoreFont.HELVETICA_BOLD).apply { size = 7f }
|
||||
|
||||
// Font used for the table contents
|
||||
val f2 = Font(pdf, CoreFont.HELVETICA).apply { size = 7f }
|
||||
|
||||
// Creates a new PDF table
|
||||
val table = Table()
|
||||
|
||||
// 2D array of cells used to populate the PDF table
|
||||
val tableData = mutableListOf<List<Cell>>()
|
||||
|
||||
// Add column names/headers
|
||||
val columnNames = intArrayOf(
|
||||
R.string.title_from, R.string.title_to, R.string.title_memo, R.string.title_date,
|
||||
R.string.title_time, R.string.title_amount, R.string.title_equivalent_value
|
||||
)
|
||||
|
||||
val header = columnNames.map { columnName ->
|
||||
Cell(f1, applicationContext.getString(columnName)).apply { setPadding(2f) }
|
||||
}
|
||||
|
||||
// Add the table headers
|
||||
tableData.add(header)
|
||||
|
||||
// Add the table contents
|
||||
val locale = ConfigurationCompat.getLocales(applicationContext.resources.configuration)[0]
|
||||
tableData.addAll(getData(transactions, f2, locale))
|
||||
|
||||
// Configure the PDF table
|
||||
table.setData(tableData, Table.DATA_HAS_1_HEADER_ROWS)
|
||||
table.setCellBordersWidth(0.2f)
|
||||
// The A4 size has 595 points of width, with the below we are trying to assign the same
|
||||
// width to all cells and also keep them centered.
|
||||
for (i in 0..6) {
|
||||
table.setColumnWidth(i, 65f)
|
||||
}
|
||||
table.wrapAroundCellText()
|
||||
table.mergeOverlaidBorders()
|
||||
|
||||
// Populate the PDF table
|
||||
while (table.hasMoreData()) {
|
||||
// Configures the PDF page
|
||||
val page = Page(pdf, Letter.PORTRAIT)
|
||||
table.setLocation(45f, 30f)
|
||||
table.drawOn(page)
|
||||
}
|
||||
|
||||
pdf.close()
|
||||
|
||||
Log.d("ExportTransactionsToPdf", "PDF generated and saved")
|
||||
}
|
||||
|
||||
private suspend fun getData(
|
||||
transferDetails: List<TransferDetail>,
|
||||
font: Font,
|
||||
locale: Locale
|
||||
): List<List<Cell>> = withContext(Dispatchers.IO) { // TODO Inject Dispatcher
|
||||
|
||||
val tableData = mutableListOf<List<Cell>>()
|
||||
|
||||
// Configure date and time formats to reuse in all the transfers
|
||||
val dateFormat = SimpleDateFormat("MM-dd-yyyy", locale)
|
||||
val timeFormat = SimpleDateFormat("HH:mm:ss", locale)
|
||||
var date: Date
|
||||
|
||||
// Save all the transfers information
|
||||
for ((index, transferDetail) in transferDetails.withIndex()) {
|
||||
val cols = mutableListOf<String>()
|
||||
|
||||
date = Date(transferDetail.date * 1000)
|
||||
|
||||
cols.add(transferDetail.from ?: "") // From
|
||||
cols.add(transferDetail.to ?: "") // To
|
||||
cols.add(transferDetail.memo) // Memo
|
||||
cols.add(dateFormat.format(date)) // Date
|
||||
cols.add(timeFormat.format(date)) // Time
|
||||
|
||||
// Asset Amount
|
||||
val assetPrecision = transferDetail.assetPrecision
|
||||
val assetAmount = transferDetail.assetAmount / 10.0.pow(assetPrecision)
|
||||
cols.add(
|
||||
String.format(
|
||||
"%.${assetPrecision}f %s",
|
||||
assetAmount,
|
||||
transferDetail.assetSymbol
|
||||
)
|
||||
)
|
||||
|
||||
// Fiat Equivalent
|
||||
if (transferDetail.fiatAmount != null && transferDetail.fiatSymbol != null) {
|
||||
val currency = Currency.getInstance(transferDetail.fiatSymbol)
|
||||
val fiatAmount =
|
||||
transferDetail.fiatAmount / 10.0.pow(currency.defaultFractionDigits)
|
||||
cols.add(
|
||||
String.format(
|
||||
"%.${currency.defaultFractionDigits}f %s",
|
||||
fiatAmount, currency.currencyCode
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val row = cols.map { col ->
|
||||
Cell(font, col).apply { setPadding(2f) }
|
||||
}
|
||||
|
||||
tableData.add(row)
|
||||
|
||||
appendMissingCells(tableData, font)
|
||||
|
||||
// TODO update progress
|
||||
}
|
||||
|
||||
tableData
|
||||
}
|
||||
|
||||
private fun appendMissingCells(tableData: List<List<Cell>>, font: Font) {
|
||||
val firstRow = tableData[0]
|
||||
val numOfColumns = firstRow.size
|
||||
for (i in tableData.indices) {
|
||||
val dataRow = tableData[i] as ArrayList<Cell>
|
||||
val dataRowColumns = dataRow.size
|
||||
if (dataRowColumns < numOfColumns) {
|
||||
for (j in 0 until numOfColumns - dataRowColumns) {
|
||||
dataRow.add(Cell(font))
|
||||
}
|
||||
dataRow[dataRowColumns - 1].colSpan = numOfColumns - dataRowColumns + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,22 +5,20 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import cy.agorise.bitsybitshareswallet.adapters.BalancesAdapter
|
||||
import cy.agorise.bitsybitshareswallet.database.joins.BalanceDetail
|
||||
import cy.agorise.bitsybitshareswallet.databinding.FragmentBalancesBinding
|
||||
import cy.agorise.bitsybitshareswallet.viewmodels.BalanceDetailViewModel
|
||||
|
||||
class BalancesFragment : Fragment() {
|
||||
|
||||
private val viewModel: BalanceDetailViewModel by viewModels()
|
||||
|
||||
private var _binding: FragmentBalancesBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private lateinit var mBalanceDetailViewModel: BalanceDetailViewModel
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
|
@ -40,19 +38,15 @@ class BalancesFragment : Fragment() {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Configure BalanceDetailViewModel to show the current balances
|
||||
mBalanceDetailViewModel =
|
||||
ViewModelProviders.of(this).get(BalanceDetailViewModel::class.java)
|
||||
|
||||
val balancesAdapter = BalancesAdapter(context!!)
|
||||
val balancesAdapter = BalancesAdapter(requireContext())
|
||||
binding.rvBalances.adapter = balancesAdapter
|
||||
binding.rvBalances.layoutManager = LinearLayoutManager(context!!)
|
||||
binding.rvBalances.layoutManager = LinearLayoutManager(requireContext())
|
||||
binding.rvBalances.addItemDecoration(
|
||||
DividerItemDecoration(context!!, DividerItemDecoration.VERTICAL)
|
||||
DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL)
|
||||
)
|
||||
|
||||
mBalanceDetailViewModel.getAll()
|
||||
.observe(this, Observer<List<BalanceDetail>> { balancesDetails ->
|
||||
balancesAdapter.replaceAll(balancesDetails)
|
||||
})
|
||||
viewModel.getAll().observe(viewLifecycleOwner, { balancesDetails ->
|
||||
balancesAdapter.replaceAll(balancesDetails)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package cy.agorise.bitsybitshareswallet.fragments
|
||||
|
||||
import android.preference.PreferenceManager
|
||||
import androidx.core.content.edit
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import cy.agorise.bitsybitshareswallet.R
|
||||
import cy.agorise.bitsybitshareswallet.activities.ConnectedActivity
|
||||
import cy.agorise.bitsybitshareswallet.database.entities.Authority
|
||||
import cy.agorise.bitsybitshareswallet.repositories.AuthorityRepository
|
||||
import cy.agorise.bitsybitshareswallet.repositories.UserAccountRepository
|
||||
|
@ -13,7 +15,6 @@ import cy.agorise.graphenej.BrainKey
|
|||
import cy.agorise.graphenej.PublicKey
|
||||
import cy.agorise.graphenej.models.AccountProperties
|
||||
import org.bitcoinj.core.ECKey
|
||||
import cy.agorise.bitsybitshareswallet.activities.ConnectedActivity
|
||||
|
||||
|
||||
abstract class BaseAccountFragment : ConnectedFragment() {
|
||||
|
@ -36,23 +37,26 @@ abstract class BaseAccountFragment : ConnectedFragment() {
|
|||
val hashedPIN = CryptoUtils.createSHA256Hash(salt + pin)
|
||||
|
||||
// Stores the user selected PIN, hashed
|
||||
PreferenceManager.getDefaultSharedPreferences(context!!).edit()
|
||||
.putString(Constants.KEY_HASHED_PIN_PATTERN, hashedPIN)
|
||||
.putString(Constants.KEY_PIN_PATTERN_SALT, salt)
|
||||
.putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 1).apply() // 1 -> PIN
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit {
|
||||
putString(Constants.KEY_HASHED_PIN_PATTERN, hashedPIN)
|
||||
putString(Constants.KEY_PIN_PATTERN_SALT, salt)
|
||||
putInt(Constants.KEY_SECURITY_LOCK_SELECTED, 1) // 1 -> PIN
|
||||
}
|
||||
|
||||
// Stores the accounts this key refers to
|
||||
val id = accountProperties.id
|
||||
val name = accountProperties.name
|
||||
val isLTM = accountProperties.membership_expiration_date == Constants.LIFETIME_EXPIRATION_DATE
|
||||
val isLTM =
|
||||
accountProperties.membership_expiration_date == Constants.LIFETIME_EXPIRATION_DATE
|
||||
|
||||
val userAccount = cy.agorise.bitsybitshareswallet.database.entities.UserAccount(id, name, isLTM)
|
||||
val userAccount =
|
||||
cy.agorise.bitsybitshareswallet.database.entities.UserAccount(id, name, isLTM)
|
||||
|
||||
val userAccountRepository = UserAccountRepository(context!!.applicationContext)
|
||||
val userAccountRepository = UserAccountRepository(requireContext().applicationContext)
|
||||
userAccountRepository.insert(userAccount)
|
||||
|
||||
// Stores the id of the currently active user account
|
||||
PreferenceManager.getDefaultSharedPreferences(context!!).edit()
|
||||
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit()
|
||||
.putString(Constants.KEY_CURRENT_ACCOUNT_ID, accountProperties.id).apply()
|
||||
|
||||
// Trying to store all possible authorities (owner, active and memo) into the database
|
||||
|
@ -65,13 +69,25 @@ abstract class BaseAccountFragment : ConnectedFragment() {
|
|||
val publicKey = PublicKey(ECKey.fromPublicOnly(mBrainKey!!.privateKey.pubKey))
|
||||
|
||||
if (ownerAuthority.keyAuths.keys.contains(publicKey)) {
|
||||
addAuthorityToDatabase(accountProperties.id, AuthorityType.OWNER.ordinal, mBrainKey!!)
|
||||
addAuthorityToDatabase(
|
||||
accountProperties.id,
|
||||
AuthorityType.OWNER.ordinal,
|
||||
mBrainKey!!
|
||||
)
|
||||
}
|
||||
if (activeAuthority.keyAuths.keys.contains(publicKey)) {
|
||||
addAuthorityToDatabase(accountProperties.id, AuthorityType.ACTIVE.ordinal, mBrainKey!!)
|
||||
addAuthorityToDatabase(
|
||||
accountProperties.id,
|
||||
AuthorityType.ACTIVE.ordinal,
|
||||
mBrainKey!!
|
||||
)
|
||||
}
|
||||
if (options.memoKey == publicKey) {
|
||||
addAuthorityToDatabase(accountProperties.id, AuthorityType.MEMO.ordinal, mBrainKey!!)
|
||||
addAuthorityToDatabase(
|
||||
accountProperties.id,
|
||||
AuthorityType.MEMO.ordinal,
|
||||
mBrainKey!!
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,13 +107,21 @@ abstract class BaseAccountFragment : ConnectedFragment() {
|
|||
val wif = brainKey.walletImportFormat
|
||||
val sequenceNumber = brainKey.sequenceNumber
|
||||
|
||||
val encryptedBrainKey = CryptoUtils.encrypt(context!!, brainKeyWords)
|
||||
val encryptedSequenceNumber = CryptoUtils.encrypt(context!!, sequenceNumber.toString())
|
||||
val encryptedWIF = CryptoUtils.encrypt(context!!, wif)
|
||||
val encryptedBrainKey = CryptoUtils.encrypt(requireContext(), brainKeyWords)
|
||||
val encryptedSequenceNumber =
|
||||
CryptoUtils.encrypt(requireContext(), sequenceNumber.toString())
|
||||
val encryptedWIF = CryptoUtils.encrypt(requireContext(), wif)
|
||||
|
||||
val authority = Authority(0, userId, authorityType, encryptedWIF, encryptedBrainKey, encryptedSequenceNumber)
|
||||
val authority = Authority(
|
||||
0,
|
||||
userId,
|
||||
authorityType,
|
||||
encryptedWIF,
|
||||
encryptedBrainKey,
|
||||
encryptedSequenceNumber
|
||||
)
|
||||
|
||||
val authorityRepository = AuthorityRepository(context!!)
|
||||
val authorityRepository = AuthorityRepository(requireContext())
|
||||
authorityRepository.insert(authority)
|
||||
}
|
||||
}
|
|
@ -2,11 +2,12 @@ package cy.agorise.bitsybitshareswallet.fragments
|
|||
|
||||
import android.os.Bundle
|
||||
import android.os.CountDownTimer
|
||||
import android.preference.PreferenceManager
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.edit
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.preference.PreferenceManager
|
||||
import cy.agorise.bitsybitshareswallet.R
|
||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
|
@ -118,10 +119,10 @@ abstract class BaseSecurityLockDialog : DialogFragment() {
|
|||
|
||||
incorrectSecurityLockTime = now
|
||||
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||
.putInt(Constants.KEY_INCORRECT_SECURITY_LOCK_ATTEMPTS, ++incorrectSecurityLockAttempts)
|
||||
.putLong(Constants.KEY_INCORRECT_SECURITY_LOCK_TIME, now)
|
||||
.apply()
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
putInt(Constants.KEY_INCORRECT_SECURITY_LOCK_ATTEMPTS, ++incorrectSecurityLockAttempts)
|
||||
putLong(Constants.KEY_INCORRECT_SECURITY_LOCK_TIME, now)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -132,10 +133,10 @@ abstract class BaseSecurityLockDialog : DialogFragment() {
|
|||
incorrectSecurityLockTime = 0
|
||||
incorrectSecurityLockAttempts = 0
|
||||
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||
.putInt(Constants.KEY_INCORRECT_SECURITY_LOCK_ATTEMPTS, incorrectSecurityLockAttempts)
|
||||
.putLong(Constants.KEY_INCORRECT_SECURITY_LOCK_TIME, incorrectSecurityLockTime)
|
||||
.apply()
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
putInt(Constants.KEY_INCORRECT_SECURITY_LOCK_ATTEMPTS, incorrectSecurityLockAttempts)
|
||||
putLong(Constants.KEY_INCORRECT_SECURITY_LOCK_TIME, incorrectSecurityLockTime)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun startContDownTimer() {
|
||||
|
|
|
@ -362,8 +362,9 @@ class CreateAccountFragment : BaseAccountFragment() {
|
|||
var reader: BufferedReader? = null
|
||||
val dictionary: String
|
||||
try {
|
||||
reader =
|
||||
BufferedReader(InputStreamReader(context!!.assets.open(BRAINKEY_FILE), "UTF-8"))
|
||||
reader = BufferedReader(
|
||||
InputStreamReader(requireContext().assets.open(BRAINKEY_FILE), "UTF-8")
|
||||
)
|
||||
dictionary = reader.readLine()
|
||||
|
||||
val brainKeySuggestion = BrainKey.suggest(dictionary)
|
||||
|
|
|
@ -1,27 +1,23 @@
|
|||
package cy.agorise.bitsybitshareswallet.fragments
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.text.Html
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.*
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import cy.agorise.bitsybitshareswallet.R
|
||||
import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail
|
||||
import cy.agorise.bitsybitshareswallet.databinding.FragmentEReceiptBinding
|
||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||
import cy.agorise.bitsybitshareswallet.utils.Helper
|
||||
import cy.agorise.bitsybitshareswallet.utils.toast
|
||||
import cy.agorise.bitsybitshareswallet.viewmodels.EReceiptViewModel
|
||||
import java.math.RoundingMode
|
||||
import java.text.DecimalFormat
|
||||
|
@ -29,21 +25,17 @@ import java.text.DecimalFormatSymbols
|
|||
import java.text.NumberFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.math.pow
|
||||
|
||||
class EReceiptFragment : Fragment() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "EReceiptFragment"
|
||||
private val args: EReceiptFragmentArgs by navArgs()
|
||||
|
||||
private const val REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION = 100
|
||||
}
|
||||
private val viewModel: EReceiptViewModel by viewModels()
|
||||
|
||||
private var _binding: FragmentEReceiptBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val args: EReceiptFragmentArgs by navArgs()
|
||||
|
||||
private lateinit var mEReceiptViewModel: EReceiptViewModel
|
||||
private lateinit var mLocale: Locale
|
||||
|
||||
override fun onCreateView(
|
||||
|
@ -75,12 +67,9 @@ class EReceiptFragment : Fragment() {
|
|||
|
||||
val transferId = args.transferId
|
||||
|
||||
mEReceiptViewModel = ViewModelProviders.of(this).get(EReceiptViewModel::class.java)
|
||||
|
||||
mEReceiptViewModel.get(userId, transferId)
|
||||
.observe(this, Observer<TransferDetail> { transferDetail ->
|
||||
bindTransferDetail(transferDetail)
|
||||
})
|
||||
viewModel.get(userId, transferId).observe(viewLifecycleOwner) { transferDetail ->
|
||||
bindTransferDetail(transferDetail)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindTransferDetail(transferDetail: TransferDetail) {
|
||||
|
@ -98,7 +87,7 @@ class EReceiptFragment : Fragment() {
|
|||
df.decimalFormatSymbols = DecimalFormatSymbols(Locale.getDefault())
|
||||
|
||||
val amount = transferDetail.assetAmount.toDouble() /
|
||||
Math.pow(10.toDouble(), transferDetail.assetPrecision.toDouble())
|
||||
10.toDouble().pow(transferDetail.assetPrecision.toDouble())
|
||||
val assetAmount = "${df.format(amount)} ${transferDetail.getUIAssetSymbol()}"
|
||||
binding.tvAmount.text = assetAmount
|
||||
|
||||
|
@ -107,7 +96,7 @@ class EReceiptFragment : Fragment() {
|
|||
val numberFormat = NumberFormat.getNumberInstance()
|
||||
val currency = Currency.getInstance(transferDetail.fiatSymbol)
|
||||
val fiatEquivalent = transferDetail.fiatAmount.toDouble() /
|
||||
Math.pow(10.0, currency.defaultFractionDigits.toDouble())
|
||||
10.0.pow(currency.defaultFractionDigits.toDouble())
|
||||
|
||||
val equivalentValue = "${numberFormat.format(fiatEquivalent)} ${currency.currencyCode}"
|
||||
binding.tvEquivalentValue.text = equivalentValue
|
||||
|
@ -133,12 +122,11 @@ class EReceiptFragment : Fragment() {
|
|||
/** Formats the transfer TextView to show a link to explore the given transfer
|
||||
* in a BitShares explorer */
|
||||
private fun formatTransferTextView(transferId: String) {
|
||||
val tx = Html.fromHtml(
|
||||
getString(
|
||||
R.string.template__tx,
|
||||
"<a href=\"http://bitshares-explorer.io/#/operations/$transferId\">$transferId</a>"
|
||||
)
|
||||
val html = getString(
|
||||
R.string.template__tx,
|
||||
"<a href=\"http://blocksights.info/#/operations/$transferId\">$transferId</a>"
|
||||
)
|
||||
val tx = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||
binding.tvTransferID.text = tx
|
||||
binding.tvTransferID.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
@ -155,47 +143,13 @@ class EReceiptFragment : Fragment() {
|
|||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == R.id.menu_share) {
|
||||
verifyStoragePermission()
|
||||
shareEReceiptScreenshot()
|
||||
return true
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
/** Verifies if the storage permission is already granted, if that is the case then it takes the screenshot and
|
||||
* shares it but if it is not then it asks the user for that permission */
|
||||
private fun verifyStoragePermission() {
|
||||
if (ContextCompat
|
||||
.checkSelfPermission(activity!!, Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
!= PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
// Permission is not already granted
|
||||
requestPermissions(
|
||||
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
|
||||
REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION
|
||||
)
|
||||
} else {
|
||||
// Permission is already granted
|
||||
shareEReceiptScreenshot()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
|
||||
if (requestCode == REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION) {
|
||||
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
||||
shareEReceiptScreenshot()
|
||||
} else {
|
||||
context?.toast(getString(R.string.msg__storage_permission_necessary_share))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/** Takes a screenshot as a bitmap ( |