Merge branch 'develop'
This commit is contained in:
commit
710a04ac5d
64 changed files with 2378 additions and 913 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -64,3 +64,7 @@ app/fabric.properties
|
|||
|
||||
# Allocation tracker
|
||||
/captures/*
|
||||
|
||||
# Google services info
|
||||
app/google-services.json
|
||||
|
||||
|
|
|
@ -13,13 +13,14 @@ android {
|
|||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName "0.1"
|
||||
versionName "0.8.0-beta"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
// TODO Fix minify issues and enable again
|
||||
minifyEnabled false
|
||||
shrinkResources false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
|
@ -34,6 +35,11 @@ android {
|
|||
exclude 'lib/x86_64/freebsd/libscrypt.so'
|
||||
exclude 'lib/x86_64/linux/libscrypt.so'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@ -47,7 +53,7 @@ dependencies {
|
|||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
// AndroidX
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3'
|
||||
// Google
|
||||
implementation 'com.google.zxing:core:3.3.1'
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
|
@ -67,18 +73,21 @@ dependencies {
|
|||
implementation "com.jakewharton.rxbinding3:rxbinding:$rx_bindings_version"
|
||||
implementation "com.jakewharton.rxbinding3:rxbinding-material:$rx_bindings_version" // Material Components widgets
|
||||
implementation "com.jakewharton.rxbinding3:rxbinding-appcompat:$rx_bindings_version" // AndroidX appcompat widgets
|
||||
// Retrofit
|
||||
// Retrofit & OkHttp
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
|
||||
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
|
||||
implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.12.0'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:3.5.0'
|
||||
//Firebase
|
||||
implementation 'com.google.firebase:firebase-core:16.0.6'
|
||||
implementation 'com.google.firebase:firebase-crash:16.2.1'
|
||||
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.7'
|
||||
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.8'
|
||||
// Others
|
||||
implementation 'org.bitcoinj:bitcoinj-core:0.14.3'
|
||||
implementation 'com.moldedbits.r2d2:r2d2:1.0.1'
|
||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||
implementation 'com.afollestad.material-dialogs:core:2.0.0-rc3'
|
||||
implementation 'com.afollestad.material-dialogs:core:2.0.0-rc7'
|
||||
// Android Debug Database
|
||||
debugImplementation 'com.amitshekhar.android:debug-db:1.0.4'
|
||||
|
||||
|
|
|
@ -44,8 +44,6 @@
|
|||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".activities.LicenseActivity"/>
|
||||
<activity android:name=".activities.ImportBrainkeyActivity"/>
|
||||
<activity
|
||||
android:name=".activities.MainActivity"
|
||||
android:screenOrientation="portrait"
|
||||
|
|
1
app/src/main/assets/brainkeydict.txt
Normal file
1
app/src/main/assets/brainkeydict.txt
Normal file
File diff suppressed because one or more lines are too long
1
app/src/main/assets/eula.html
Normal file
1
app/src/main/assets/eula.html
Normal file
File diff suppressed because one or more lines are too long
|
@ -13,6 +13,7 @@ import android.widget.Toast
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.crashlytics.android.Crashlytics
|
||||
import cy.agorise.bitsybitshareswallet.database.entities.Balance
|
||||
import cy.agorise.bitsybitshareswallet.processors.TransfersLoader
|
||||
import cy.agorise.bitsybitshareswallet.repositories.AssetRepository
|
||||
|
@ -40,16 +41,21 @@ import kotlin.collections.ArrayList
|
|||
import kotlin.collections.HashMap
|
||||
|
||||
/**
|
||||
* Class in charge of managing the connection to graphenej's NetworkService
|
||||
* The app uses the single Activity methodology, but this activity was created so that MainActivity can extend from it.
|
||||
* This class manages everything related to keeping the information in the database updated using graphenej's
|
||||
* NetworkService, leaving to MainActivity only the Navigation work and some other UI features.
|
||||
*/
|
||||
abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
||||
private val TAG = this.javaClass.simpleName
|
||||
|
||||
private val RESPONSE_GET_FULL_ACCOUNTS = 1
|
||||
private val RESPONSE_GET_ACCOUNTS = 2
|
||||
private val RESPONSE_GET_ACCOUNT_BALANCES = 3
|
||||
private val RESPONSE_GET_ASSETS = 4
|
||||
private val RESPONSE_GET_BLOCK_HEADER = 5
|
||||
companion object {
|
||||
private const val TAG = "ConnectedActivity"
|
||||
|
||||
private const val RESPONSE_GET_FULL_ACCOUNTS = 1
|
||||
private const val RESPONSE_GET_ACCOUNTS = 2
|
||||
private const val RESPONSE_GET_ACCOUNT_BALANCES = 3
|
||||
private const val RESPONSE_GET_ASSETS = 4
|
||||
private const val RESPONSE_GET_BLOCK_HEADER = 5
|
||||
}
|
||||
|
||||
private lateinit var mUserAccountViewModel: UserAccountViewModel
|
||||
private lateinit var mBalanceViewModel: BalanceViewModel
|
||||
|
@ -89,10 +95,7 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val userId = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getString(Constants.KEY_CURRENT_ACCOUNT_ID, "")
|
||||
if (userId != "")
|
||||
mCurrentAccount = UserAccount(userId)
|
||||
getUserAccount()
|
||||
|
||||
mAssetRepository = AssetRepository(this)
|
||||
|
||||
|
@ -139,10 +142,19 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
.subscribe { handleIncomingMessage(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the userId from the shared preferences and creates a [UserAccount] instance.
|
||||
* Created as a public function, so that it can be called from its Fragments.
|
||||
*/
|
||||
fun getUserAccount() {
|
||||
val userId = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getString(Constants.KEY_CURRENT_ACCOUNT_ID, "") ?: ""
|
||||
if (userId != "")
|
||||
mCurrentAccount = UserAccount(userId)
|
||||
}
|
||||
|
||||
private fun handleIncomingMessage(message: Any?) {
|
||||
if (message is JsonRpcResponse<*>) {
|
||||
// Generic processing taken care by subclasses
|
||||
handleJsonRpcResponse(message)
|
||||
|
||||
if (message.error == null) {
|
||||
if (responseMap.containsKey(message.id)) {
|
||||
|
@ -178,8 +190,11 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
).show()
|
||||
}
|
||||
} else if (message is ConnectionStatusUpdate) {
|
||||
handleConnectionStatusUpdate(message)
|
||||
if (message.updateCode == ConnectionStatusUpdate.DISCONNECTED) {
|
||||
if (message.updateCode == ConnectionStatusUpdate.CONNECTED) {
|
||||
// Make sure the Crashlytics report contains the currently selected node
|
||||
val selectedNode = mNetworkService?.selectedNode
|
||||
Crashlytics.log(selectedNode?.url)
|
||||
} else if (message.updateCode == ConnectionStatusUpdate.DISCONNECTED) {
|
||||
// If we got a disconnection notification, we should clear our response map, since
|
||||
// all its stored request ids will now be reset
|
||||
responseMap.clear()
|
||||
|
@ -198,7 +213,7 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
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.")
|
||||
mNetworkService!!.removeCurrentNodeAndReconnect()
|
||||
mNetworkService?.removeCurrentNodeAndReconnect()
|
||||
} else if (storedOpCount == -1L) {
|
||||
// Initial case when the app starts
|
||||
storedOpCount = latestOpCount
|
||||
|
@ -291,7 +306,7 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
}
|
||||
|
||||
private fun updateBalances() {
|
||||
if (mNetworkService!!.isConnected) {
|
||||
if (mNetworkService?.isConnected == true) {
|
||||
val id = mNetworkService!!.sendMessage(GetAccountBalances(mCurrentAccount, ArrayList()),
|
||||
GetAccountBalances.REQUIRED_API)
|
||||
|
||||
|
@ -304,7 +319,7 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
*/
|
||||
private val mRequestMissingUserAccountsTask = object : Runnable {
|
||||
override fun run() {
|
||||
if (mNetworkService!!.isConnected) {
|
||||
if (mNetworkService?.isConnected == true) {
|
||||
val id = mNetworkService!!.sendMessage(GetAccounts(missingUserAccounts), GetAccounts.REQUIRED_API)
|
||||
|
||||
responseMap[id] = RESPONSE_GET_ACCOUNTS
|
||||
|
@ -319,7 +334,7 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
*/
|
||||
private val mRequestMissingAssetsTask = object : Runnable {
|
||||
override fun run() {
|
||||
if (mNetworkService!!.isConnected) {
|
||||
if (mNetworkService?.isConnected == true) {
|
||||
val id = mNetworkService!!.sendMessage(GetAssets(missingAssets), GetAssets.REQUIRED_API)
|
||||
|
||||
responseMap[id] = RESPONSE_GET_ASSETS
|
||||
|
@ -334,7 +349,7 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
*/
|
||||
private val mCheckMissingPaymentsTask = object : Runnable {
|
||||
override fun run() {
|
||||
if (mNetworkService != null && mNetworkService!!.isConnected) {
|
||||
if (mNetworkService?.isConnected == true) {
|
||||
if (mCurrentAccount != null) {
|
||||
val userAccounts = ArrayList<String>()
|
||||
userAccounts.add(mCurrentAccount!!.objectId)
|
||||
|
@ -357,7 +372,7 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
private val mRequestBlockMissingTimeTask = object : Runnable {
|
||||
override fun run() {
|
||||
|
||||
if (mNetworkService != null && mNetworkService!!.isConnected) {
|
||||
if (mNetworkService?.isConnected == true) {
|
||||
val id = mNetworkService!!.sendMessage(GetBlockHeader(blockNumberWithMissingTime),
|
||||
GetBlockHeader.REQUIRED_API)
|
||||
|
||||
|
@ -387,6 +402,7 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
mHandler.removeCallbacks(mCheckMissingPaymentsTask)
|
||||
mHandler.removeCallbacks(mRequestMissingUserAccountsTask)
|
||||
mHandler.removeCallbacks(mRequestMissingAssetsTask)
|
||||
mHandler.removeCallbacks(mRequestBlockMissingTimeTask)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
@ -405,16 +421,4 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
super.onDestroy()
|
||||
if (!mDisposable!!.isDisposed) mDisposable!!.dispose()
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to be implemented by all subclasses in order to be notified of JSON-RPC responses.
|
||||
* @param response
|
||||
*/
|
||||
internal abstract fun handleJsonRpcResponse(response: JsonRpcResponse<*>)
|
||||
|
||||
/**
|
||||
* Method to be implemented by all subclasses in order to be notified of connection status updates
|
||||
* @param connectionStatusUpdate
|
||||
*/
|
||||
internal abstract fun handleConnectionStatusUpdate(connectionStatusUpdate: ConnectionStatusUpdate)
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package cy.agorise.bitsybitshareswallet.activities
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import cy.agorise.bitsybitshareswallet.R
|
||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||
import kotlinx.android.synthetic.main.activity_license.*
|
||||
|
||||
class LicenseActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_license)
|
||||
|
||||
// Get version number of the last agreed license version
|
||||
val agreedLicenseVersion = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getInt(Constants.KEY_LAST_AGREED_LICENSE_VERSION, 0)
|
||||
|
||||
// If the last agreed license version is the actual one then proceed to the following Activities
|
||||
if (agreedLicenseVersion == Constants.CURRENT_LICENSE_VERSION) {
|
||||
agree()
|
||||
} else {
|
||||
wbLA.loadData(getString(R.string.licence_html), "text/html", "UTF-8")
|
||||
|
||||
btnDisagree.setOnClickListener { finish() }
|
||||
|
||||
btnAgree.setOnClickListener { agree() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function stores the version of the current accepted license version into the Shared Preferences and
|
||||
* sends the user to import/create account if there is no active account or to the MainActivity otherwise.
|
||||
*/
|
||||
private fun agree() {
|
||||
PreferenceManager.getDefaultSharedPreferences(this).edit()
|
||||
.putInt(Constants.KEY_LAST_AGREED_LICENSE_VERSION, Constants.CURRENT_LICENSE_VERSION).apply()
|
||||
|
||||
val intent : Intent?
|
||||
|
||||
val initialSetupDone = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getBoolean(Constants.KEY_INITIAL_SETUP_DONE, false)
|
||||
|
||||
intent = if (!initialSetupDone)
|
||||
Intent(this, ImportBrainkeyActivity::class.java)
|
||||
else
|
||||
Intent(this, MainActivity::class.java)
|
||||
|
||||
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}
|
||||
}
|
|
@ -12,12 +12,13 @@ import androidx.navigation.ui.onNavDestinationSelected
|
|||
import androidx.navigation.ui.setupActionBarWithNavController
|
||||
import cy.agorise.bitsybitshareswallet.R
|
||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||
import cy.agorise.graphenej.api.ConnectionStatusUpdate
|
||||
import cy.agorise.graphenej.models.JsonRpcResponse
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
|
||||
/**
|
||||
* Uses the AAC Navigation Component with a NavHostFragment which is the place where all Fragments are shown,
|
||||
* following the philosophy of using a single Activity.
|
||||
*/
|
||||
class MainActivity : ConnectedActivity() {
|
||||
private val TAG = this.javaClass.simpleName
|
||||
|
||||
private lateinit var appBarConfiguration : AppBarConfiguration
|
||||
|
||||
|
@ -100,15 +101,12 @@ class MainActivity : ConnectedActivity() {
|
|||
return findNavController(R.id.navHostFragment).navigateUp(appBarConfiguration)
|
||||
}
|
||||
|
||||
override fun handleJsonRpcResponse(response: JsonRpcResponse<*>) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Private method called whenever there's an update to the connection status
|
||||
* @param connectionStatusUpdate Connection status update.
|
||||
*/
|
||||
override fun handleConnectionStatusUpdate(connectionStatusUpdate: ConnectionStatusUpdate) {
|
||||
|
||||
override fun onBackPressed() {
|
||||
// Trick used to avoid crashes when the user is in the License or ImportBrainkey and presses the back button
|
||||
val currentDestination=NavHostFragment.findNavController(navHostFragment).currentDestination
|
||||
when(currentDestination?.id) {
|
||||
R.id.license_dest, R.id.import_brainkey_dest -> finish()
|
||||
else -> super.onBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ class SplashActivity : AppCompatActivity() {
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val intent = Intent(this, LicenseActivity::class.java)
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}
|
||||
|
|
|
@ -9,7 +9,9 @@ import android.widget.TextView
|
|||
import cy.agorise.bitsybitshareswallet.database.joins.BalanceDetail
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Adapter used to populate a Spinner with a list of [BalanceDetail] items.
|
||||
*/
|
||||
class BalancesDetailsAdapter(context: Context, resource: Int, data: List<BalanceDetail>) :
|
||||
ArrayAdapter<BalanceDetail>(context, resource, data) {
|
||||
|
||||
|
|
|
@ -128,7 +128,7 @@ class TransfersDetailsAdapter(private val context: Context) :
|
|||
val cryptoAmount = "${df.format(amount)} ${transferDetail.cryptoSymbol}"
|
||||
viewHolder.tvCryptoAmount.text = cryptoAmount
|
||||
|
||||
viewHolder.tvFiatEquivalent.text = "$4119.75"
|
||||
viewHolder.tvFiatEquivalent.text = "-"
|
||||
|
||||
viewHolder.ivDirectionArrow.setImageDrawable(context.getDrawable(
|
||||
if(transferDetail.direction) R.drawable.ic_arrow_receive else R.drawable.ic_arrow_send
|
||||
|
|
|
@ -15,6 +15,6 @@ interface AssetDao {
|
|||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertAll(assets: List<Asset>)
|
||||
|
||||
@Query("SELECT * FROM assets")
|
||||
fun getAll(): LiveData<List<Asset>>
|
||||
@Query("SELECT id, symbol, precision, description, bit_asset_id FROM assets INNER JOIN balances WHERE assets.id = balances.asset_id AND balances.asset_amount > 0")
|
||||
fun getAllNonZero(): LiveData<List<Asset>>
|
||||
}
|
||||
|
|
|
@ -7,6 +7,6 @@ import androidx.room.Query
|
|||
@Dao
|
||||
interface BalanceDetailDao {
|
||||
@Query("SELECT assets.id AS id, balances.asset_amount AS amount, assets.precision, assets.symbol " +
|
||||
"FROM balances INNER JOIN assets on balances.asset_id = assets.id")
|
||||
"FROM balances INNER JOIN assets on balances.asset_id = assets.id WHERE balances.asset_amount > 0")
|
||||
fun getAll(): LiveData<List<BalanceDetail>>
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package cy.agorise.bitsybitshareswallet.fragments
|
||||
|
||||
import android.preference.PreferenceManager
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import cy.agorise.bitsybitshareswallet.R
|
||||
import cy.agorise.bitsybitshareswallet.database.entities.Authority
|
||||
import cy.agorise.bitsybitshareswallet.repositories.AuthorityRepository
|
||||
import cy.agorise.bitsybitshareswallet.repositories.UserAccountRepository
|
||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||
import cy.agorise.bitsybitshareswallet.utils.CryptoUtils
|
||||
import cy.agorise.graphenej.AuthorityType
|
||||
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() {
|
||||
|
||||
/** Private variable that will hold an instance of the [BrainKey] class */
|
||||
protected var mBrainKey: BrainKey? = null
|
||||
|
||||
/**
|
||||
* Method called internally once an account has been detected. This method will store internally
|
||||
* the following details:
|
||||
*
|
||||
* - Account name in the database
|
||||
* - Account authorities in the database
|
||||
* - The current account id in the shared preferences
|
||||
*
|
||||
* @param accountProperties Account properties object
|
||||
*/
|
||||
protected fun onAccountSelected(accountProperties: AccountProperties, pin: String) {
|
||||
val encryptedPIN = CryptoUtils.encrypt(context!!, pin)
|
||||
|
||||
// Stores the user selected PIN encrypted
|
||||
PreferenceManager.getDefaultSharedPreferences(context!!)
|
||||
.edit()
|
||||
.putString(Constants.KEY_ENCRYPTED_PIN, encryptedPIN)
|
||||
.apply()
|
||||
|
||||
// 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 userAccount = cy.agorise.bitsybitshareswallet.database.entities.UserAccount(id, name, isLTM)
|
||||
|
||||
val userAccountRepository = UserAccountRepository(context!!.applicationContext)
|
||||
userAccountRepository.insert(userAccount)
|
||||
|
||||
// Stores the id of the currently active user account
|
||||
PreferenceManager.getDefaultSharedPreferences(context!!).edit()
|
||||
.putString(Constants.KEY_CURRENT_ACCOUNT_ID, accountProperties.id).apply()
|
||||
|
||||
// Trying to store all possible authorities (owner, active and memo) into the database
|
||||
val ownerAuthority = accountProperties.owner
|
||||
val activeAuthority = accountProperties.active
|
||||
val options = accountProperties.options
|
||||
|
||||
for (i in 0..2) {
|
||||
mBrainKey!!.sequenceNumber = i
|
||||
val publicKey = PublicKey(ECKey.fromPublicOnly(mBrainKey!!.privateKey.pubKey))
|
||||
|
||||
if (ownerAuthority.keyAuths.keys.contains(publicKey)) {
|
||||
addAuthorityToDatabase(accountProperties.id, AuthorityType.OWNER.ordinal, mBrainKey!!)
|
||||
}
|
||||
if (activeAuthority.keyAuths.keys.contains(publicKey)) {
|
||||
addAuthorityToDatabase(accountProperties.id, AuthorityType.ACTIVE.ordinal, mBrainKey!!)
|
||||
}
|
||||
if (options.memoKey == publicKey) {
|
||||
addAuthorityToDatabase(accountProperties.id, AuthorityType.MEMO.ordinal, mBrainKey!!)
|
||||
}
|
||||
}
|
||||
|
||||
// Force [ConnectedActivity] to refresh the userId from the SharedPreferences, so that the app can immediately
|
||||
// to fetch the account's transactions.
|
||||
(activity as ConnectedActivity).getUserAccount()
|
||||
|
||||
// Send the user back to HomeFragment
|
||||
findNavController().navigate(R.id.home_action)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given BrainKey encrypted as AuthorityType of userId.
|
||||
*/
|
||||
private fun addAuthorityToDatabase(userId: String, authorityType: Int, brainKey: BrainKey) {
|
||||
val brainKeyWords = brainKey.brainKey
|
||||
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 authority = Authority(0, userId, authorityType, encryptedWIF, encryptedBrainKey, encryptedSequenceNumber)
|
||||
|
||||
val authorityRepository = AuthorityRepository(context!!)
|
||||
authorityRepository.insert(authority)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package cy.agorise.bitsybitshareswallet.fragments
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import cy.agorise.graphenej.api.ConnectionStatusUpdate
|
||||
import cy.agorise.graphenej.api.android.NetworkService
|
||||
import cy.agorise.graphenej.api.android.RxBus
|
||||
import cy.agorise.graphenej.models.JsonRpcResponse
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
|
||||
/**
|
||||
* Base fragment that defines the methods and variables commonly used in all fragments that directly connect and
|
||||
* talk to the BitShares nodes through graphenej's NetworkService
|
||||
*/
|
||||
abstract class ConnectedFragment : Fragment(), ServiceConnection {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ConnectedFragment"
|
||||
}
|
||||
|
||||
/** Network service connection */
|
||||
protected var mNetworkService: NetworkService? = null
|
||||
|
||||
/** Flag used to keep track of the NetworkService binding state */
|
||||
private var mShouldUnbindNetwork: Boolean = false
|
||||
|
||||
/** Keeps track of all RxJava disposables, to make sure they are all disposed when the fragment is destroyed */
|
||||
protected var mDisposables = CompositeDisposable()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Connect to the RxBus, which receives events from the NetworkService
|
||||
mDisposables.add(
|
||||
RxBus.getBusInstance()
|
||||
.asFlowable()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { handleIncomingMessage(it) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleIncomingMessage(message: Any?) {
|
||||
if (message is JsonRpcResponse<*>) {
|
||||
// Generic processing taken care by subclasses
|
||||
handleJsonRpcResponse(message)
|
||||
} else if (message is ConnectionStatusUpdate) {
|
||||
// Generic processing taken care by subclasses
|
||||
handleConnectionStatusUpdate(message)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
val intent = Intent(context, NetworkService::class.java)
|
||||
if (context?.bindService(intent, this, Context.BIND_AUTO_CREATE) == true) {
|
||||
mShouldUnbindNetwork = true
|
||||
} else {
|
||||
Log.e(TAG, "Binding to the network service failed.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
// Unbinding from network service
|
||||
if (mShouldUnbindNetwork) {
|
||||
context?.unbindService(this)
|
||||
mShouldUnbindNetwork = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
if (!mDisposables.isDisposed) mDisposables.dispose()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) { }
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
// We've bound to LocalService, cast the IBinder and get LocalService instance
|
||||
val binder = service as NetworkService.LocalBinder
|
||||
mNetworkService = binder.service
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to be implemented by all subclasses in order to be notified of JSON-RPC responses.
|
||||
* @param response
|
||||
*/
|
||||
abstract fun handleJsonRpcResponse(response: JsonRpcResponse<*>)
|
||||
|
||||
/**
|
||||
* Method to be implemented by all subclasses in order to be notified of connection status updates
|
||||
* @param connectionStatusUpdate
|
||||
*/
|
||||
abstract fun handleConnectionStatusUpdate(connectionStatusUpdate: ConnectionStatusUpdate)
|
||||
}
|
|
@ -0,0 +1,305 @@
|
|||
package cy.agorise.bitsybitshareswallet.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import cy.agorise.bitsybitshareswallet.R
|
||||
import cy.agorise.bitsybitshareswallet.network.FaucetService
|
||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||
import cy.agorise.bitsybitshareswallet.utils.containsDigits
|
||||
import cy.agorise.bitsybitshareswallet.utils.containsVowels
|
||||
import cy.agorise.bitsybitshareswallet.utils.toast
|
||||
import cy.agorise.graphenej.Address
|
||||
import cy.agorise.graphenej.BrainKey
|
||||
import cy.agorise.graphenej.api.ConnectionStatusUpdate
|
||||
import cy.agorise.graphenej.api.calls.GetAccountByName
|
||||
import cy.agorise.graphenej.models.AccountProperties
|
||||
import cy.agorise.graphenej.models.JsonRpcResponse
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.android.synthetic.main.fragment_create_account.*
|
||||
import org.bitcoinj.core.ECKey
|
||||
import retrofit2.Callback
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.util.concurrent.TimeUnit
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import cy.agorise.bitsybitshareswallet.models.FaucetRequest
|
||||
import cy.agorise.bitsybitshareswallet.models.FaucetResponse
|
||||
import cy.agorise.bitsybitshareswallet.network.ServiceGenerator
|
||||
import retrofit2.Call
|
||||
import retrofit2.Response
|
||||
import java.util.*
|
||||
|
||||
|
||||
class CreateAccountFragment : BaseAccountFragment() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "CreateAccountFragment"
|
||||
|
||||
private const val BRAINKEY_FILE = "brainkeydict.txt"
|
||||
private const val MIN_ACCOUNT_NAME_LENGTH = 8
|
||||
|
||||
// Used when trying to validate that the account name is available
|
||||
private const val RESPONSE_GET_ACCOUNT_BY_NAME_VALIDATION = 1
|
||||
// Used when trying to obtain the info of the newly created account
|
||||
private const val RESPONSE_GET_ACCOUNT_BY_NAME_CREATED = 2
|
||||
}
|
||||
|
||||
private lateinit var mAddress: String
|
||||
|
||||
/** Variables used to store the validation status of the form fields */
|
||||
private var isPINValid = false
|
||||
private var isPINConfirmationValid = false
|
||||
private var isAccountValidAndAvailable = false
|
||||
|
||||
// Map used to keep track of request and response id pairs
|
||||
private val responseMap = HashMap<Long, Int>()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
return inflater.inflate(R.layout.fragment_create_account, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Use RxJava Debounce to check the validity and availability of the user's proposed account name
|
||||
mDisposables.add(
|
||||
tietAccountName.textChanges()
|
||||
.skipInitialValue()
|
||||
.debounce(800, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { validateAccountName(it.toString()) }
|
||||
)
|
||||
|
||||
// Use RxJava Debounce to update the PIN error only after the user stops writing for > 500 ms
|
||||
mDisposables.add(
|
||||
tietPin.textChanges()
|
||||
.skipInitialValue()
|
||||
.debounce(500, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { validatePIN() }
|
||||
)
|
||||
|
||||
// Use RxJava Debounce to update the PIN Confirmation error only after the user stops writing for > 500 ms
|
||||
mDisposables.add(
|
||||
tietPinConfirmation.textChanges()
|
||||
.skipInitialValue()
|
||||
.debounce(500, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { validatePINConfirmation() }
|
||||
)
|
||||
|
||||
btnCancel.setOnClickListener { findNavController().navigateUp() }
|
||||
|
||||
btnCreate.isEnabled = false
|
||||
btnCreate.setOnClickListener { createAccount() }
|
||||
|
||||
// Generating BrainKey
|
||||
generateKeys()
|
||||
}
|
||||
|
||||
private fun validateAccountName(accountName: String) {
|
||||
isAccountValidAndAvailable = false
|
||||
|
||||
if ( !isAccountNameValid(accountName) ) {
|
||||
tilAccountName.helperText = ""
|
||||
tilAccountName.error = getString(R.string.error__invalid_account_name)
|
||||
} else {
|
||||
tilAccountName.isErrorEnabled = false
|
||||
tilAccountName.helperText = getString(R.string.text__verifying_account_availability)
|
||||
val id = mNetworkService?.sendMessage(GetAccountByName(accountName), GetAccountByName.REQUIRED_API)
|
||||
|
||||
if (id != null)
|
||||
responseMap[id] = RESPONSE_GET_ACCOUNT_BY_NAME_VALIDATION
|
||||
}
|
||||
|
||||
enableDisableCreateButton()
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to determine if the account name entered by the user is valid
|
||||
* @param accountName The proposed account name
|
||||
* @return True if the name is valid, false otherwise
|
||||
*/
|
||||
private fun isAccountNameValid(accountName: String): Boolean {
|
||||
return accountName.length >= MIN_ACCOUNT_NAME_LENGTH &&
|
||||
(accountName.containsDigits() || !accountName.containsVowels()) &&
|
||||
!accountName.contains("_")
|
||||
}
|
||||
|
||||
private fun validatePIN() {
|
||||
val pin = tietPin.text.toString()
|
||||
|
||||
if (pin.length < Constants.MIN_PIN_LENGTH) {
|
||||
tilPin.error = getString(R.string.error__pin_too_short)
|
||||
isPINValid = false
|
||||
} else {
|
||||
tilPin.isErrorEnabled = false
|
||||
isPINValid = true
|
||||
}
|
||||
|
||||
validatePINConfirmation()
|
||||
}
|
||||
|
||||
private fun validatePINConfirmation() {
|
||||
val pinConfirmation = tietPinConfirmation.text.toString()
|
||||
|
||||
if (pinConfirmation != tietPin.text.toString()) {
|
||||
tilPinConfirmation.error = getString(R.string.error__pin_mismatch)
|
||||
isPINConfirmationValid = false
|
||||
} else {
|
||||
tilPinConfirmation.isErrorEnabled = false
|
||||
isPINConfirmationValid = true
|
||||
}
|
||||
|
||||
enableDisableCreateButton()
|
||||
}
|
||||
|
||||
private fun enableDisableCreateButton() {
|
||||
btnCreate.isEnabled = (isPINValid && isPINConfirmationValid && isAccountValidAndAvailable)
|
||||
}
|
||||
|
||||
override fun handleJsonRpcResponse(response: JsonRpcResponse<*>) {
|
||||
if (responseMap.containsKey(response.id)) {
|
||||
val responseType = responseMap[response.id]
|
||||
when (responseType) {
|
||||
RESPONSE_GET_ACCOUNT_BY_NAME_VALIDATION -> handleAccountNameValidation(response.result)
|
||||
RESPONSE_GET_ACCOUNT_BY_NAME_CREATED -> handleAccountNameCreated(response.result)
|
||||
}
|
||||
responseMap.remove(response.id)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleConnectionStatusUpdate(connectionStatusUpdate: ConnectionStatusUpdate) {
|
||||
// TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the response from the NetworkService's GetAccountByName call to decide if the user's suggested
|
||||
* account is available or not.
|
||||
*/
|
||||
private fun handleAccountNameValidation(result: Any?) {
|
||||
if (result is AccountProperties) {
|
||||
tilAccountName.helperText = ""
|
||||
tilAccountName.error = getString(R.string.error__account_not_available)
|
||||
isAccountValidAndAvailable = false
|
||||
} else {
|
||||
tilAccountName.isErrorEnabled = false
|
||||
tilAccountName.helperText = getString(R.string.text__account_is_available)
|
||||
isAccountValidAndAvailable = true
|
||||
}
|
||||
|
||||
enableDisableCreateButton()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the response from the NetworkService's GetAccountByName call and stores the information of the newly
|
||||
* created account if the result is successful, shows a toast error otherwise
|
||||
*/
|
||||
private fun handleAccountNameCreated(result: Any?) {
|
||||
if (result is AccountProperties) {
|
||||
onAccountSelected(result, tietPin.text.toString())
|
||||
} else {
|
||||
context?.toast(getString(R.string.error__created_account_not_found))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the account-creation request to the faucet server.
|
||||
* Only account name and public address is sent here.
|
||||
*/
|
||||
private fun createAccount() {
|
||||
val accountName = tietAccountName.text.toString()
|
||||
val faucetRequest = FaucetRequest(accountName, mAddress, Constants.FAUCET_REFERRER)
|
||||
|
||||
val sg = ServiceGenerator(Constants.FAUCET_URL)
|
||||
val faucetService = sg.getService(FaucetService::class.java)
|
||||
|
||||
val call = faucetService.registerPrivateAccount(faucetRequest)
|
||||
|
||||
// Execute the call asynchronously. Get a positive or negative callback.
|
||||
call.enqueue(object : Callback<FaucetResponse> {
|
||||
override fun onResponse(call: Call<FaucetResponse>, response: Response<FaucetResponse>) {
|
||||
// The network call was a success and we got a response, obtain the info of the newly created account
|
||||
// with a delay to let the nodes update their information
|
||||
val handler = Handler()
|
||||
|
||||
handler.postDelayed({
|
||||
getCreatedAccountInfo(response.body())
|
||||
}, 4000)
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<FaucetResponse>, t: Throwable) {
|
||||
// the network call was a failure
|
||||
MaterialDialog(context!!)
|
||||
.title(R.string.title_error)
|
||||
.message(cy.agorise.bitsybitshareswallet.R.string.error__faucet)
|
||||
.negativeButton(android.R.string.ok)
|
||||
.show()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun getCreatedAccountInfo(faucetResponse: FaucetResponse?) {
|
||||
if (faucetResponse?.account != null) {
|
||||
val id = mNetworkService?.sendMessage(GetAccountByName(faucetResponse.account?.name),
|
||||
GetAccountByName.REQUIRED_API)
|
||||
|
||||
if (id != null)
|
||||
responseMap[id] = RESPONSE_GET_ACCOUNT_BY_NAME_CREATED
|
||||
} else {
|
||||
Log.d(TAG, "Private account creation failed ")
|
||||
val content = if (faucetResponse?.error?.base?.size ?: 0 > 0) {
|
||||
getString(R.string.error__faucet_template, faucetResponse?.error?.base?.get(0))
|
||||
} else {
|
||||
getString(R.string.error__faucet_template, "None")
|
||||
}
|
||||
|
||||
MaterialDialog(context!!)
|
||||
.title(R.string.title_error)
|
||||
.message(text = content)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that generates a fresh key that will be controlling the newly created account.
|
||||
*/
|
||||
private fun generateKeys() {
|
||||
var reader: BufferedReader? = null
|
||||
val dictionary: String
|
||||
try {
|
||||
reader = BufferedReader(InputStreamReader(context!!.assets.open(BRAINKEY_FILE), "UTF-8"))
|
||||
dictionary = reader.readLine()
|
||||
|
||||
val brainKeySuggestion = BrainKey.suggest(dictionary)
|
||||
mBrainKey = BrainKey(brainKeySuggestion, 0)
|
||||
val address = Address(ECKey.fromPublicOnly(mBrainKey?.privateKey?.pubKey))
|
||||
Log.d(TAG, "brain key: $brainKeySuggestion")
|
||||
Log.d(TAG, "address would be: " + address.toString())
|
||||
mAddress = address.toString()
|
||||
tvBrainKey.text = mBrainKey?.brainKey
|
||||
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "IOException while trying to generate key. Msg: " + e.message)
|
||||
context?.toast(getString(R.string.error__read_dict_file))
|
||||
} finally {
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close()
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "IOException while trying to close BufferedReader. Msg: " + e.message)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,11 +6,18 @@ import android.content.res.Resources
|
|||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Message
|
||||
import android.view.View
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import android.widget.*
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.lifecycle.Observer
|
||||
import cy.agorise.bitsybitshareswallet.R
|
||||
import cy.agorise.bitsybitshareswallet.adapters.BalancesDetailsAdapter
|
||||
import cy.agorise.bitsybitshareswallet.database.joins.BalanceDetail
|
||||
import cy.agorise.bitsybitshareswallet.viewmodels.BalanceDetailViewModel
|
||||
import cy.agorise.bitsybitshareswallet.views.DatePickerFragment
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.ClassCastException
|
||||
|
@ -22,24 +29,65 @@ import kotlin.ClassCastException
|
|||
*/
|
||||
class FilterOptionsDialog : DialogFragment() {
|
||||
|
||||
companion object {
|
||||
|
||||
const val KEY_FILTER_TRANSACTION_DIRECTION = "key_filter_transaction_direction"
|
||||
const val KEY_FILTER_DATE_RANGE_ALL = "key_filter_date_range_all"
|
||||
const val KEY_FILTER_START_DATE = "key_filter_start_date"
|
||||
const val KEY_FILTER_END_DATE = "key_filter_end_date"
|
||||
const val KEY_FILTER_ASSET_ALL = "key_filter_asset_all"
|
||||
const val KEY_FILTER_ASSET = "key_filter_asset"
|
||||
const val KEY_FILTER_FIAT_AMOUNT_ALL = "key_filter_fiat_amount_all"
|
||||
const val KEY_FILTER_FROM_FIAT_AMOUNT = "key_filter_from_fiat_amount"
|
||||
const val KEY_FILTER_TO_FIAT_AMOUNT = "key_filter_to_fiat_amount"
|
||||
const val KEY_FILTER_AGORISE_FEES = "key_filter_agorise_fees"
|
||||
|
||||
const val KEY_TIMESTAMP = "key_timestamp"
|
||||
|
||||
const val START_DATE_PICKER = 0
|
||||
const val END_DATE_PICKER = 1
|
||||
|
||||
fun newInstance(filterTransactionsDirection: Int, filterDateRangeAll: Boolean,
|
||||
filterStartDate: Long, filterEndDate: Long, filterAssetAll: Boolean,
|
||||
filterAsset: String, filterFiatAmountAll: Boolean,
|
||||
filterFromFiatAmount: Long, filterToFiatAmount: Long, filterAgoriseFees: Boolean): FilterOptionsDialog {
|
||||
val frag = FilterOptionsDialog()
|
||||
val args = Bundle()
|
||||
args.putInt(KEY_FILTER_TRANSACTION_DIRECTION, filterTransactionsDirection)
|
||||
args.putBoolean(KEY_FILTER_DATE_RANGE_ALL, filterDateRangeAll)
|
||||
args.putLong(KEY_FILTER_START_DATE, filterStartDate)
|
||||
args.putLong(KEY_FILTER_END_DATE, filterEndDate)
|
||||
args.putBoolean(KEY_FILTER_ASSET_ALL, filterAssetAll)
|
||||
args.putString(KEY_FILTER_ASSET, filterAsset)
|
||||
args.putBoolean(KEY_FILTER_FIAT_AMOUNT_ALL, filterFiatAmountAll)
|
||||
args.putLong(KEY_FILTER_FROM_FIAT_AMOUNT, filterFromFiatAmount)
|
||||
args.putLong(KEY_FILTER_TO_FIAT_AMOUNT, filterToFiatAmount)
|
||||
args.putBoolean(KEY_FILTER_AGORISE_FEES, filterAgoriseFees)
|
||||
frag.arguments = args
|
||||
return frag
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Widgets TODO use android-kotlin-extensions {onViewCreated}
|
||||
lateinit var rbTransactionAll: RadioButton
|
||||
lateinit var rbTransactionSent: RadioButton
|
||||
lateinit var rbTransactionReceived: RadioButton
|
||||
lateinit var cbDateRange: CheckBox
|
||||
lateinit var llDateRange: LinearLayout
|
||||
lateinit var tvStartDate: TextView
|
||||
lateinit var tvEndDate: TextView
|
||||
lateinit var cbCryptocurrency: CheckBox
|
||||
lateinit var sCryptocurrency: Spinner
|
||||
lateinit var cbFiatAmount: CheckBox
|
||||
lateinit var llFiatAmount: LinearLayout
|
||||
private lateinit var rbTransactionAll: RadioButton
|
||||
private lateinit var rbTransactionSent: RadioButton
|
||||
private lateinit var rbTransactionReceived: RadioButton
|
||||
private lateinit var cbDateRange: CheckBox
|
||||
private lateinit var llDateRange: LinearLayout
|
||||
private lateinit var tvStartDate: TextView
|
||||
private lateinit var tvEndDate: TextView
|
||||
private lateinit var cbAsset: CheckBox
|
||||
private lateinit var sAsset: Spinner
|
||||
private lateinit var cbFiatAmount: CheckBox
|
||||
private lateinit var llFiatAmount: LinearLayout
|
||||
// lateinit var etFromFiatAmount: CurrencyEditText
|
||||
// lateinit var etToFiatAmount: CurrencyEditText
|
||||
private lateinit var switchAgoriseFees: Switch
|
||||
|
||||
private var mCallback: OnFilterOptionsSelectedListener? = null
|
||||
|
||||
private var mDatePickerHandler: DatePickerHandler? = null
|
||||
private lateinit var mDatePickerHandler: DatePickerHandler
|
||||
|
||||
private var dateFormat: SimpleDateFormat = SimpleDateFormat("d/MMM/yyyy",
|
||||
Resources.getSystem().configuration.locale)
|
||||
|
@ -52,43 +100,9 @@ class FilterOptionsDialog : DialogFragment() {
|
|||
// */
|
||||
// private val mUserCurrency = RuntimeData.EXTERNAL_CURRENCY
|
||||
|
||||
companion object {
|
||||
private lateinit var mBalanceDetailViewModel: BalanceDetailViewModel
|
||||
|
||||
const val KEY_FILTER_TRANSACTION_DIRECTION = "key_filter_transaction_direction"
|
||||
const val KEY_FILTER_DATE_RANGE_ALL = "key_filter_date_range_all"
|
||||
const val KEY_FILTER_START_DATE = "key_filter_start_date"
|
||||
const val KEY_FILTER_END_DATE = "key_filter_end_date"
|
||||
const val KEY_FILTER_CRYPTOCURRENCY_ALL = "key_filter_cryptocurrency_all"
|
||||
const val KEY_FILTER_CRYPTOCURRENCY = "key_filter_cryptocurrency"
|
||||
const val KEY_FILTER_FIAT_AMOUNT_ALL = "key_filter_fiat_amount_all"
|
||||
const val KEY_FILTER_FROM_FIAT_AMOUNT = "filter_from_fiat_amount"
|
||||
const val KEY_FILTER_TO_FIAT_AMOUNT = "filter_to_fiat_amount"
|
||||
|
||||
const val KEY_TIMESTAMP = "key_timestamp"
|
||||
|
||||
const val START_DATE_PICKER = 0
|
||||
const val END_DATE_PICKER = 1
|
||||
|
||||
fun newInstance(filterTransactionsDirection: Int, filterDateRangeAll: Boolean,
|
||||
filterStartDate: Long, filterEndDate: Long, filterCryptocurrencyAll: Boolean,
|
||||
filterCryptocurrency: String, filterFiatAmountAll: Boolean,
|
||||
filterFromFiatAmount: Long, filterToFiatAmount: Long): FilterOptionsDialog {
|
||||
val frag = FilterOptionsDialog()
|
||||
val args = Bundle()
|
||||
args.putInt(KEY_FILTER_TRANSACTION_DIRECTION, filterTransactionsDirection)
|
||||
args.putBoolean(KEY_FILTER_DATE_RANGE_ALL, filterDateRangeAll)
|
||||
args.putLong(KEY_FILTER_START_DATE, filterStartDate)
|
||||
args.putLong(KEY_FILTER_END_DATE, filterEndDate)
|
||||
args.putBoolean(KEY_FILTER_CRYPTOCURRENCY_ALL, filterCryptocurrencyAll)
|
||||
args.putString(KEY_FILTER_CRYPTOCURRENCY, filterCryptocurrency)
|
||||
args.putBoolean(KEY_FILTER_FIAT_AMOUNT_ALL, filterFiatAmountAll)
|
||||
args.putLong(KEY_FILTER_FROM_FIAT_AMOUNT, filterFromFiatAmount)
|
||||
args.putLong(KEY_FILTER_TO_FIAT_AMOUNT, filterToFiatAmount)
|
||||
frag.arguments = args
|
||||
return frag
|
||||
}
|
||||
|
||||
}
|
||||
private var mBalancesDetailsAdapter: BalancesDetailsAdapter? = null
|
||||
|
||||
/**
|
||||
* DatePicker message handler.
|
||||
|
@ -139,11 +153,12 @@ class FilterOptionsDialog : DialogFragment() {
|
|||
filterDateRangeAll: Boolean,
|
||||
filterStartDate: Long,
|
||||
filterEndDate: Long,
|
||||
filterCryptocurrencyAll: Boolean,
|
||||
filterCryptocurrency: String,
|
||||
filterAssetAll: Boolean,
|
||||
filterAsset: String,
|
||||
filterFiatAmountAll: Boolean,
|
||||
filterFromFiatAmount: Long,
|
||||
filterToFiatAmount: Long)
|
||||
filterToFiatAmount: Long,
|
||||
filterAgoriseFees: Boolean)
|
||||
}
|
||||
|
||||
|
||||
|
@ -154,9 +169,9 @@ class FilterOptionsDialog : DialogFragment() {
|
|||
mDatePickerHandler = DatePickerHandler()
|
||||
|
||||
val builder = AlertDialog.Builder(context!!)
|
||||
.setTitle("Filter options")
|
||||
.setPositiveButton("Filter") { _, _ -> validateFields() }
|
||||
.setNegativeButton("Cancel") { _, _ -> dismiss() }
|
||||
.setTitle(getString(R.string.title_filter_options))
|
||||
.setPositiveButton(getString(R.string.button__filter)) { _, _ -> validateFields() }
|
||||
.setNegativeButton(getString(android.R.string.cancel)) { _, _ -> dismiss() }
|
||||
|
||||
// Inflate layout
|
||||
val inflater = activity!!.layoutInflater
|
||||
|
@ -175,32 +190,47 @@ class FilterOptionsDialog : DialogFragment() {
|
|||
|
||||
// Initialize Date range
|
||||
cbDateRange = view.findViewById(R.id.cbDateRange)
|
||||
// llDateRange = view.findViewById(R.id.llDateRange)
|
||||
// cbDateRange.setOnCheckedChangeListener { _, isChecked ->
|
||||
// llDateRange.visibility = if(isChecked) View.GONE else View.VISIBLE }
|
||||
llDateRange = view.findViewById(R.id.llDateRange)
|
||||
cbDateRange.setOnCheckedChangeListener { _, isChecked ->
|
||||
llDateRange.visibility = if(isChecked) View.GONE else View.VISIBLE }
|
||||
cbDateRange.isChecked = arguments!!.getBoolean(KEY_FILTER_DATE_RANGE_ALL, true)
|
||||
//
|
||||
// tvStartDate = view.findViewById(R.id.tvStartDate)
|
||||
// tvEndDate = view.findViewById(R.id.tvEndDate)
|
||||
//
|
||||
// startDate = arguments!!.getLong(KEY_FILTER_START_DATE, 0)
|
||||
// tvStartDate.setOnClickListener(mDateClickListener)
|
||||
//
|
||||
// endDate = arguments!!.getLong(KEY_FILTER_END_DATE, 0)
|
||||
// tvEndDate.setOnClickListener(mDateClickListener)
|
||||
//
|
||||
// updateDateTextViews()
|
||||
|
||||
// Initialize Cryptocurrency
|
||||
cbCryptocurrency = view.findViewById(R.id.cbCryptocurrency)
|
||||
// sCryptocurrency = view.findViewById(R.id.sCryptocurrency)
|
||||
// cbCryptocurrency.setOnCheckedChangeListener { _, isChecked ->
|
||||
// sCryptocurrency.visibility = if(isChecked) View.GONE else View.VISIBLE }
|
||||
cbCryptocurrency.isChecked = arguments!!.getBoolean(KEY_FILTER_CRYPTOCURRENCY_ALL, true)
|
||||
tvStartDate = view.findViewById(R.id.tvStartDate)
|
||||
tvEndDate = view.findViewById(R.id.tvEndDate)
|
||||
|
||||
// sCryptocurrency = view.findViewById(R.id.sCryptocurrency)
|
||||
// initializeCryptocurrencySpinner()
|
||||
startDate = arguments!!.getLong(KEY_FILTER_START_DATE, 0)
|
||||
tvStartDate.setOnClickListener(mDateClickListener)
|
||||
|
||||
endDate = arguments!!.getLong(KEY_FILTER_END_DATE, 0)
|
||||
tvEndDate.setOnClickListener(mDateClickListener)
|
||||
|
||||
updateDateTextViews()
|
||||
|
||||
// Initialize Asset
|
||||
cbAsset = view.findViewById(R.id.cbAsset)
|
||||
sAsset = view.findViewById(R.id.sAsset)
|
||||
cbAsset.setOnCheckedChangeListener { _, isChecked ->
|
||||
sAsset.visibility = if(isChecked) View.GONE else View.VISIBLE
|
||||
}
|
||||
cbAsset.isChecked = arguments!!.getBoolean(KEY_FILTER_ASSET_ALL, true)
|
||||
|
||||
// Configure BalanceDetailViewModel to obtain the user's Balances
|
||||
mBalanceDetailViewModel = ViewModelProviders.of(this).get(BalanceDetailViewModel::class.java)
|
||||
|
||||
mBalanceDetailViewModel.getAll().observe(this, Observer<List<BalanceDetail>> { balancesDetails ->
|
||||
mBalancesDetailsAdapter = BalancesDetailsAdapter(context!!, android.R.layout.simple_spinner_item, balancesDetails!!)
|
||||
sAsset.adapter = mBalancesDetailsAdapter
|
||||
|
||||
val assetSelected = arguments!!.getString(KEY_FILTER_ASSET)
|
||||
|
||||
// Try to select the selectedAssetSymbol
|
||||
for (i in 0 until mBalancesDetailsAdapter!!.count) {
|
||||
if (mBalancesDetailsAdapter!!.getItem(i)!!.symbol == assetSelected) {
|
||||
sAsset.setSelection(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Initialize Fiat amount
|
||||
cbFiatAmount = view.findViewById(R.id.cbFiatAmount)
|
||||
|
@ -221,6 +251,10 @@ class FilterOptionsDialog : DialogFragment() {
|
|||
// val toFiatAmount = arguments!!.getLong(KEY_FILTER_TO_FIAT_AMOUNT, 0)
|
||||
// etToFiatAmount.setText("$toFiatAmount", TextView.BufferType.EDITABLE)
|
||||
|
||||
// Initialize transaction network fees
|
||||
switchAgoriseFees = view.findViewById(R.id.switchAgoriseFees)
|
||||
switchAgoriseFees.isChecked = arguments!!.getBoolean(KEY_FILTER_AGORISE_FEES, true)
|
||||
|
||||
builder.setView(view)
|
||||
|
||||
return builder.create()
|
||||
|
@ -238,48 +272,29 @@ class FilterOptionsDialog : DialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
// private fun initializeCryptocurrencySpinner() {
|
||||
// val cryptoCurrencyList = database!!.getSortedCryptoCurrencies(false,
|
||||
// SortType.DESCENDING, true)
|
||||
//
|
||||
// val cryptocurrencySpinnerAdapter = CryptocurrencySpinnerAdapter(context!!,
|
||||
// R.layout.item_cryptocurrency,
|
||||
// R.id.tvCryptocurrencyName,
|
||||
// cryptoCurrencyList)
|
||||
//
|
||||
// sCryptocurrency.adapter = cryptocurrencySpinnerAdapter
|
||||
//
|
||||
// val cryptocurrencySelected = arguments!!.getString(KEY_FILTER_CRYPTOCURRENCY)
|
||||
//
|
||||
// val index = Math.max(cryptocurrencySpinnerAdapter.getPosition(database!!.getCryptocurrencyBySymbol(
|
||||
// cryptocurrencySelected)), 0)
|
||||
//
|
||||
// sCryptocurrency.setSelection(index)
|
||||
// }
|
||||
private val mDateClickListener = View.OnClickListener { v ->
|
||||
val calendar = Calendar.getInstance()
|
||||
|
||||
// private val mDateClickListener = View.OnClickListener { v ->
|
||||
// val calendar = Calendar.getInstance()
|
||||
//
|
||||
// // Variable used to select that date on the calendar
|
||||
// var currentTime = calendar.timeInMillis
|
||||
// var maxTime = currentTime
|
||||
//
|
||||
// var which = -1
|
||||
// if (v.id == R.id.tvStartDate) {
|
||||
// which = START_DATE_PICKER
|
||||
// currentTime = startDate
|
||||
// calendar.timeInMillis = endDate
|
||||
// calendar.add(Calendar.MONTH, -1)
|
||||
// maxTime = calendar.timeInMillis
|
||||
// } else if (v.id == R.id.tvEndDate) {
|
||||
// which = END_DATE_PICKER
|
||||
// currentTime = endDate
|
||||
// }
|
||||
//
|
||||
// val datePickerFragment = DatePickerFragment.newInstance(which, currentTime,
|
||||
// maxTime, mDatePickerHandler)
|
||||
// datePickerFragment.show(activity!!.supportFragmentManager, "date-picker")
|
||||
// }
|
||||
// Variable used to select that date on the calendar
|
||||
var currentTime = calendar.timeInMillis
|
||||
var maxTime = currentTime
|
||||
|
||||
var which = -1
|
||||
if (v.id == R.id.tvStartDate) {
|
||||
which = START_DATE_PICKER
|
||||
currentTime = startDate
|
||||
calendar.timeInMillis = endDate
|
||||
calendar.add(Calendar.MONTH, -1)
|
||||
maxTime = calendar.timeInMillis
|
||||
} else if (v.id == R.id.tvEndDate) {
|
||||
which = END_DATE_PICKER
|
||||
currentTime = endDate
|
||||
}
|
||||
|
||||
val datePickerFragment = DatePickerFragment.newInstance(which, currentTime,
|
||||
maxTime, mDatePickerHandler)
|
||||
datePickerFragment.show(activity!!.supportFragmentManager, "date-picker")
|
||||
}
|
||||
|
||||
private fun validateFields() {
|
||||
val filterTransactionsDirection = when {
|
||||
|
@ -291,9 +306,9 @@ class FilterOptionsDialog : DialogFragment() {
|
|||
|
||||
val filterDateRangeAll = cbDateRange.isChecked
|
||||
|
||||
val filterCryptocurrencyAll = cbCryptocurrency.isChecked
|
||||
val filterAssetAll = cbAsset.isChecked
|
||||
|
||||
val filterCryptocurrency = "" //(sCryptocurrency.selectedItem as CryptoCurrency).symbol
|
||||
val filterAsset = (sAsset.selectedItem as BalanceDetail).symbol
|
||||
|
||||
val filterFiatAmountAll = cbFiatAmount.isChecked
|
||||
|
||||
|
@ -309,8 +324,10 @@ class FilterOptionsDialog : DialogFragment() {
|
|||
// Math.pow(10.0, mUserCurrency.defaultFractionDigits.toDouble()).toLong()
|
||||
// }
|
||||
|
||||
val filterAgoriseFees = switchAgoriseFees.isChecked
|
||||
|
||||
mCallback!!.onFilterOptionsSelected(filterTransactionsDirection, filterDateRangeAll,
|
||||
startDate, endDate, filterCryptocurrencyAll, filterCryptocurrency, filterFiatAmountAll,
|
||||
filterFromFiatAmount, filterToFiatAmount)
|
||||
startDate, endDate, filterAssetAll, filterAsset, filterFiatAmountAll,
|
||||
filterFromFiatAmount, filterToFiatAmount, filterAgoriseFees)
|
||||
}
|
||||
}
|
|
@ -3,9 +3,9 @@ package cy.agorise.bitsybitshareswallet.fragments
|
|||
import androidx.lifecycle.ViewModelProviders
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentPagerAdapter
|
||||
|
@ -23,14 +23,21 @@ class HomeFragment : Fragment() {
|
|||
|
||||
private lateinit var mUserAccountViewModel: UserAccountViewModel
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
val nightMode = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean(Constants.KEY_NIGHT_MODE_ACTIVATED, false)
|
||||
|
||||
// Sets the toolbar background color to primaryColor and forces shows the Bitsy icon to the left
|
||||
val toolbar: Toolbar? = activity?.findViewById(R.id.toolbar)
|
||||
toolbar?.navigationIcon = resources.getDrawable(R.drawable.ic_bitsy_logo_2, null)
|
||||
toolbar?.setBackgroundResource(if (!nightMode) R.color.colorPrimary else R.color.colorToolbarDark)
|
||||
|
||||
// Sets the status bar background color to a primaryColorDark
|
||||
val window = activity?.window
|
||||
window?.statusBarColor = ContextCompat.getColor(context!!,
|
||||
if (!nightMode) R.color.colorPrimaryDark else R.color.colorStatusBarDark)
|
||||
|
||||
return inflater.inflate(R.layout.fragment_home, container, false)
|
||||
}
|
||||
|
@ -38,14 +45,23 @@ class HomeFragment : Fragment() {
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Get version number of the last agreed license version
|
||||
val agreedLicenseVersion = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getInt(Constants.KEY_LAST_AGREED_LICENSE_VERSION, 0)
|
||||
|
||||
val userId = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getString(Constants.KEY_CURRENT_ACCOUNT_ID, "") ?: ""
|
||||
|
||||
if (agreedLicenseVersion != Constants.CURRENT_LICENSE_VERSION || userId == "") {
|
||||
findNavController().navigate(R.id.license_action)
|
||||
return
|
||||
}
|
||||
|
||||
// Configure UserAccountViewModel to show the current account
|
||||
mUserAccountViewModel = ViewModelProviders.of(this).get(UserAccountViewModel::class.java)
|
||||
|
||||
val userId = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getString(Constants.KEY_CURRENT_ACCOUNT_ID, "")
|
||||
|
||||
mUserAccountViewModel.getUserAccount(userId!!).observe(this, Observer<UserAccount>{ user ->
|
||||
tvAccountName.text = user.name
|
||||
mUserAccountViewModel.getUserAccount(userId).observe(this, Observer<UserAccount>{ user ->
|
||||
tvAccountName.text = user?.name ?: ""
|
||||
})
|
||||
|
||||
// Navigate to the Receive Transaction Fragment
|
||||
|
@ -61,7 +77,7 @@ class HomeFragment : Fragment() {
|
|||
// Navigate to the Send Transaction Fragment using Navigation's SafeArgs to activate the camera
|
||||
fabSendTransactionCamera.setOnClickListener {
|
||||
val action = HomeFragmentDirections.sendActionCamera()
|
||||
action.setOpenCamera(true)
|
||||
action.openCamera = true
|
||||
findNavController().navigate(action)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,46 +1,50 @@
|
|||
package cy.agorise.bitsybitshareswallet.activities
|
||||
package cy.agorise.bitsybitshareswallet.fragments
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.ComponentName
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.navigation.Navigation
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.callbacks.onDismiss
|
||||
import com.afollestad.materialdialogs.list.customListAdapter
|
||||
import com.afollestad.materialdialogs.list.listItemsSingleChoice
|
||||
import com.jakewharton.rxbinding3.widget.textChanges
|
||||
import cy.agorise.bitsybitshareswallet.BuildConfig
|
||||
import cy.agorise.bitsybitshareswallet.R
|
||||
import cy.agorise.bitsybitshareswallet.database.entities.Authority
|
||||
import cy.agorise.bitsybitshareswallet.repositories.AuthorityRepository
|
||||
import cy.agorise.bitsybitshareswallet.repositories.UserAccountRepository
|
||||
import cy.agorise.bitsybitshareswallet.adapters.FullNodesAdapter
|
||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||
import cy.agorise.bitsybitshareswallet.utils.CryptoUtils
|
||||
import cy.agorise.bitsybitshareswallet.utils.toast
|
||||
import cy.agorise.graphenej.*
|
||||
import cy.agorise.graphenej.api.ConnectionStatusUpdate
|
||||
import cy.agorise.graphenej.api.calls.GetAccounts
|
||||
import cy.agorise.graphenej.api.calls.GetDynamicGlobalProperties
|
||||
import cy.agorise.graphenej.api.calls.GetKeyReferences
|
||||
import cy.agorise.graphenej.models.AccountProperties
|
||||
import cy.agorise.graphenej.models.DynamicGlobalProperties
|
||||
import cy.agorise.graphenej.models.JsonRpcResponse
|
||||
import cy.agorise.graphenej.network.FullNode
|
||||
import io.reactivex.Observer
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import kotlinx.android.synthetic.main.activity_import_brainkey.*
|
||||
import io.reactivex.disposables.Disposable
|
||||
import kotlinx.android.synthetic.main.fragment_import_brainkey.*
|
||||
import org.bitcoinj.core.ECKey
|
||||
import java.text.NumberFormat
|
||||
import java.util.ArrayList
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
// TODO Add method to load the 20? most important assets
|
||||
// TODO add progress bar or something while the user waits for the import response from the node
|
||||
class ImportBrainkeyFragment : BaseAccountFragment() {
|
||||
|
||||
class ImportBrainkeyActivity : ConnectedActivity() {
|
||||
private val TAG = "ImportBrainkeyActivity"
|
||||
companion object {
|
||||
private const val TAG = "ImportBrainkeyActivity"
|
||||
}
|
||||
|
||||
/**
|
||||
* Private variable that will hold an instance of the [BrainKey] class
|
||||
*/
|
||||
private var mBrainKey: BrainKey? = null
|
||||
|
||||
/**
|
||||
* User account associated with the key derived from the brainkey that the user just typed in
|
||||
*/
|
||||
/** User account associated with the key derived from the brainkey that the user just typed in */
|
||||
private var mUserAccount: UserAccount? = null
|
||||
|
||||
/**
|
||||
|
@ -51,18 +55,32 @@ class ImportBrainkeyActivity : ConnectedActivity() {
|
|||
|
||||
private var mKeyReferencesAttempts = 0
|
||||
|
||||
private var keyReferencesRequestId: Long = 0
|
||||
private var getAccountsRequestId: Long = 0
|
||||
|
||||
private var mDisposables = CompositeDisposable()
|
||||
private var keyReferencesRequestId: Long? = null
|
||||
private var getAccountsRequestId: Long? = null
|
||||
|
||||
private var isPINValid = false
|
||||
private var isPINConfirmationValid = false
|
||||
private var isBrainKeyValid = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_import_brainkey)
|
||||
// Dialog displaying the list of nodes and their latencies
|
||||
private var mNodesDialog: MaterialDialog? = null
|
||||
|
||||
/** Adapter that holds the FullNode list used in the Bitshares nodes modal */
|
||||
private var mNodesAdapter: FullNodesAdapter? = null
|
||||
|
||||
/** Handler that will be used to make recurrent calls to get the latest BitShares block number*/
|
||||
private val mHandler = Handler()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
// Remove up navigation icon from the toolbar
|
||||
val toolbar: Toolbar? = activity?.findViewById(R.id.toolbar)
|
||||
toolbar?.navigationIcon = null
|
||||
|
||||
return inflater.inflate(R.layout.fragment_import_brainkey, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Use RxJava Debounce to update the PIN error only after the user stops writing for > 500 ms
|
||||
mDisposables.add(
|
||||
|
@ -94,6 +112,35 @@ class ImportBrainkeyActivity : ConnectedActivity() {
|
|||
|
||||
btnImport.isEnabled = false
|
||||
btnImport.setOnClickListener { verifyBrainKey(false) }
|
||||
|
||||
btnCreate.setOnClickListener (
|
||||
Navigation.createNavigateOnClickListener(R.id.create_account_action)
|
||||
)
|
||||
|
||||
tvNetworkStatus.setOnClickListener { v ->
|
||||
if (mNetworkService != null) {
|
||||
// PublishSubject used to announce full node latencies updates
|
||||
val fullNodePublishSubject = mNetworkService!!.nodeLatencyObservable
|
||||
fullNodePublishSubject?.observeOn(AndroidSchedulers.mainThread())?.subscribe(nodeLatencyObserver)
|
||||
|
||||
val fullNodes = mNetworkService!!.nodes
|
||||
|
||||
mNodesAdapter = FullNodesAdapter(v.context)
|
||||
mNodesAdapter?.add(fullNodes)
|
||||
|
||||
mNodesDialog = MaterialDialog(v.context)
|
||||
.title(text = String.format("%s v%s", getString(R.string.app_name), BuildConfig.VERSION_NAME))
|
||||
.message(text = getString(R.string.title__bitshares_nodes_dialog, "-------"))
|
||||
.customListAdapter(mNodesAdapter as FullNodesAdapter)
|
||||
.negativeButton(android.R.string.ok)
|
||||
.onDismiss { mHandler.removeCallbacks(mRequestDynamicGlobalPropertiesTask) }
|
||||
|
||||
mNodesDialog?.show()
|
||||
|
||||
// Registering a recurrent task used to poll for dynamic global properties requests
|
||||
mHandler.post(mRequestDynamicGlobalPropertiesTask)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun validatePIN() {
|
||||
|
@ -161,7 +208,7 @@ class ImportBrainkeyActivity : ConnectedActivity() {
|
|||
*/
|
||||
private fun verifyBrainKey(switchCase: Boolean) {
|
||||
//showDialog("", getString(R.string.importing_your_wallet))
|
||||
val brainKey = tietBrainKey.text.toString()
|
||||
val brainKey = tietBrainKey.text.toString().trim()
|
||||
// Should we switch the brainkey case?
|
||||
if (switchCase) {
|
||||
if (Character.isUpperCase(brainKey.toCharArray()[brainKey.length - 1])) {
|
||||
|
@ -189,70 +236,19 @@ class ImportBrainkeyActivity : ConnectedActivity() {
|
|||
mBrainKey = BrainKey(brainKey, 0)
|
||||
val address = Address(ECKey.fromPublicOnly(mBrainKey!!.privateKey.pubKey))
|
||||
Log.d(TAG, String.format("Brainkey would generate address: %s", address.toString()))
|
||||
keyReferencesRequestId = mNetworkService!!.sendMessage(GetKeyReferences(address), GetKeyReferences.REQUIRED_API)
|
||||
keyReferencesRequestId = mNetworkService?.sendMessage(GetKeyReferences(address), GetKeyReferences.REQUIRED_API)
|
||||
}
|
||||
|
||||
override fun handleJsonRpcResponse(response: JsonRpcResponse<*>) {
|
||||
Log.d(TAG, "handleResponse.Thread: " + Thread.currentThread().name)
|
||||
if (response.id == keyReferencesRequestId) {
|
||||
val resp = response.result as List<List<UserAccount>>
|
||||
val accountList: List<UserAccount> = resp[0].distinct()
|
||||
if (accountList.isEmpty() && mKeyReferencesAttempts == 0) {
|
||||
mKeyReferencesAttempts++
|
||||
verifyBrainKey(true)
|
||||
} else {
|
||||
if (accountList.isEmpty()) {
|
||||
//hideDialog()
|
||||
Toast.makeText(applicationContext, R.string.error__invalid_brainkey, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
if (accountList.size == 1) {
|
||||
// If we only found one account linked to this key, then we just proceed
|
||||
// trying to find out the account name
|
||||
mUserAccount = accountList[0]
|
||||
getAccountsRequestId =
|
||||
mNetworkService!!.sendMessage(GetAccounts(mUserAccount), GetAccounts.REQUIRED_API)
|
||||
} else {
|
||||
// If we found more than one account linked to this key, we must also
|
||||
// find out the account names, but the procedure is a bit different in
|
||||
// that after having those, we must still ask the user to decide which
|
||||
// account should be imported.
|
||||
mUserAccountCandidates = accountList
|
||||
getAccountsRequestId = mNetworkService!!.sendMessage(
|
||||
GetAccounts(mUserAccountCandidates),
|
||||
GetAccounts.REQUIRED_API
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
handleBrainKeyAccountReferences(response.result)
|
||||
} else if (response.id == getAccountsRequestId) {
|
||||
val accountPropertiesList = response.result as List<AccountProperties>
|
||||
if (accountPropertiesList.size > 1) {
|
||||
val candidates = ArrayList<String>()
|
||||
for (accountProperties in accountPropertiesList) {
|
||||
candidates.add(accountProperties.name)
|
||||
}
|
||||
// hideDialog()
|
||||
MaterialDialog(this)
|
||||
.title(R.string.dialog__account_candidates_title)
|
||||
.message(R.string.dialog__account_candidates_content)
|
||||
.listItemsSingleChoice (items = candidates, initialSelection = -1) { _, index, _ ->
|
||||
if (index >= 0) {
|
||||
// If one account was selected, we keep a reference to it and
|
||||
// store the account properties
|
||||
mUserAccount = mUserAccountCandidates!![index]
|
||||
onAccountSelected(accountPropertiesList[index])
|
||||
}
|
||||
}
|
||||
.positiveButton(android.R.string.ok)
|
||||
.negativeButton(android.R.string.cancel) {
|
||||
mKeyReferencesAttempts = 0
|
||||
}
|
||||
.cancelable(false)
|
||||
.show()
|
||||
} else if (accountPropertiesList.size == 1) {
|
||||
onAccountSelected(accountPropertiesList[0])
|
||||
} else {
|
||||
Toast.makeText(applicationContext, R.string.error__try_again, Toast.LENGTH_SHORT).show()
|
||||
handleAccountProperties(response.result)
|
||||
} else if (response.result is DynamicGlobalProperties) {
|
||||
val dynamicGlobalProperties = response.result as DynamicGlobalProperties
|
||||
if (mNodesDialog != null && mNodesDialog?.isShowing == true) {
|
||||
val blockNumber = NumberFormat.getInstance().format(dynamicGlobalProperties.head_block_number)
|
||||
mNodesDialog?.message(text = getString(R.string.title__bitshares_nodes_dialog, blockNumber))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -262,96 +258,132 @@ class ImportBrainkeyActivity : ConnectedActivity() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Method called internally once an account has been detected. This method will store internally
|
||||
* the following details:
|
||||
*
|
||||
* - Account name in the database
|
||||
* - Account authorities in the database
|
||||
* - The current account id in the shared preferences
|
||||
*
|
||||
* @param accountProperties Account properties object
|
||||
* Handles the response from the NetworkService when the app asks for the accounts that are controlled by a
|
||||
* specified BrainKey
|
||||
*/
|
||||
private fun onAccountSelected(accountProperties: AccountProperties) {
|
||||
mUserAccount!!.name = accountProperties.name
|
||||
|
||||
val encryptedPIN = CryptoUtils.encrypt(this, tietPin.text!!.toString())
|
||||
|
||||
// Stores the user selected PIN encrypted
|
||||
PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.edit()
|
||||
.putString(Constants.KEY_ENCRYPTED_PIN, encryptedPIN)
|
||||
.apply()
|
||||
|
||||
// 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 userAccount = cy.agorise.bitsybitshareswallet.database.entities.UserAccount(id, name, isLTM)
|
||||
|
||||
val userAccountRepository = UserAccountRepository(application)
|
||||
userAccountRepository.insert(userAccount)
|
||||
|
||||
// Stores the id of the currently active user account
|
||||
PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.edit()
|
||||
.putString(Constants.KEY_CURRENT_ACCOUNT_ID, mUserAccount!!.objectId)
|
||||
.apply()
|
||||
|
||||
// Trying to store all possible authorities (owner, active and memo) into the database
|
||||
val ownerAuthority = accountProperties.owner
|
||||
val activeAuthority = accountProperties.active
|
||||
val options = accountProperties.options
|
||||
|
||||
for (i in 0..2) {
|
||||
mBrainKey!!.sequenceNumber = i
|
||||
val publicKey = PublicKey(ECKey.fromPublicOnly(mBrainKey!!.privateKey.pubKey))
|
||||
|
||||
if (ownerAuthority.keyAuths.keys.contains(publicKey)) {
|
||||
addAuthorityToDatabase(accountProperties.id, AuthorityType.OWNER.ordinal, mBrainKey!!)
|
||||
}
|
||||
if (activeAuthority.keyAuths.keys.contains(publicKey)) {
|
||||
addAuthorityToDatabase(accountProperties.id, AuthorityType.ACTIVE.ordinal, mBrainKey!!)
|
||||
}
|
||||
if (options.memoKey == publicKey) {
|
||||
addAuthorityToDatabase(accountProperties.id, AuthorityType.MEMO.ordinal, mBrainKey!!)
|
||||
}
|
||||
private fun handleBrainKeyAccountReferences(result: Any?) {
|
||||
if (result !is List<*> || result[0] !is List<*>) {
|
||||
context?.toast(getString(R.string.error__invalid_brainkey))
|
||||
return
|
||||
}
|
||||
|
||||
// Stores a flag into the SharedPreferences to tell the app there is an active account and there is no need
|
||||
// to show this activity again, until the account is removed.
|
||||
PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.edit()
|
||||
.putBoolean(Constants.KEY_INITIAL_SETUP_DONE, true)
|
||||
.apply()
|
||||
val resp = result as List<List<UserAccount>>
|
||||
val accountList: List<UserAccount> = resp[0].distinct()
|
||||
|
||||
// Send the user to the MainActivity
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
if (accountList.isEmpty()) {
|
||||
if (mKeyReferencesAttempts == 0) {
|
||||
mKeyReferencesAttempts++
|
||||
verifyBrainKey(true)
|
||||
} else {
|
||||
context?.toast(getString(R.string.error__invalid_brainkey))
|
||||
}
|
||||
} else if (accountList.size == 1) {
|
||||
// If we only found one account linked to this key, then we just proceed
|
||||
// trying to find out the account name
|
||||
mUserAccount = accountList[0]
|
||||
getAccountsRequestId =
|
||||
mNetworkService?.sendMessage(GetAccounts(mUserAccount), GetAccounts.REQUIRED_API)
|
||||
} else {
|
||||
// If we found more than one account linked to this key, we must also
|
||||
// find out the account names, but the procedure is a bit different in
|
||||
// that after having those, we must still ask the user to decide which
|
||||
// account should be imported.
|
||||
mUserAccountCandidates = accountList
|
||||
getAccountsRequestId = mNetworkService?.sendMessage(
|
||||
GetAccounts(mUserAccountCandidates),
|
||||
GetAccounts.REQUIRED_API
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given BrainKey encrypted as AuthorityType of userId.
|
||||
* Handles the response from the NetworkService when the app asks for the AccountProperties of a list of
|
||||
* Accounts controlled by the given BrainKey
|
||||
*/
|
||||
private fun addAuthorityToDatabase(userId: String, authorityType: Int, brainKey: BrainKey) {
|
||||
val brainKeyWords = brainKey.brainKey
|
||||
val wif = brainKey.walletImportFormat
|
||||
val sequenceNumber = brainKey.sequenceNumber
|
||||
|
||||
val encryptedBrainKey = CryptoUtils.encrypt(this, brainKeyWords)
|
||||
val encryptedSequenceNumber = CryptoUtils.encrypt(this, sequenceNumber.toString())
|
||||
val encryptedWIF = CryptoUtils.encrypt(this, wif)
|
||||
|
||||
val authority = Authority(0, userId, authorityType, encryptedWIF, encryptedBrainKey, encryptedSequenceNumber)
|
||||
|
||||
val authorityRepository = AuthorityRepository(this)
|
||||
authorityRepository.insert(authority)
|
||||
private fun handleAccountProperties(result: Any?) {
|
||||
if (result is List<*> && result[0] is AccountProperties) {
|
||||
val accountPropertiesList = result as List<AccountProperties>
|
||||
if (accountPropertiesList.size > 1) {
|
||||
val candidates = ArrayList<String>()
|
||||
for (accountProperties in accountPropertiesList) {
|
||||
candidates.add(accountProperties.name)
|
||||
}
|
||||
MaterialDialog(context!!)
|
||||
.title(R.string.dialog__account_candidates_title)
|
||||
.message(R.string.dialog__account_candidates_content)
|
||||
.listItemsSingleChoice (items = candidates, initialSelection = -1) { _, index, _ ->
|
||||
if (index >= 0) {
|
||||
// If one account was selected, we keep a reference to it and
|
||||
// store the account properties
|
||||
mUserAccount = mUserAccountCandidates!![index]
|
||||
onAccountSelected(accountPropertiesList[index], tietPin.text.toString())
|
||||
}
|
||||
}
|
||||
.positiveButton(android.R.string.ok)
|
||||
.negativeButton(android.R.string.cancel) {
|
||||
mKeyReferencesAttempts = 0
|
||||
}
|
||||
.cancelable(false)
|
||||
.show()
|
||||
} else if (accountPropertiesList.size == 1) {
|
||||
onAccountSelected(accountPropertiesList[0], tietPin.text.toString())
|
||||
} else {
|
||||
context?.toast(getString(R.string.error__try_again))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
/**
|
||||
* Observer used to be notified about node latency measurement updates.
|
||||
*/
|
||||
private val nodeLatencyObserver = object : Observer<FullNode> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
mDisposables.add(d)
|
||||
}
|
||||
|
||||
if (!mDisposables.isDisposed) mDisposables.dispose()
|
||||
override fun onNext(fullNode: FullNode) {
|
||||
if (!fullNode.isRemoved)
|
||||
mNodesAdapter?.add(fullNode)
|
||||
else
|
||||
mNodesAdapter?.remove(fullNode)
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
Log.e(TAG, "nodeLatencyObserver.onError.Msg: " + e.message)
|
||||
}
|
||||
|
||||
override fun onComplete() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Task used to obtain frequent updates on the global dynamic properties object
|
||||
*/
|
||||
private val mRequestDynamicGlobalPropertiesTask = object : Runnable {
|
||||
override fun run() {
|
||||
if (mNetworkService != null) {
|
||||
if (mNetworkService?.isConnected == true) {
|
||||
mNetworkService?.sendMessage(GetDynamicGlobalProperties(), GetDynamicGlobalProperties.REQUIRED_API)
|
||||
} else {
|
||||
Log.d(TAG, "NetworkService exists but is not connected")
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "NetworkService reference is null")
|
||||
}
|
||||
mHandler.postDelayed(this, Constants.BLOCK_PERIOD)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
super.onServiceDisconnected(name)
|
||||
|
||||
tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null,
|
||||
resources.getDrawable(R.drawable.ic_disconnected, null), null)
|
||||
}
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
super.onServiceConnected(name, service)
|
||||
|
||||
tvNetworkStatus.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null,
|
||||
resources.getDrawable(R.drawable.ic_connected, null), null)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package cy.agorise.bitsybitshareswallet.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import cy.agorise.bitsybitshareswallet.R
|
||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||
import kotlinx.android.synthetic.main.fragment_license.*
|
||||
|
||||
class LicenseFragment : Fragment() {
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
// Remove up navigation icon from the toolbar
|
||||
val toolbar: Toolbar? = activity?.findViewById(R.id.toolbar)
|
||||
toolbar?.navigationIcon = null
|
||||
|
||||
return inflater.inflate(R.layout.fragment_license, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Get version number of the last agreed license version
|
||||
val agreedLicenseVersion = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getInt(Constants.KEY_LAST_AGREED_LICENSE_VERSION, 0)
|
||||
|
||||
// If the last agreed license version is the actual one then proceed to the following Activities
|
||||
if (agreedLicenseVersion == Constants.CURRENT_LICENSE_VERSION) {
|
||||
agree()
|
||||
} else {
|
||||
wbLA.loadUrl("file:///android_asset/eula.html")
|
||||
|
||||
btnDisagree.setOnClickListener { activity?.finish() }
|
||||
|
||||
btnAgree.setOnClickListener { agree() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function stores the version of the current accepted license version into the Shared Preferences and
|
||||
* sends the user to import/create account.
|
||||
*/
|
||||
private fun agree() {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||
.putInt(Constants.KEY_LAST_AGREED_LICENSE_VERSION, Constants.CURRENT_LICENSE_VERSION).apply()
|
||||
|
||||
findNavController().navigate(R.id.import_brainkey_action)
|
||||
}
|
||||
}
|
|
@ -1,22 +1,18 @@
|
|||
package cy.agorise.bitsybitshareswallet.fragments
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import android.widget.AdapterView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.google.common.primitives.UnsignedLong
|
||||
|
@ -35,12 +31,9 @@ import cy.agorise.bitsybitshareswallet.viewmodels.AssetViewModel
|
|||
import cy.agorise.bitsybitshareswallet.viewmodels.UserAccountViewModel
|
||||
import cy.agorise.graphenej.*
|
||||
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.ListAssets
|
||||
import cy.agorise.graphenej.models.JsonRpcResponse
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import kotlinx.android.synthetic.main.fragment_receive_transaction.*
|
||||
import java.lang.Exception
|
||||
import java.math.RoundingMode
|
||||
|
@ -50,16 +43,19 @@ import java.util.*
|
|||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class ReceiveTransactionFragment : Fragment(), ServiceConnection {
|
||||
private val TAG = this.javaClass.simpleName
|
||||
class ReceiveTransactionFragment : ConnectedFragment() {
|
||||
|
||||
private val RESPONSE_LIST_ASSETS = 1
|
||||
private val REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION = 100
|
||||
companion object {
|
||||
private const val TAG = "ReceiveTransactionFrag"
|
||||
|
||||
/** Number of assets to request from the NetworkService to show as suggestions in the AutoCompleteTextView */
|
||||
private val AUTO_SUGGEST_ASSET_LIMIT = 5
|
||||
private const val RESPONSE_LIST_ASSETS = 1
|
||||
private const val REQUEST_WRITE_EXTERNAL_STORAGE_PERMISSION = 100
|
||||
|
||||
private val OTHER_ASSET = "other_asset"
|
||||
/** Number of assets to request from the NetworkService to show as suggestions in the AutoCompleteTextView */
|
||||
private const val AUTO_SUGGEST_ASSET_LIMIT = 5
|
||||
|
||||
private const val OTHER_ASSET = "other_asset"
|
||||
}
|
||||
|
||||
private lateinit var mUserAccountViewModel: UserAccountViewModel
|
||||
private lateinit var mAssetViewModel: AssetViewModel
|
||||
|
@ -67,8 +63,6 @@ class ReceiveTransactionFragment : Fragment(), ServiceConnection {
|
|||
/** Current user account */
|
||||
private var mUserAccount: UserAccount? = null
|
||||
|
||||
private var mDisposables = CompositeDisposable()
|
||||
|
||||
private var mAsset: Asset? = null
|
||||
|
||||
private var mAssetsAdapter: AssetsAdapter? = null
|
||||
|
@ -85,15 +79,21 @@ class ReceiveTransactionFragment : Fragment(), ServiceConnection {
|
|||
// Map used to keep track of request and response id pairs
|
||||
private val responseMap = HashMap<Long, Int>()
|
||||
|
||||
/* Network service connection */
|
||||
private var mNetworkService: NetworkService? = null
|
||||
|
||||
/** Flag used to keep track of the NetworkService binding state */
|
||||
private var mShouldUnbindNetwork: Boolean = false
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
val nightMode = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean(Constants.KEY_NIGHT_MODE_ACTIVATED, false)
|
||||
|
||||
// Sets the toolbar background color to green
|
||||
val toolbar: Toolbar? = activity?.findViewById(R.id.toolbar)
|
||||
toolbar?.setBackgroundResource(if (!nightMode) R.color.colorReceive else R.color.colorToolbarDark)
|
||||
|
||||
// Sets the status bar background color to a dark green
|
||||
val window = activity?.window
|
||||
window?.statusBarColor = ContextCompat.getColor(context!!,
|
||||
if (!nightMode) R.color.colorReceiveDark else R.color.colorStatusBarDark)
|
||||
|
||||
return inflater.inflate(R.layout.fragment_receive_transaction, container, false)
|
||||
}
|
||||
|
||||
|
@ -114,14 +114,14 @@ class ReceiveTransactionFragment : Fragment(), ServiceConnection {
|
|||
// Configure Assets spinner to show Assets already saved into the db
|
||||
mAssetViewModel = ViewModelProviders.of(this).get(AssetViewModel::class.java)
|
||||
|
||||
mAssetViewModel.getAll().observe(this,
|
||||
mAssetViewModel.getAllNonZero().observe(this,
|
||||
Observer<List<cy.agorise.bitsybitshareswallet.database.entities.Asset>> { assets ->
|
||||
mAssets.clear()
|
||||
mAssets.addAll(assets)
|
||||
|
||||
// Add an option at the end so the user can search for an asset other than the ones saved in the db
|
||||
val asset = cy.agorise.bitsybitshareswallet.database.entities.Asset(
|
||||
OTHER_ASSET, "Other...", 0, "", ""
|
||||
OTHER_ASSET, getString(R.string.text__other), 0, "", ""
|
||||
)
|
||||
mAssets.add(asset)
|
||||
|
||||
|
@ -198,50 +198,49 @@ class ReceiveTransactionFragment : Fragment(), ServiceConnection {
|
|||
selectedInAutoCompleteTextView = true
|
||||
updateQR()
|
||||
}
|
||||
|
||||
// Connect to the RxBus, which receives events from the NetworkService
|
||||
mDisposables.add(
|
||||
RxBus.getBusInstance()
|
||||
.asFlowable()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { handleIncomingMessage(it) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleIncomingMessage(message: Any?) {
|
||||
if (message is JsonRpcResponse<*>) {
|
||||
if (responseMap.containsKey(message.id)) {
|
||||
val responseType = responseMap[message.id]
|
||||
when (responseType) {
|
||||
RESPONSE_LIST_ASSETS -> handleListAssets(message.result as List<Asset>)
|
||||
}
|
||||
responseMap.remove(message.id)
|
||||
}
|
||||
} else if (message is ConnectionStatusUpdate) {
|
||||
if (message.updateCode == ConnectionStatusUpdate.DISCONNECTED) {
|
||||
// If we got a disconnection notification, we should clear our response map, since
|
||||
// all its stored request ids will now be reset
|
||||
responseMap.clear()
|
||||
override fun handleJsonRpcResponse(response: JsonRpcResponse<*>) {
|
||||
if (responseMap.containsKey(response.id)) {
|
||||
val responseType = responseMap[response.id]
|
||||
when (responseType) {
|
||||
RESPONSE_LIST_ASSETS -> handleListAssets(response.result)
|
||||
}
|
||||
responseMap.remove(response.id)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleListAssets(assetList: List<Asset>) {
|
||||
Log.d(TAG, "handleListAssets")
|
||||
val assets = ArrayList<cy.agorise.bitsybitshareswallet.database.entities.Asset>()
|
||||
for (_asset in assetList) {
|
||||
val asset = cy.agorise.bitsybitshareswallet.database.entities.Asset(
|
||||
_asset.objectId,
|
||||
_asset.symbol,
|
||||
_asset.precision,
|
||||
_asset.description ?: "",
|
||||
_asset.bitassetId ?: ""
|
||||
)
|
||||
|
||||
assets.add(asset)
|
||||
override fun handleConnectionStatusUpdate(connectionStatusUpdate: ConnectionStatusUpdate) {
|
||||
if (connectionStatusUpdate.updateCode == ConnectionStatusUpdate.DISCONNECTED) {
|
||||
// If we got a disconnection notification, we should clear our response map, since
|
||||
// all its stored request ids will now be reset
|
||||
responseMap.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the list of assets returned from the node that correspond to what the user has typed in the Asset
|
||||
* AutoCompleteTextView and adds them to its adapter to show as suggestions.
|
||||
*/
|
||||
private fun handleListAssets(result: Any?) {
|
||||
if (result is List<*> && result.isNotEmpty() && result[0] is Asset) {
|
||||
val assetList = result as List<Asset>
|
||||
Log.d(TAG, "handleListAssets")
|
||||
val assets = ArrayList<cy.agorise.bitsybitshareswallet.database.entities.Asset>()
|
||||
for (_asset in assetList) {
|
||||
val asset = cy.agorise.bitsybitshareswallet.database.entities.Asset(
|
||||
_asset.objectId,
|
||||
_asset.symbol,
|
||||
_asset.precision,
|
||||
_asset.description ?: "",
|
||||
_asset.bitassetId ?: ""
|
||||
)
|
||||
|
||||
assets.add(asset)
|
||||
}
|
||||
mAutoSuggestAssetAdapter.setData(assets)
|
||||
mAutoSuggestAssetAdapter.notifyDataSetChanged()
|
||||
}
|
||||
mAutoSuggestAssetAdapter.setData(assets)
|
||||
mAutoSuggestAssetAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private fun updateQR() {
|
||||
|
@ -325,14 +324,18 @@ class ReceiveTransactionFragment : Fragment(), ServiceConnection {
|
|||
* @param account Account to pay total
|
||||
*/
|
||||
private fun updateAmountAddressUI(total: AssetAmount, account: String) {
|
||||
val df = DecimalFormat("####."+("#".repeat(total.asset.precision)))
|
||||
df.roundingMode = RoundingMode.CEILING
|
||||
df.decimalFormatSymbols = DecimalFormatSymbols(Locale.getDefault())
|
||||
val txtAmount: String = if (total.amount.toLong() == 0L) {
|
||||
getString(R.string.template__please_send, getString(R.string.text__any_amount), " ")
|
||||
} else {
|
||||
val df = DecimalFormat("####."+("#".repeat(total.asset.precision)))
|
||||
df.roundingMode = RoundingMode.CEILING
|
||||
df.decimalFormatSymbols = DecimalFormatSymbols(Locale.getDefault())
|
||||
|
||||
val amount = total.amount.toDouble() / Math.pow(10.toDouble(), total.asset.precision.toDouble())
|
||||
val strAmount = df.format(amount)
|
||||
val amount = total.amount.toDouble() / Math.pow(10.toDouble(), total.asset.precision.toDouble())
|
||||
val strAmount = df.format(amount)
|
||||
getString(R.string.template__please_send, strAmount, total.asset.symbol)
|
||||
}
|
||||
|
||||
val txtAmount = getString(R.string.template__please_pay, strAmount, total.asset.symbol)
|
||||
val txtAccount = getString(R.string.template__to, account)
|
||||
|
||||
tvPleasePay.text = txtAmount
|
||||
|
@ -370,8 +373,7 @@ class ReceiveTransactionFragment : Fragment(), ServiceConnection {
|
|||
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
||||
shareQRScreenshot()
|
||||
} else {
|
||||
// TODO extract string resource
|
||||
Toast.makeText(context!!, "Storage permission is necessary to share QR codes.", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context!!, getString(R.string.msg__storage__permission__necessary), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -406,39 +408,4 @@ class ReceiveTransactionFragment : Fragment(), ServiceConnection {
|
|||
shareIntent.type = "*/*"
|
||||
startActivity(Intent.createChooser(shareIntent, getString(R.string.text__share_with)))
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
val intent = Intent(context, NetworkService::class.java)
|
||||
if (context?.bindService(intent, this, Context.BIND_AUTO_CREATE) == true) {
|
||||
mShouldUnbindNetwork = true
|
||||
} else {
|
||||
Log.e(TAG, "Binding to the network service failed.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
// Unbinding from network service
|
||||
if (mShouldUnbindNetwork) {
|
||||
context?.unbindService(this)
|
||||
mShouldUnbindNetwork = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
if (!mDisposables.isDisposed) mDisposables.dispose()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) { }
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
// We've bound to LocalService, cast the IBinder and get LocalService instance
|
||||
val binder = service as NetworkService.LocalBinder
|
||||
mNetworkService = binder.service
|
||||
}
|
||||
}
|
|
@ -1,24 +1,20 @@
|
|||
package cy.agorise.bitsybitshareswallet.fragments
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.*
|
||||
import android.widget.AdapterView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.customview.customView
|
||||
import com.google.common.primitives.UnsignedLong
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.Result
|
||||
|
@ -27,23 +23,21 @@ import cy.agorise.bitsybitshareswallet.R
|
|||
import cy.agorise.bitsybitshareswallet.adapters.BalancesDetailsAdapter
|
||||
import cy.agorise.bitsybitshareswallet.database.joins.BalanceDetail
|
||||
import cy.agorise.bitsybitshareswallet.repositories.AuthorityRepository
|
||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||
import cy.agorise.bitsybitshareswallet.utils.CryptoUtils
|
||||
import cy.agorise.bitsybitshareswallet.utils.*
|
||||
import cy.agorise.bitsybitshareswallet.viewmodels.BalanceDetailViewModel
|
||||
import cy.agorise.graphenej.*
|
||||
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.BroadcastTransaction
|
||||
import cy.agorise.graphenej.api.calls.GetAccountByName
|
||||
import cy.agorise.graphenej.api.calls.GetDynamicGlobalProperties
|
||||
import cy.agorise.graphenej.api.calls.GetRequiredFees
|
||||
import cy.agorise.graphenej.crypto.SecureRandomGenerator
|
||||
import cy.agorise.graphenej.models.AccountProperties
|
||||
import cy.agorise.graphenej.models.DynamicGlobalProperties
|
||||
import cy.agorise.graphenej.models.JsonRpcResponse
|
||||
import cy.agorise.graphenej.operations.TransferOperation
|
||||
import cy.agorise.graphenej.operations.TransferOperationBuilder
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.android.synthetic.main.fragment_send_transaction.*
|
||||
import me.dm7.barcodescanner.zxing.ZXingScannerView
|
||||
|
@ -57,17 +51,22 @@ import java.util.Locale
|
|||
import java.util.concurrent.TimeUnit
|
||||
import javax.crypto.AEADBadTagException
|
||||
|
||||
class SendTransactionFragment : Fragment(), ZXingScannerView.ResultHandler, ServiceConnection {
|
||||
private val TAG = this.javaClass.simpleName
|
||||
class SendTransactionFragment : ConnectedFragment(), ZXingScannerView.ResultHandler {
|
||||
|
||||
// Camera Permission
|
||||
private val REQUEST_CAMERA_PERMISSION = 1
|
||||
companion object {
|
||||
private const val TAG = "SendTransactionFragment"
|
||||
|
||||
private val RESPONSE_GET_ACCOUNT_BY_NAME = 1
|
||||
private val RESPONSE_GET_DYNAMIC_GLOBAL_PARAMETERS = 2
|
||||
private val RESPONSE_GET_REQUIRED_FEES = 3
|
||||
private val RESPONSE_BROADCAST_TRANSACTION = 4
|
||||
// Camera Permission
|
||||
private const val REQUEST_CAMERA_PERMISSION = 1
|
||||
|
||||
// Constants used to organize NetworkService requests
|
||||
private const val RESPONSE_GET_ACCOUNT_BY_NAME = 1
|
||||
private const val RESPONSE_GET_DYNAMIC_GLOBAL_PARAMETERS = 2
|
||||
private const val RESPONSE_GET_REQUIRED_FEES = 3
|
||||
private const val RESPONSE_BROADCAST_TRANSACTION = 4
|
||||
}
|
||||
|
||||
/** Variables used in field's validation */
|
||||
private var isCameraPreviewVisible = false
|
||||
private var isToAccountCorrect = false
|
||||
private var isAmountCorrect = false
|
||||
|
@ -78,6 +77,7 @@ class SendTransactionFragment : Fragment(), ZXingScannerView.ResultHandler, Serv
|
|||
|
||||
private var mBalancesDetailsAdapter: BalancesDetailsAdapter? = null
|
||||
|
||||
/** Keeps track of the asset's symbol selected in the Asset spinner */
|
||||
private var selectedAssetSymbol = ""
|
||||
|
||||
/** Current user account */
|
||||
|
@ -86,17 +86,10 @@ class SendTransactionFragment : Fragment(), ZXingScannerView.ResultHandler, Serv
|
|||
/** User account to which send the funds */
|
||||
private var mSelectedUserAccount: UserAccount? = null
|
||||
|
||||
private var mDisposables = CompositeDisposable()
|
||||
|
||||
/* Network service connection */
|
||||
private var mNetworkService: NetworkService? = null
|
||||
|
||||
/** Flag used to keep track of the NetworkService binding state */
|
||||
private var mShouldUnbindNetwork: Boolean = false
|
||||
|
||||
// Map used to keep track of request and response id pairs
|
||||
private val responseMap = HashMap<Long, Int>()
|
||||
|
||||
/** Transaction being built */
|
||||
private var transaction: Transaction? = null
|
||||
|
||||
/** Variable holding the current user's private key in the WIF format */
|
||||
|
@ -105,10 +98,24 @@ class SendTransactionFragment : Fragment(), ZXingScannerView.ResultHandler, Serv
|
|||
/** Repository to access and update Authorities */
|
||||
private var authorityRepository: AuthorityRepository? = null
|
||||
|
||||
/* This is one of the of the recipient account's public key, it will be used for memo encoding */
|
||||
/** This is one of the recipient account's public key, it will be used for memo encoding */
|
||||
private var destinationPublicKey: PublicKey? = null
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
val nightMode = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean(Constants.KEY_NIGHT_MODE_ACTIVATED, false)
|
||||
|
||||
// Sets the toolbar background color to red
|
||||
val toolbar: Toolbar? = activity?.findViewById(R.id.toolbar)
|
||||
toolbar?.setBackgroundResource(if (!nightMode) R.color.colorSend else R.color.colorToolbarDark)
|
||||
|
||||
// Sets the status bar background color to a dark red
|
||||
val window = activity?.window
|
||||
window?.statusBarColor = ContextCompat.getColor(context!!,
|
||||
if (!nightMode) R.color.colorSendDark else R.color.colorStatusBarDark)
|
||||
|
||||
return inflater.inflate(R.layout.fragment_send_transaction, container, false)
|
||||
}
|
||||
|
||||
|
@ -116,13 +123,13 @@ class SendTransactionFragment : Fragment(), ZXingScannerView.ResultHandler, Serv
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val userId = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getString(Constants.KEY_CURRENT_ACCOUNT_ID, "")
|
||||
.getString(Constants.KEY_CURRENT_ACCOUNT_ID, "") ?: ""
|
||||
|
||||
if (userId != "")
|
||||
mUserAccount = UserAccount(userId)
|
||||
|
||||
// Use Navigation SafeArgs to decide if we should activate or not the camera feed
|
||||
val safeArgs = SendTransactionFragmentArgs.fromBundle(arguments)
|
||||
val safeArgs = SendTransactionFragmentArgs.fromBundle(arguments!!)
|
||||
if (safeArgs.openCamera)
|
||||
verifyCameraPermission()
|
||||
|
||||
|
@ -145,27 +152,16 @@ class SendTransactionFragment : Fragment(), ZXingScannerView.ResultHandler, Serv
|
|||
}
|
||||
})
|
||||
|
||||
spAsset.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) { }
|
||||
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
val balance = mBalancesDetailsAdapter!!.getItem(position)!!
|
||||
selectedAssetSymbol = balance.symbol
|
||||
|
||||
val amount = balance.amount.toDouble() / Math.pow(10.0, balance.precision.toDouble())
|
||||
|
||||
tvAvailableAssetAmount.text =
|
||||
String.format("%." + Math.min(balance.precision, 8) + "f %s", amount, balance.symbol)
|
||||
}
|
||||
}
|
||||
spAsset.onItemSelectedListener = assetItemSelectedListener
|
||||
|
||||
fabSendTransaction.setOnClickListener { startSendTransferOperation() }
|
||||
fabSendTransaction.hide()
|
||||
fabSendTransaction.disable(R.color.lightGray)
|
||||
|
||||
authorityRepository = AuthorityRepository(context!!)
|
||||
|
||||
// Obtain the WifKey from the db, which is used in the Send Transfer procedure
|
||||
mDisposables.add(
|
||||
authorityRepository!!.getWIF(userId!!, AuthorityType.ACTIVE.ordinal)
|
||||
authorityRepository!!.getWIF(userId, AuthorityType.ACTIVE.ordinal)
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { encryptedWIF ->
|
||||
|
@ -181,56 +177,67 @@ class SendTransactionFragment : Fragment(), ZXingScannerView.ResultHandler, Serv
|
|||
// Use RxJava Debounce to avoid making calls to the NetworkService on every text change event
|
||||
mDisposables.add(
|
||||
tietTo.textChanges()
|
||||
.skipInitialValue()
|
||||
.debounce(500, TimeUnit.MILLISECONDS)
|
||||
.map { it.toString().trim() }
|
||||
.filter { it.length > 1 }
|
||||
.subscribe {
|
||||
val id = mNetworkService!!.sendMessage(GetAccountByName(it!!), GetAccountByName.REQUIRED_API)
|
||||
responseMap[id] = RESPONSE_GET_ACCOUNT_BY_NAME
|
||||
val id = mNetworkService?.sendMessage(GetAccountByName(it!!), GetAccountByName.REQUIRED_API)
|
||||
if (id != null) responseMap[id] = RESPONSE_GET_ACCOUNT_BY_NAME
|
||||
}
|
||||
)
|
||||
|
||||
// Use RxJava Debounce to update the Amount error only after the user stops writing for > 500 ms
|
||||
mDisposables.add(
|
||||
tietAmount.textChanges()
|
||||
.skipInitialValue()
|
||||
.debounce(500, TimeUnit.MILLISECONDS)
|
||||
.filter { it.isNotEmpty() }
|
||||
.map { it.toString().trim().toDouble() }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { validateAmount(it!!) }
|
||||
)
|
||||
|
||||
// Connect to the RxBus, which receives events from the NetworkService
|
||||
mDisposables.add(
|
||||
RxBus.getBusInstance()
|
||||
.asFlowable()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { handleIncomingMessage(it) }
|
||||
.subscribe { validateAmount() }
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleIncomingMessage(message: Any?) {
|
||||
if (message is JsonRpcResponse<*>) {
|
||||
if (responseMap.containsKey(message.id)) {
|
||||
val responseType = responseMap[message.id]
|
||||
when (responseType) {
|
||||
RESPONSE_GET_ACCOUNT_BY_NAME -> handleAccountName(message.result)
|
||||
RESPONSE_GET_DYNAMIC_GLOBAL_PARAMETERS -> handleDynamicGlobalProperties(message.result)
|
||||
RESPONSE_GET_REQUIRED_FEES -> handleRequiredFees(message.result)
|
||||
RESPONSE_BROADCAST_TRANSACTION -> handleBroadcastTransaction(message)
|
||||
}
|
||||
responseMap.remove(message.id)
|
||||
}
|
||||
} else if (message is ConnectionStatusUpdate) {
|
||||
if (message.updateCode == ConnectionStatusUpdate.DISCONNECTED) {
|
||||
// If we got a disconnection notification, we should clear our response map, since
|
||||
// all its stored request ids will now be reset
|
||||
responseMap.clear()
|
||||
}
|
||||
/** Handles the selection of items in the Asset spinner, to keep track of the selectedAssetSymbol and show the
|
||||
* current user's balance of the selected asset. */
|
||||
private val assetItemSelectedListener = object : AdapterView.OnItemSelectedListener{
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) { }
|
||||
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
val balance = mBalancesDetailsAdapter!!.getItem(position)!!
|
||||
selectedAssetSymbol = balance.symbol
|
||||
|
||||
val amount = balance.amount.toDouble() / Math.pow(10.0, balance.precision.toDouble())
|
||||
|
||||
tvAvailableAssetAmount.text =
|
||||
String.format("%." + Math.min(balance.precision, 8) + "f %s", amount, balance.symbol)
|
||||
|
||||
validateAmount()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAccountName(result: Any?) {
|
||||
override fun handleJsonRpcResponse(response: JsonRpcResponse<*>) {
|
||||
if (responseMap.containsKey(response.id)) {
|
||||
val responseType = responseMap[response.id]
|
||||
when (responseType) {
|
||||
RESPONSE_GET_ACCOUNT_BY_NAME -> handleAccountProperties(response.result)
|
||||
RESPONSE_GET_DYNAMIC_GLOBAL_PARAMETERS -> handleDynamicGlobalProperties(response.result)
|
||||
RESPONSE_GET_REQUIRED_FEES -> handleRequiredFees(response.result)
|
||||
RESPONSE_BROADCAST_TRANSACTION -> handleBroadcastTransaction(response)
|
||||
}
|
||||
responseMap.remove(response.id)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleConnectionStatusUpdate(connectionStatusUpdate: ConnectionStatusUpdate) {
|
||||
if (connectionStatusUpdate.updateCode == ConnectionStatusUpdate.DISCONNECTED) {
|
||||
// If we got a disconnection notification, we should clear our response map, since
|
||||
// all its stored request ids will now be reset
|
||||
responseMap.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/** Handles the result of the [GetAccountByName] api call to find out if the account written in the To text
|
||||
* field corresponds to an actual BitShares account or not and acts accordingly */
|
||||
private fun handleAccountProperties(result: Any?) {
|
||||
if (result is AccountProperties) {
|
||||
mSelectedUserAccount = UserAccount(result.id, result.name)
|
||||
destinationPublicKey = result.active.keyAuths.keys.iterator().next()
|
||||
|
@ -239,13 +246,16 @@ class SendTransactionFragment : Fragment(), ZXingScannerView.ResultHandler, Serv
|
|||
} else {
|
||||
mSelectedUserAccount = null
|
||||
destinationPublicKey = null
|
||||
tilTo.error = "Invalid account"
|
||||
tilTo.error = getString(R.string.error__invalid_account)
|
||||
isToAccountCorrect = false
|
||||
}
|
||||
|
||||
enableDisableSendFAB()
|
||||
}
|
||||
|
||||
/** Handles the result of the [GetDynamicGlobalProperties] api call to add the needed metadata to the [Transaction]
|
||||
* the app is building and ultimately send, if everything is correct adds the needed info to the [Transaction] and
|
||||
* calls the next step which is [GetRequiredFees] else it shows an error */
|
||||
private fun handleDynamicGlobalProperties(result: Any?) {
|
||||
if (result is DynamicGlobalProperties) {
|
||||
val expirationTime = (result.time.time / 1000) + Transaction.DEFAULT_EXPIRATION_TIME
|
||||
|
@ -256,43 +266,41 @@ class SendTransactionFragment : Fragment(), ZXingScannerView.ResultHandler, Serv
|
|||
|
||||
val asset = Asset(mBalancesDetailsAdapter!!.getItem(spAsset.selectedItemPosition)!!.id)
|
||||
|
||||
val id = mNetworkService!!.sendMessage(GetRequiredFees(transaction!!, asset), GetRequiredFees.REQUIRED_API)
|
||||
responseMap[id] = RESPONSE_GET_REQUIRED_FEES
|
||||
val id = mNetworkService?.sendMessage(GetRequiredFees(transaction!!, asset), GetRequiredFees.REQUIRED_API)
|
||||
if (id != null) responseMap[id] = RESPONSE_GET_REQUIRED_FEES
|
||||
} else {
|
||||
// TODO unableToSendTransactionError()
|
||||
context?.toast(getString(R.string.msg__transaction_not_sent))
|
||||
}
|
||||
}
|
||||
|
||||
/** Handles the result of the [GetRequiredFees] api call to add the fees to the [Transaction] the app is building
|
||||
* and ultimately send, and if everything is correct broadcasts the [Transaction] else it shows an error */
|
||||
private fun handleRequiredFees(result: Any?) {
|
||||
if (result is List<*> && result[0] is AssetAmount) {
|
||||
Log.d(TAG, "GetRequiredFees: " + transaction.toString())
|
||||
transaction!!.setFees(result as List<AssetAmount>) // TODO find how to remove this warning
|
||||
|
||||
val id = mNetworkService!!.sendMessage(BroadcastTransaction(transaction), BroadcastTransaction.REQUIRED_API)
|
||||
responseMap[id] = RESPONSE_BROADCAST_TRANSACTION
|
||||
val id = mNetworkService?.sendMessage(BroadcastTransaction(transaction), BroadcastTransaction.REQUIRED_API)
|
||||
if (id != null) responseMap[id] = RESPONSE_BROADCAST_TRANSACTION
|
||||
} else {
|
||||
// TODO unableToSendTransactionError()
|
||||
context?.toast(getString(R.string.msg__transaction_not_sent))
|
||||
}
|
||||
}
|
||||
|
||||
/** Handles the result of the [BroadcastTransaction] api call to find out if the Transaction was sent successfully
|
||||
* or not and acts accordingly */
|
||||
private fun handleBroadcastTransaction(message: JsonRpcResponse<*>) {
|
||||
if (message.result == null && message.error == null) {
|
||||
// TODO extract string resources
|
||||
Toast.makeText(context!!, "Transaction sent!", Toast.LENGTH_SHORT).show()
|
||||
context?.toast(getString(R.string.text__transaction_sent))
|
||||
|
||||
// Remove information from the text fields and disable send button
|
||||
tietTo.setText("")
|
||||
tietAmount.setText("")
|
||||
tietMemo.setText("")
|
||||
isToAccountCorrect = false
|
||||
isAmountCorrect = false
|
||||
enableDisableSendFAB()
|
||||
} else {
|
||||
// TODO extract error messages to show a better explanation to the user
|
||||
Toast.makeText(context!!, message.error.message, Toast.LENGTH_LONG).show()
|
||||
// Return to the main screen
|
||||
findNavController().navigateUp()
|
||||
} else if (message.error != null) {
|
||||
context?.toast(message.error.message, Toast.LENGTH_LONG)
|
||||
}
|
||||
}
|
||||
|
||||
/** Verifies if the user has already granted the Camera permission, if not the asks for it */
|
||||
private fun verifyCameraPermission() {
|
||||
if (ContextCompat.checkSelfPermission(activity!!, Manifest.permission.CAMERA)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
|
@ -304,6 +312,7 @@ class SendTransactionFragment : Fragment(), ZXingScannerView.ResultHandler, Serv
|
|||
}
|
||||
}
|
||||
|
||||
/** Handles the result from the camera permission request */
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
|
||||
|
@ -311,8 +320,7 @@ class SendTransactionFragment : Fragment(), ZXingScannerView.ResultHandler, Serv
|
|||
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
||||
startCameraPreview()
|
||||
} else {
|
||||
// TODO extract string resource
|
||||
Toast.makeText(context!!, "Camera permission is necessary to read QR codes.", Toast.LENGTH_SHORT).show()
|
||||
context?.toast(getString(R.string.msg__camera_permission_necessary))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -340,6 +348,8 @@ class SendTransactionFragment : Fragment(), ZXingScannerView.ResultHandler, Serv
|
|||
cameraPreview.stopCamera()
|
||||
}
|
||||
|
||||
/** Handles the result of the QR code read from the camera and tries to populate the Account, Amount and Memo fields
|
||||
* and the Asset spinner with the obtained information */
|
||||
override fun handleResult(result: Result?) {
|
||||
try {
|
||||
val invoice = Invoice.fromQrCode(result!!.text)
|
||||
|
@ -348,21 +358,25 @@ class SendTransactionFragment : Fragment(), ZXingScannerView.ResultHandler, Serv
|
|||
|
||||
tietTo.setText(invoice.to)
|
||||
|
||||
for (i in 0 until mBalancesDetailsAdapter!!.count) {
|
||||
if (mBalancesDetailsAdapter!!.getItem(i)!!.symbol == invoice.currency.toUpperCase()) {
|
||||
// Try to select the invoice's Asset in the Assets spinner
|
||||
for (i in 0 until (mBalancesDetailsAdapter?.count ?: 0)) {
|
||||
if (mBalancesDetailsAdapter?.getItem(i)?.symbol == invoice.currency.toUpperCase() ||
|
||||
(invoice.currency.startsWith("bit", true) &&
|
||||
invoice.currency.replaceFirst("bit", "").toUpperCase() ==
|
||||
mBalancesDetailsAdapter?.getItem(i)?.symbol)) {
|
||||
spAsset.setSelection(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
tietMemo.setText(invoice.memo)
|
||||
|
||||
tietMemo.setText(invoice.memo)
|
||||
|
||||
var amount = 0.0
|
||||
for (nextItem in invoice.lineItems) {
|
||||
amount += nextItem.quantity * nextItem.price
|
||||
}
|
||||
// TODO Improve pattern to account for different asset precisions
|
||||
val df = DecimalFormat("####.#####")
|
||||
val df = DecimalFormat("####.########")
|
||||
df.roundingMode = RoundingMode.CEILING
|
||||
df.decimalFormatSymbols = DecimalFormatSymbols(Locale.getDefault())
|
||||
tietAmount.setText(df.format(amount))
|
||||
|
@ -372,33 +386,52 @@ class SendTransactionFragment : Fragment(), ZXingScannerView.ResultHandler, Serv
|
|||
}
|
||||
}
|
||||
|
||||
private fun validateAmount(amount: Double) {
|
||||
private fun validateAmount() {
|
||||
val txtAmount = tietAmount.text.toString()
|
||||
|
||||
if (mBalancesDetailsAdapter?.isEmpty != false) return
|
||||
val balance = mBalancesDetailsAdapter?.getItem(spAsset.selectedItemPosition) ?: return
|
||||
val currentAmount = balance.amount.toDouble() / Math.pow(10.0, balance.precision.toDouble())
|
||||
|
||||
if (currentAmount < amount) {
|
||||
// TODO extract string resource
|
||||
tilAmount.error = "Not enough funds"
|
||||
isAmountCorrect = false
|
||||
} else {
|
||||
tilAmount.isErrorEnabled = false
|
||||
isAmountCorrect = true
|
||||
val amount: Double = try {
|
||||
txtAmount.toDouble()
|
||||
} catch (e: Exception) {
|
||||
0.0
|
||||
}
|
||||
|
||||
when {
|
||||
currentAmount < amount -> {
|
||||
tilAmount.error = getString(R.string.error__not_enough_funds)
|
||||
isAmountCorrect = false
|
||||
}
|
||||
amount == 0.0 -> {
|
||||
tilAmount.isErrorEnabled = false
|
||||
isAmountCorrect = false
|
||||
}
|
||||
else -> {
|
||||
tilAmount.isErrorEnabled = false
|
||||
isAmountCorrect = true
|
||||
}
|
||||
}
|
||||
|
||||
enableDisableSendFAB()
|
||||
}
|
||||
|
||||
private fun enableDisableSendFAB() {
|
||||
if (isToAccountCorrect && isAmountCorrect)
|
||||
fabSendTransaction.show()
|
||||
else
|
||||
fabSendTransaction.hide()
|
||||
if (isToAccountCorrect && isAmountCorrect) {
|
||||
fabSendTransaction.enable(R.color.colorSend)
|
||||
vSend.setBackgroundResource(R.drawable.send_fab_background)
|
||||
} else {
|
||||
fabSendTransaction.disable(R.color.lightGray)
|
||||
vSend.setBackgroundResource(R.drawable.send_fab_background_disabled)
|
||||
}
|
||||
}
|
||||
|
||||
/** Starts the Send Transfer operation procedure, creating a [TransferOperation] and sending a call to the
|
||||
* NetworkService to obtain the [DynamicGlobalProperties] object needed to successfully send a Transfer */
|
||||
private fun startSendTransferOperation() {
|
||||
// Create TransferOperation
|
||||
if (mNetworkService!!.isConnected) {
|
||||
if (mNetworkService?.isConnected == true) {
|
||||
val balance = mBalancesDetailsAdapter!!.getItem(spAsset.selectedItemPosition)!!
|
||||
val amount = (tietAmount.text.toString().toDouble() * Math.pow(10.0, balance.precision.toDouble())).toLong()
|
||||
|
||||
|
@ -411,66 +444,82 @@ class SendTransactionFragment : Fragment(), ZXingScannerView.ResultHandler, Serv
|
|||
|
||||
val privateKey = ECKey.fromPrivate(DumpedPrivateKey.fromBase58(null, wifKey).key.privKeyBytes)
|
||||
|
||||
// Add memo if exists TODO enable memo
|
||||
// val memoMsg = tietMemo.text.toString()
|
||||
// if (memoMsg.isNotEmpty()) {
|
||||
// val nonce = SecureRandomGenerator.getSecureRandom().nextLong().toBigInteger()
|
||||
// val encryptedMemo = Memo.encryptMessage(privateKey, destinationPublicKey!!, nonce, memoMsg)
|
||||
// val from = Address(ECKey.fromPublicOnly(privateKey.pubKey))
|
||||
// val to = Address(destinationPublicKey!!.key)
|
||||
// val memo = Memo(from, to, nonce, encryptedMemo)
|
||||
// operationBuilder.setMemo(memo)
|
||||
// }
|
||||
// Add memo if it is not empty
|
||||
val memoMsg = tietMemo.text.toString()
|
||||
if (memoMsg.isNotEmpty()) {
|
||||
val nonce = Math.abs(SecureRandomGenerator.getSecureRandom().nextLong()).toBigInteger()
|
||||
val encryptedMemo = Memo.encryptMessage(privateKey, destinationPublicKey!!, nonce, memoMsg)
|
||||
val from = Address(ECKey.fromPublicOnly(privateKey.pubKey))
|
||||
val to = Address(destinationPublicKey!!.key)
|
||||
val memo = Memo(from, to, nonce, encryptedMemo)
|
||||
operationBuilder.setMemo(memo)
|
||||
}
|
||||
|
||||
// Object that will contain all operations to be sent at once
|
||||
val operations = ArrayList<BaseOperation>()
|
||||
operations.add(operationBuilder.build())
|
||||
// Transfer from the current user to the selected one
|
||||
val transferOperation = operationBuilder.build()
|
||||
operations.add(transferOperation)
|
||||
|
||||
// Transfer operation to be sent as a fee to Agorise
|
||||
val feeOperation = getAgoriseFeeOperation(transferOperation)
|
||||
if (feeOperation != null && (feeOperation.assetAmount?.amount?.toLong() ?: 0L) > 0L)
|
||||
operations.add(feeOperation)
|
||||
|
||||
transaction = Transaction(privateKey, null, operations)
|
||||
|
||||
val id = mNetworkService!!.sendMessage(GetDynamicGlobalProperties(),
|
||||
// Start the send transaction procedure which includes a series of calls
|
||||
val id = mNetworkService?.sendMessage(GetDynamicGlobalProperties(),
|
||||
GetDynamicGlobalProperties.REQUIRED_API)
|
||||
responseMap[id] = RESPONSE_GET_DYNAMIC_GLOBAL_PARAMETERS
|
||||
if (id != null ) responseMap[id] = RESPONSE_GET_DYNAMIC_GLOBAL_PARAMETERS
|
||||
} else
|
||||
Log.d(TAG, "Network Service is not connected")
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the correct [TransferOperation] object to send the fee to Agorise. A fee is only sent if the Asset is
|
||||
* BTS or a SmartCoin.
|
||||
*/
|
||||
private fun getAgoriseFeeOperation(transferOperation: TransferOperation): TransferOperation? {
|
||||
// Verify that the current Asset is either BTS or a SmartCoin
|
||||
if (Constants.assetsWhichSendFeeToAgorise.contains(transferOperation.assetAmount?.asset?.objectId ?: "")) {
|
||||
val fee = transferOperation.assetAmount?.multiplyBy(Constants.FEE_PERCENTAGE) ?: return null
|
||||
|
||||
return TransferOperationBuilder()
|
||||
.setSource(mUserAccount)
|
||||
.setDestination(Constants.AGORISE_ACCOUNT)
|
||||
.setTransferAmount(fee)
|
||||
.build()
|
||||
} else
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_send_transaction, menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
||||
if (item?.itemId == R.id.menu_info) {
|
||||
MaterialDialog(context!!).show {
|
||||
customView(R.layout.dialog_send_transaction_info, scrollable = true)
|
||||
positiveButton(android.R.string.ok) { dismiss() }
|
||||
}
|
||||
return true
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
if (isCameraPreviewVisible)
|
||||
startCameraPreview()
|
||||
|
||||
val intent = Intent(context, NetworkService::class.java)
|
||||
if (context?.bindService(intent, this, Context.BIND_AUTO_CREATE) == true) {
|
||||
mShouldUnbindNetwork = true
|
||||
} else {
|
||||
Log.e(TAG, "Binding to the network service failed.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
// Unbinding from network service
|
||||
if (mShouldUnbindNetwork) {
|
||||
context?.unbindService(this)
|
||||
mShouldUnbindNetwork = false
|
||||
}
|
||||
|
||||
if (!isCameraPreviewVisible)
|
||||
stopCameraPreview()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
if (!mDisposables.isDisposed) mDisposables.dispose()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) { }
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
// We've bound to LocalService, cast the IBinder and get LocalService instance
|
||||
val binder = service as NetworkService.LocalBinder
|
||||
mNetworkService = binder.service
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package cy.agorise.bitsybitshareswallet.fragments
|
||||
|
||||
import android.content.*
|
||||
import android.content.Context.CLIPBOARD_SERVICE
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
|
@ -10,9 +9,9 @@ import android.util.Log
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.callbacks.onDismiss
|
||||
import com.afollestad.materialdialogs.customview.customView
|
||||
import com.afollestad.materialdialogs.list.customListAdapter
|
||||
import cy.agorise.bitsybitshareswallet.BuildConfig
|
||||
|
@ -37,7 +36,10 @@ import kotlinx.android.synthetic.main.fragment_settings.*
|
|||
import java.text.NumberFormat
|
||||
|
||||
class SettingsFragment : Fragment(), ServiceConnection {
|
||||
private val TAG = this.javaClass.simpleName
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SettingsFragment"
|
||||
}
|
||||
|
||||
private var mDisposables = CompositeDisposable()
|
||||
|
||||
|
@ -79,15 +81,14 @@ class SettingsFragment : Fragment(), ServiceConnection {
|
|||
val fullNodes = mNetworkService!!.nodes
|
||||
|
||||
nodesAdapter = FullNodesAdapter(v.context)
|
||||
nodesAdapter!!.add(fullNodes)
|
||||
nodesAdapter?.add(fullNodes)
|
||||
|
||||
mNodesDialog = MaterialDialog(v.context)
|
||||
.title(text = String.format("%s v%s", getString(R.string.app_name), BuildConfig.VERSION_NAME))
|
||||
.message(text = getString(R.string.title__bitshares_nodes_dialog, "-------"))
|
||||
.customListAdapter(nodesAdapter as FullNodesAdapter)
|
||||
.negativeButton(android.R.string.ok) {
|
||||
mHandler.removeCallbacks(mRequestDynamicGlobalPropertiesTask)
|
||||
}
|
||||
.negativeButton(android.R.string.ok)
|
||||
.onDismiss { mHandler.removeCallbacks(mRequestDynamicGlobalPropertiesTask) }
|
||||
|
||||
mNodesDialog?.show()
|
||||
|
||||
|
@ -228,13 +229,7 @@ class SettingsFragment : Fragment(), ServiceConnection {
|
|||
message(text = brainKey.brainKey)
|
||||
customView(R.layout.dialog_copy_brainkey)
|
||||
cancelable(false)
|
||||
positiveButton(android.R.string.copy) {
|
||||
Toast.makeText(it.context, "Copied to clipboard", Toast.LENGTH_SHORT).show()
|
||||
val clipboard = it.context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText("label", brainKey.brainKey)
|
||||
clipboard.primaryClip = clip
|
||||
it.dismiss()
|
||||
}
|
||||
positiveButton(R.string.button__copied) { it.dismiss() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,11 +39,12 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele
|
|||
private var filterDateRangeAll = true
|
||||
private var filterStartDate = 0L
|
||||
private var filterEndDate = 0L
|
||||
private var filterCryptocurrencyAll = true
|
||||
private var filterCryptocurrency = "BTS"
|
||||
private var filterAssetAll = true
|
||||
private var filterAsset = "BTS"
|
||||
private var filterFiatAmountAll = true
|
||||
private var filterFromFiatAmount = 0L
|
||||
private var filterToFiatAmount = 500L
|
||||
private var filterAgoriseFees = true
|
||||
|
||||
private var mDisposables = CompositeDisposable()
|
||||
|
||||
|
@ -110,8 +111,8 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele
|
|||
R.id.menu_filter -> {
|
||||
val filterOptionsDialog = FilterOptionsDialog.newInstance(
|
||||
filterTransactionsDirection, filterDateRangeAll, filterStartDate * 1000,
|
||||
filterEndDate * 1000, filterCryptocurrencyAll, filterCryptocurrency,
|
||||
filterFiatAmountAll, filterFromFiatAmount, filterToFiatAmount
|
||||
filterEndDate * 1000, filterAssetAll, filterAsset,
|
||||
filterFiatAmountAll, filterFromFiatAmount, filterToFiatAmount, filterAgoriseFees
|
||||
)
|
||||
filterOptionsDialog.show(childFragmentManager, "filter-options-tag")
|
||||
true
|
||||
|
@ -160,14 +161,18 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele
|
|||
if (!filterDateRangeAll && (transferDetail.date < filterStartDate || transferDetail.date > filterEndDate))
|
||||
continue
|
||||
|
||||
// Filter by cryptocurrency
|
||||
if (!filterCryptocurrencyAll && transferDetail.cryptoSymbol != filterCryptocurrency)
|
||||
// Filter by asset
|
||||
if (!filterAssetAll && transferDetail.cryptoSymbol != filterAsset)
|
||||
continue
|
||||
|
||||
// // Filter by fiat amount
|
||||
// if (!filterFiatAmountAll && (transferDetail.fiatAmount < filterFromFiatAmount || transferDetail.fiatAmount > filterToFiatAmount))
|
||||
// continue
|
||||
|
||||
// Filter transactions sent to agorise
|
||||
if (filterAgoriseFees && transferDetail.to.equals("agorise"))
|
||||
continue
|
||||
|
||||
// Filter by search query
|
||||
val text = (transferDetail.from ?: "").toLowerCase() + (transferDetail.to ?: "").toLowerCase()
|
||||
if (text.contains(filterQuery, ignoreCase = true)) {
|
||||
|
@ -183,28 +188,30 @@ class TransactionsFragment : Fragment(), FilterOptionsDialog.OnFilterOptionsSele
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets called when the user selects some filter options in the [FilterOptionsDialog] and wants to apply them.
|
||||
*/
|
||||
override fun onFilterOptionsSelected(
|
||||
filterTransactionsDirection: Int,
|
||||
filterDateRangeAll: Boolean,
|
||||
filterStartDate: Long,
|
||||
filterEndDate: Long,
|
||||
filterCryptocurrencyAll: Boolean,
|
||||
filterCryptocurrency: String,
|
||||
filterAssetAll: Boolean,
|
||||
filterAsset: String,
|
||||
filterFiatAmountAll: Boolean,
|
||||
filterFromFiatAmount: Long,
|
||||
filterToFiatAmount: Long
|
||||
filterToFiatAmount: Long,
|
||||
filterAgoriseFees: Boolean
|
||||
) {
|
||||
this.filterTransactionsDirection = filterTransactionsDirection
|
||||
this.filterDateRangeAll = filterDateRangeAll
|
||||
this.filterStartDate = filterStartDate / 1000
|
||||
this.filterEndDate = filterEndDate / 1000
|
||||
this.filterCryptocurrencyAll = filterCryptocurrencyAll
|
||||
this.filterCryptocurrency = filterCryptocurrency
|
||||
this.filterAssetAll = filterAssetAll
|
||||
this.filterAsset = filterAsset
|
||||
this.filterFiatAmountAll = filterFiatAmountAll
|
||||
this.filterFromFiatAmount = filterFromFiatAmount
|
||||
this.filterToFiatAmount = filterToFiatAmount
|
||||
this.filterAgoriseFees = filterAgoriseFees
|
||||
applyFilterOptions(true)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
package cy.agorise.bitsybitshareswallet.models;
|
||||
|
||||
/**
|
||||
* Class used to deserialize a the "account" object contained in the faucet response to the
|
||||
* {@link cy.agorise.bitsybitshareswallet.network.FaucetService#registerPrivateAccount(FaucetRequest)} API call.
|
||||
*/
|
||||
public class FaucetAccount {
|
||||
public String name;
|
||||
public String owner_key;
|
||||
public String active_key;
|
||||
public String memo_key;
|
||||
public String referrer;
|
||||
public String refcode;
|
||||
|
||||
public FaucetAccount(String accountName, String address, String referrer){
|
||||
this.name = accountName;
|
||||
this.owner_key = address;
|
||||
this.active_key = address;
|
||||
this.memo_key = address;
|
||||
this.refcode = referrer;
|
||||
this.referrer = referrer;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getOwnerKey() {
|
||||
return owner_key;
|
||||
}
|
||||
|
||||
public void setOwnerKey(String owner_key) {
|
||||
this.owner_key = owner_key;
|
||||
}
|
||||
|
||||
public String getActiveKey() {
|
||||
return active_key;
|
||||
}
|
||||
|
||||
public void setActiveKey(String active_key) {
|
||||
this.active_key = active_key;
|
||||
}
|
||||
|
||||
public String getMemoKey() {
|
||||
return memo_key;
|
||||
}
|
||||
|
||||
public void setMemoKey(String memo_key) {
|
||||
this.memo_key = memo_key;
|
||||
}
|
||||
|
||||
public String getRefcode() {
|
||||
return refcode;
|
||||
}
|
||||
|
||||
public void setRefcode(String refcode) {
|
||||
this.refcode = refcode;
|
||||
}
|
||||
|
||||
public String getReferrer() {
|
||||
return referrer;
|
||||
}
|
||||
|
||||
public void setReferrer(String referrer) {
|
||||
this.referrer = referrer;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package cy.agorise.bitsybitshareswallet.models;
|
||||
|
||||
/**
|
||||
* Class used to encapsulate a faucet account creation request
|
||||
*/
|
||||
|
||||
public class FaucetRequest {
|
||||
private FaucetAccount account;
|
||||
|
||||
public FaucetRequest(String accountName, String address, String referrer){
|
||||
account = new FaucetAccount(accountName, address, referrer);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package cy.agorise.bitsybitshareswallet.models;
|
||||
|
||||
public class FaucetResponse {
|
||||
public FaucetAccount account;
|
||||
public Error error;
|
||||
|
||||
public class Error {
|
||||
public String[] base;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package cy.agorise.bitsybitshareswallet.network
|
||||
|
||||
import cy.agorise.bitsybitshareswallet.models.FaucetRequest
|
||||
import cy.agorise.bitsybitshareswallet.models.FaucetResponse
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Headers
|
||||
import retrofit2.http.POST
|
||||
|
||||
/**
|
||||
* Interface to the faucet service. The faucet is used in order to register new BitShares accounts.
|
||||
*/
|
||||
interface FaucetService {
|
||||
|
||||
@GET("/")
|
||||
fun checkStatus(): Call<ResponseBody>
|
||||
|
||||
@Headers("Content-Type: application/json")
|
||||
@POST("/api/v1/accounts")
|
||||
fun registerPrivateAccount(@Body faucetRequest: FaucetRequest): Call<FaucetResponse>
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package cy.agorise.bitsybitshareswallet.network;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.logging.HttpLoggingInterceptor;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ServiceGenerator{
|
||||
public static String API_BASE_URL;
|
||||
private static HttpLoggingInterceptor logging;
|
||||
private static OkHttpClient.Builder httpClient;
|
||||
private static Retrofit.Builder builder;
|
||||
|
||||
private static HashMap<Class<?>, Object> Services;
|
||||
|
||||
public ServiceGenerator(String apiBaseUrl) {
|
||||
API_BASE_URL= apiBaseUrl;
|
||||
logging = new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||
httpClient = new OkHttpClient.Builder().addInterceptor(logging);
|
||||
builder = new Retrofit.Builder()
|
||||
.baseUrl(API_BASE_URL)
|
||||
.addConverterFactory(GsonConverterFactory.create(getGson()))
|
||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.create());
|
||||
Services = new HashMap<Class<?>, Object>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Customizes the Gson instance with specific de-serialization logic
|
||||
*/
|
||||
private Gson getGson(){
|
||||
GsonBuilder builder = new GsonBuilder();
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
public void setCallbackExecutor(Executor executor){
|
||||
builder.callbackExecutor(executor);
|
||||
}
|
||||
|
||||
public static <T> void setService(Class<T> klass, T thing) {
|
||||
Services.put(klass, thing);
|
||||
}
|
||||
|
||||
public <T> T getService(Class<T> serviceClass) {
|
||||
|
||||
T service = serviceClass.cast(Services.get(serviceClass));
|
||||
if (service == null) {
|
||||
service = createService(serviceClass);
|
||||
setService(serviceClass, service);
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
public static <S> S createService(Class<S> serviceClass) {
|
||||
|
||||
httpClient.interceptors().add(new Interceptor() {
|
||||
@Override
|
||||
public okhttp3.Response intercept(Interceptor.Chain chain) throws IOException {
|
||||
okhttp3.Request original = chain.request();
|
||||
// Request customization: add request headers
|
||||
okhttp3.Request.Builder requestBuilder = original.newBuilder().method(original.method(), original.body());
|
||||
|
||||
okhttp3.Request request = requestBuilder.build();
|
||||
return chain.proceed(request);
|
||||
}
|
||||
});
|
||||
httpClient.readTimeout(5, TimeUnit.MINUTES);
|
||||
httpClient.connectTimeout(5, TimeUnit.MINUTES);
|
||||
OkHttpClient client = httpClient.build();
|
||||
Retrofit retrofit = builder.client(client).build();
|
||||
return retrofit.create(serviceClass);
|
||||
}
|
||||
}
|
|
@ -16,8 +16,8 @@ class AssetRepository internal constructor(context: Context) {
|
|||
mAssetDao = db!!.assetDao()
|
||||
}
|
||||
|
||||
fun getAll(): LiveData<List<Asset>> {
|
||||
return mAssetDao.getAll()
|
||||
fun getAllNonZero(): LiveData<List<Asset>> {
|
||||
return mAssetDao.getAllNonZero()
|
||||
}
|
||||
|
||||
fun insertAll(assets: List<Asset>) {
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package cy.agorise.bitsybitshareswallet.utils
|
||||
|
||||
import cy.agorise.graphenej.Asset
|
||||
import cy.agorise.graphenej.UserAccount
|
||||
|
||||
object Constants {
|
||||
|
||||
/** Key used to store the number of the last agreed License version */
|
||||
|
@ -8,18 +11,54 @@ object Constants {
|
|||
/** Version of the currently used license */
|
||||
const val CURRENT_LICENSE_VERSION = 1
|
||||
|
||||
/** 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"
|
||||
|
||||
/** The minimum required length for a PIN number */
|
||||
const val MIN_PIN_LENGTH = 6
|
||||
|
||||
/** Name of the account passed to the faucet as the referrer */
|
||||
const val FAUCET_REFERRER = "agorise"
|
||||
|
||||
/** Faucet URL used to create new accounts */
|
||||
const val FAUCET_URL = "https://faucet.palmpay.io"
|
||||
|
||||
/** The user selected encrypted PIN */
|
||||
const val KEY_ENCRYPTED_PIN = "key_encrypted_pin"
|
||||
|
||||
/** The fee to send in every transfer (0.01%) */
|
||||
const val FEE_PERCENTAGE = 0.0001
|
||||
|
||||
/** The account used to send the fees */
|
||||
val AGORISE_ACCOUNT = UserAccount("1.2.390320", "agorise")
|
||||
|
||||
/** List of assets symbols that send fee to Agorise when sending a transaction (BTS and smartcoins only) */
|
||||
val assetsWhichSendFeeToAgorise = setOf(
|
||||
"1.3.0", // BTS
|
||||
"1.3.113", // CNY
|
||||
"1.3.121", // USD
|
||||
"1.3.1325", // RUBLE
|
||||
"1.3.120", // EUR
|
||||
"1.3.103" // BTC
|
||||
// "1.3.109", // HKD
|
||||
// "1.3.119", // JPY
|
||||
// "1.3.102", // KRW
|
||||
// "1.3.106", // GOLD
|
||||
// "1.3.105", // SILVER
|
||||
// "1.3.118", // GBP
|
||||
// "1.3.115", // CAD
|
||||
// "1.3.1017", // ARS
|
||||
// "1.3.114", // MXN
|
||||
// "1.3.111", // SEK
|
||||
// "1.3.117", // AUD
|
||||
// "1.3.116", // CHF
|
||||
// "1.3.112", // NZD
|
||||
// "1.3.110", // RUB
|
||||
// "1.3.2650", // XCD
|
||||
// "1.3.107", // TRY
|
||||
// "1.3.108" // SGD
|
||||
)
|
||||
|
||||
/**
|
||||
* LTM accounts come with an expiration date expressed as this string.
|
||||
* This is used to recognize such accounts from regular ones.
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package cy.agorise.bitsybitshareswallet.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* Creates an enabled state, by enabling the button and using the given [colorResource] to color it.
|
||||
*/
|
||||
fun FloatingActionButton.enable(colorResource: Int) {
|
||||
this.isEnabled = true
|
||||
this.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this.context, colorResource))
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a disabled state, by disabling the button and using the given [colorResource] to color it.
|
||||
*/
|
||||
fun FloatingActionButton.disable(colorResource: Int) {
|
||||
this.isEnabled = false
|
||||
this.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this.context, colorResource))
|
||||
}
|
||||
|
||||
/**
|
||||
* Easily create a toast message with less boilerplate code
|
||||
*/
|
||||
fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_LONG) {
|
||||
Toast.makeText(this, message, duration).show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the current string contains at least one digit
|
||||
*/
|
||||
fun String.containsDigits(): Boolean {
|
||||
return Pattern.matches("\\d", this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the current string contains at least one vowel
|
||||
*/
|
||||
fun String.containsVowels(): Boolean {
|
||||
return Pattern.matches("[aeiou]", this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to hide the Keyboard from any view
|
||||
*/
|
||||
fun View.hideKeyboard(){
|
||||
val inputMethodManager = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
inputMethodManager.hideSoftInputFromWindow(this.windowToken, 0)
|
||||
}
|
|
@ -9,7 +9,7 @@ import cy.agorise.bitsybitshareswallet.repositories.AssetRepository
|
|||
class AssetViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private var mRepository = AssetRepository(application)
|
||||
|
||||
internal fun getAll(): LiveData<List<Asset>> {
|
||||
return mRepository.getAll()
|
||||
internal fun getAllNonZero(): LiveData<List<Asset>> {
|
||||
return mRepository.getAllNonZero()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package cy.agorise.bitsybitshareswallet.views
|
||||
|
||||
import android.app.DatePickerDialog
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.os.Message
|
||||
import android.widget.DatePicker
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import cy.agorise.bitsybitshareswallet.fragments.FilterOptionsDialog
|
||||
import java.util.*
|
||||
|
||||
class DatePickerFragment : DialogFragment(), DatePickerDialog.OnDateSetListener {
|
||||
|
||||
companion object {
|
||||
const val TAG = "DatePickerFragment"
|
||||
|
||||
const val KEY_WHICH = "key_which"
|
||||
const val KEY_CURRENT = "key_current"
|
||||
const val KEY_MAX = "key_max"
|
||||
|
||||
fun newInstance(
|
||||
which: Int, currentTime: Long, maxTime: Long,
|
||||
handler: FilterOptionsDialog.DatePickerHandler
|
||||
): DatePickerFragment {
|
||||
val f = DatePickerFragment()
|
||||
val bundle = Bundle()
|
||||
bundle.putInt(KEY_WHICH, which)
|
||||
bundle.putLong(KEY_CURRENT, currentTime)
|
||||
bundle.putLong(KEY_MAX, maxTime)
|
||||
f.arguments = bundle
|
||||
f.setHandler(handler)
|
||||
return f
|
||||
}
|
||||
}
|
||||
|
||||
private var which: Int = 0
|
||||
private var mHandler: FilterOptionsDialog.DatePickerHandler? = null
|
||||
|
||||
fun setHandler(handler: FilterOptionsDialog.DatePickerHandler) {
|
||||
mHandler = handler
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
which = arguments!!.getInt(KEY_WHICH)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val currentTime = arguments!!.getLong(KEY_CURRENT)
|
||||
val maxTime = arguments!!.getLong(KEY_MAX)
|
||||
|
||||
// Use the current date as the default date in the picker
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.timeInMillis = currentTime
|
||||
|
||||
val year = calendar.get(Calendar.YEAR)
|
||||
val month = calendar.get(Calendar.MONTH)
|
||||
val day = calendar.get(Calendar.DAY_OF_MONTH)
|
||||
|
||||
// Create a new instance of DatePickerDialog and return it
|
||||
val datePicker = DatePickerDialog(activity!!, this, year, month, day)
|
||||
|
||||
// Set maximum date allowed to today
|
||||
datePicker.datePicker.maxDate = maxTime
|
||||
|
||||
return datePicker
|
||||
}
|
||||
|
||||
override fun onDateSet(view: DatePicker, year: Int, month: Int, day: Int) {
|
||||
val msg = Message.obtain()
|
||||
msg.arg1 = which
|
||||
val calendar = GregorianCalendar()
|
||||
calendar.set(year, month, day)
|
||||
val bundle = Bundle()
|
||||
bundle.putLong(FilterOptionsDialog.KEY_TIMESTAMP, calendar.time.time)
|
||||
msg.data = bundle
|
||||
mHandler!!.sendMessage(msg)
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package cy.agorise.bitsybitshareswallet.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
// An EditText that lets you use actions ("Done", "Go", etc.) on multi-line edits.
|
||||
public class MyTextInputEditText extends TextInputEditText
|
||||
{
|
||||
public MyTextInputEditText(Context context)
|
||||
{
|
||||
super(context);
|
||||
}
|
||||
|
||||
public MyTextInputEditText(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public MyTextInputEditText(Context context, AttributeSet attrs, int defStyle)
|
||||
{
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
|
||||
InputConnection connection = super.onCreateInputConnection(outAttrs);
|
||||
int imeActions = outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION;
|
||||
if ((imeActions&EditorInfo.IME_ACTION_DONE) != 0) {
|
||||
// clear the existing action
|
||||
outAttrs.imeOptions ^= imeActions;
|
||||
// set the DONE action
|
||||
outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
|
||||
}
|
||||
if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
|
||||
outAttrs.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION;
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package cy.agorise.bitsybitshareswallet.views
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.util.AttributeSet
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputConnection
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import cy.agorise.bitsybitshareswallet.utils.hideKeyboard
|
||||
|
||||
|
||||
/**
|
||||
* A TextInputEditText that hides the keyboard when the focus is removed from it and also lets you
|
||||
* use actions ("Done", "Go", etc.) on multi-line edits.
|
||||
*/
|
||||
class MyTextInputEditText(context: Context?, attrs: AttributeSet?) : TextInputEditText(context, attrs){
|
||||
|
||||
override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection {
|
||||
val connection = super.onCreateInputConnection(outAttrs)
|
||||
val imeActions = outAttrs.imeOptions and EditorInfo.IME_MASK_ACTION
|
||||
if (imeActions and EditorInfo.IME_ACTION_DONE != 0) {
|
||||
// clear the existing action
|
||||
outAttrs.imeOptions = outAttrs.imeOptions xor imeActions
|
||||
// set the DONE action
|
||||
outAttrs.imeOptions = outAttrs.imeOptions or EditorInfo.IME_ACTION_DONE
|
||||
}
|
||||
if (outAttrs.imeOptions and EditorInfo.IME_FLAG_NO_ENTER_ACTION != 0) {
|
||||
outAttrs.imeOptions = outAttrs.imeOptions and EditorInfo.IME_FLAG_NO_ENTER_ACTION.inv()
|
||||
}
|
||||
return connection
|
||||
}
|
||||
|
||||
override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
|
||||
super.onFocusChanged(focused, direction, previouslyFocusedRect)
|
||||
if (!focused) this.hideKeyboard()
|
||||
}
|
||||
}
|
|
@ -3,5 +3,5 @@
|
|||
|
||||
<translate android:fromXDelta="-100%" android:toXDelta="0%"
|
||||
android:fromYDelta="0%" android:toYDelta="0%"
|
||||
android:duration="700"/>
|
||||
android:duration="300"/>
|
||||
</set>
|
|
@ -3,5 +3,5 @@
|
|||
|
||||
<translate android:fromXDelta="100%" android:toXDelta="0%"
|
||||
android:fromYDelta="0%" android:toYDelta="0%"
|
||||
android:duration="700"/>
|
||||
android:duration="300"/>
|
||||
</set>
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
|
||||
<translate android:fromXDelta="0%" android:toXDelta="-100%"
|
||||
android:fromYDelta="0%" android:toYDelta="0%"
|
||||
android:duration="700"/>
|
||||
android:duration="300"/>
|
||||
</set>
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
|
||||
<translate android:fromXDelta="0%" android:toXDelta="100%"
|
||||
android:fromYDelta="0%" android:toYDelta="0%"
|
||||
android:duration="700"/>
|
||||
android:duration="300"/>
|
||||
</set>
|
|
@ -69,7 +69,7 @@
|
|||
<objectAnimator
|
||||
android:duration="0"
|
||||
android:propertyName="elevation"
|
||||
android:valueTo="6dp"
|
||||
android:valueTo="3dp"
|
||||
android:valueType="floatType" />
|
||||
</set>
|
||||
</item>
|
||||
|
|
5
app/src/main/res/drawable/ic_info.xml
Normal file
5
app/src/main/res/drawable/ic_info.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
|
||||
</vector>
|
6
app/src/main/res/drawable/outline_rounded_corners.xml
Normal file
6
app/src/main/res/drawable/outline_rounded_corners.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@android:color/transparent"/>
|
||||
<corners android:radius="4dp"/>
|
||||
<stroke android:color="@color/colorAccent" android:width="1dp"/>
|
||||
</shape>
|
|
@ -3,5 +3,5 @@
|
|||
<corners
|
||||
android:bottomLeftRadius="60dp"
|
||||
android:topLeftRadius="60dp" />
|
||||
<solid android:color="#5fcaff" />
|
||||
<solid android:color="#88DC473A" />
|
||||
</shape>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<corners
|
||||
android:bottomLeftRadius="60dp"
|
||||
android:topLeftRadius="60dp" />
|
||||
<solid android:color="@color/superLightGray" />
|
||||
</shape>
|
|
@ -14,7 +14,7 @@
|
|||
android:id="@+id/tvTransactionDirection"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Transactions"
|
||||
android:text="@string/title_transactions"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
|
@ -31,21 +31,21 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="All"/>
|
||||
android:text="@string/text__all"/>
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbTransactionSent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Sent"/>
|
||||
android:text="@string/text__sent"/>
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbTransactionReceived"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Received"/>
|
||||
android:text="@string/text__received"/>
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
|
@ -55,7 +55,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="Date Range"
|
||||
android:text="@string/text__date_range"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintTop_toTopOf="@id/cbDateRange"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
@ -65,7 +65,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_different_topic"
|
||||
android:text="All"
|
||||
android:text="@string/text__all"
|
||||
app:layout_constraintTop_toBottomOf="@id/rgTransactionDirection"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
|
@ -100,40 +100,52 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Cryptocurrency -->
|
||||
<!-- Asset -->
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="Cryptocurrency"
|
||||
android:text="@string/text__asset"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintTop_toTopOf="@id/cbCryptocurrency"
|
||||
app:layout_constraintTop_toTopOf="@id/cbAsset"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbCryptocurrency"
|
||||
android:id="@+id/cbAsset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_different_topic"
|
||||
android:text="All"
|
||||
android:text="@string/text__all"
|
||||
app:layout_constraintTop_toBottomOf="@+id/llDateRange"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/sCryptocurrency"
|
||||
android:id="@+id/sAsset"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@android:layout/simple_list_item_1"
|
||||
app:layout_constraintTop_toBottomOf="@id/cbCryptocurrency"/>
|
||||
app:layout_constraintTop_toBottomOf="@id/cbAsset"/>
|
||||
|
||||
<!-- Ignore Agorise Fees -->
|
||||
|
||||
<Switch
|
||||
android:id="@+id/switchAgoriseFees"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_same_topic"
|
||||
android:switchPadding="12dp"
|
||||
android:text="@string/text__ignore_network_fees"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
app:layout_constraintTop_toBottomOf="@id/sAsset"/>
|
||||
|
||||
<!-- Fiat Amount -->
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="Fiat Amount"
|
||||
android:text="@string/text__fiat_amount"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintTop_toTopOf="@id/cbFiatAmount"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
@ -142,24 +154,25 @@
|
|||
android:id="@+id/cbFiatAmount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_same_topic"
|
||||
android:text="All"
|
||||
android:layout_marginTop="@dimen/spacing_different_topic"
|
||||
android:text="@string/text__all"
|
||||
android:enabled="false"
|
||||
app:layout_constraintTop_toBottomOf="@id/sCryptocurrency"
|
||||
app:layout_constraintTop_toBottomOf="@id/switchAgoriseFees"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<!--<LinearLayout-->
|
||||
<!--android:id="@+id/llFiatAmount"-->
|
||||
<!--android:layout_width="match_parent"-->
|
||||
<!--android:layout_height="wrap_content"-->
|
||||
<!--android:layout_margin="4dp"-->
|
||||
<!--android:orientation="horizontal"-->
|
||||
<!--app:layout_constraintTop_toBottomOf="@id/cbFiatAmount">-->
|
||||
<LinearLayout
|
||||
android:id="@+id/llFiatAmount"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="4dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toBottomOf="@id/cbFiatAmount">
|
||||
|
||||
<!--<TextView-->
|
||||
<!--android:layout_width="wrap_content"-->
|
||||
<!--android:layout_height="wrap_content"-->
|
||||
<!--android:text="@string/between"/>-->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Between"/>
|
||||
|
||||
<!--<faranjit.currency.edittext.CurrencyEditText-->
|
||||
<!--android:id="@+id/etFromFiatAmount"-->
|
||||
|
@ -170,10 +183,10 @@
|
|||
<!--android:textAlignment="center"-->
|
||||
<!--app:showSymbol="true"/>-->
|
||||
|
||||
<!--<TextView-->
|
||||
<!--android:layout_width="wrap_content"-->
|
||||
<!--android:layout_height="wrap_content"-->
|
||||
<!--android:text="@string/and"/>-->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="and"/>
|
||||
|
||||
<!--<faranjit.currency.edittext.CurrencyEditText-->
|
||||
<!--android:id="@+id/etToFiatAmount"-->
|
||||
|
@ -184,6 +197,6 @@
|
|||
<!--android:textAlignment="center"-->
|
||||
<!--app:showSymbol="true"/>-->
|
||||
|
||||
<!--</LinearLayout>-->
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
79
app/src/main/res/layout/dialog_send_transaction_info.xml
Normal file
79
app/src/main/res/layout/dialog_send_transaction_info.xml
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginEnd="@dimen/activity_horizontal_margin">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/text__to"
|
||||
android:textAppearance="@style/TextAppearance.Bitsy.Body1"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/msg__to_explanation"
|
||||
android:textAppearance="@style/TextAppearance.Bitsy.Body2"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_same_topic"
|
||||
android:text="@string/text__asset_balance"
|
||||
android:textAppearance="@style/TextAppearance.Bitsy.Body1"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/msg__asset_balance_explanation"
|
||||
android:textAppearance="@style/TextAppearance.Bitsy.Body2"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_same_topic"
|
||||
android:text="@string/text__memo"
|
||||
android:textAppearance="@style/TextAppearance.Bitsy.Body1"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/msg__memo_explanation"
|
||||
android:textAppearance="@style/TextAppearance.Bitsy.Body2"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_same_topic"
|
||||
android:text="@string/text__network_fee"
|
||||
android:textAppearance="@style/TextAppearance.Bitsy.Body1"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/msg__network_fee_explanation"
|
||||
android:textAppearance="@style/TextAppearance.Bitsy.Body2"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_same_topic"
|
||||
android:text="@string/text__qr_code"
|
||||
android:textAppearance="@style/TextAppearance.Bitsy.Body1"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/msg__qr_code_explanation"
|
||||
android:textAppearance="@style/TextAppearance.Bitsy.Body2"/>
|
||||
|
||||
</LinearLayout>
|
|
@ -2,30 +2,49 @@
|
|||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
tools:context=".activities.ImportBrainkeyActivity">
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:clickable="true">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilAccountName"
|
||||
style="@style/Widget.Bitsy.TextInputLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/text__bitshares_account_name">
|
||||
|
||||
<cy.agorise.bitsybitshareswallet.views.MyTextInputEditText
|
||||
android:id="@+id/tietAccountName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text"
|
||||
android:digits="abcdefghijklmnopqrstuvwxyz0123456789-"
|
||||
android:singleLine="true"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilPin"
|
||||
style="@style/Widget.Bitsy.TextInputLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/activity_vertical_margin"
|
||||
android:layout_marginStart="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginTop="@dimen/spacing_same_topic"
|
||||
android:hint="@string/text_field__6_digit_pin"
|
||||
app:passwordToggleEnabled="true">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
<cy.agorise.bitsybitshareswallet.views.MyTextInputEditText
|
||||
android:id="@+id/tietPin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="numberPassword"
|
||||
android:singleLine="true"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
|
@ -34,68 +53,55 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_same_topic"
|
||||
android:layout_marginStart="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||
android:hint="@string/text_field__confirm_pin"
|
||||
app:passwordToggleEnabled="true">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
<cy.agorise.bitsybitshareswallet.views.MyTextInputEditText
|
||||
android:id="@+id/tietPinConfirmation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="numberPassword"
|
||||
android:singleLine="true"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilBrainKey"
|
||||
style="@style/Widget.Bitsy.TextInputLayout"
|
||||
<TextView
|
||||
android:id="@+id/tvBrainKey"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_same_topic"
|
||||
android:layout_marginStart="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||
android:hint="@string/text__brain_key">
|
||||
android:background="@drawable/outline_rounded_corners"
|
||||
android:gravity="center"
|
||||
android:padding="8dp"
|
||||
android:layout_marginTop="@dimen/spacing_different_topic"
|
||||
tools:text="SAMPLE BRAINKEY SAMPLE BRAINKEY SAMPLE BRAINKEY SAMPLE BRAINKEY SAMPLE BRAINKEY SAMPLE BRAINKEY SAMPLE BRAINKEY"
|
||||
android:textAppearance="@style/TextAppearance.Bitsy.Body1"/>
|
||||
|
||||
<cy.agorise.bitsybitshareswallet.views.MyTextInputEditText
|
||||
android:id="@+id/tietBrainKey"
|
||||
android:layout_width="match_parent"
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/msg__brainkey_info"
|
||||
android:textAppearance="@style/TextAppearance.Bitsy.Body2"
|
||||
android:gravity="center"
|
||||
android:padding="8dp"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_different_topic">
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnCreate"
|
||||
style="@style/Widget.Bitsy.Button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textMultiLine"
|
||||
android:gravity="top"
|
||||
android:lines="4"
|
||||
android:scrollHorizontally="false"
|
||||
android:imeOptions="actionDone"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnImport"
|
||||
style="@style/Widget.Bitsy.Button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_different_topic"
|
||||
android:layout_marginStart="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||
android:text="@string/button__import"/>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="@dimen/spacing_different_topic"
|
||||
android:layout_marginStart="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||
android:background="@color/black"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnCreate"
|
||||
style="@style/Widget.Bitsy.Button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_different_topic"
|
||||
android:layout_marginStart="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||
android:text="@string/button__create"
|
||||
android:enabled="false"/>
|
||||
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="@string/button__create"/>
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnCancel"
|
||||
style="@style/Widget.Bitsy.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_toStartOf="@id/btnCreate"
|
||||
android:text="@android:string/cancel"/>
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
|
@ -37,11 +37,14 @@
|
|||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabReceiveTransaction"
|
||||
style="@style/Widget.MaterialComponents.FloatingActionButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:fabSize="auto"
|
||||
app:fabCustomSize="120dp"
|
||||
app:maxImageSize="70dp"
|
||||
app:elevation="@dimen/fab_elevation"
|
||||
app:borderWidth="0dp"
|
||||
android:backgroundTint="@color/colorReceive"
|
||||
android:src="@drawable/ic_receive"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
@ -54,7 +57,9 @@
|
|||
android:id="@+id/tvReceiveTransaction"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:text="@string/title_receive"
|
||||
android:textAllCaps="true"
|
||||
android:textAppearance="@style/TextAppearance.Bitsy.Body1"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintTop_toBottomOf="@+id/fabReceiveTransaction"
|
||||
|
@ -64,10 +69,13 @@
|
|||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabSendTransactionCamera"
|
||||
style="@style/Widget.MaterialComponents.FloatingActionButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:fabSize="mini"
|
||||
android:src="@drawable/ic_camera"
|
||||
app:elevation="@dimen/fab_elevation"
|
||||
app:borderWidth="0dp"
|
||||
android:backgroundTint="@color/colorSend"
|
||||
app:layout_constraintTop_toTopOf="@id/fabSendTransaction"
|
||||
app:layout_constraintBottom_toBottomOf="@id/fabSendTransaction"
|
||||
|
@ -76,11 +84,14 @@
|
|||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabSendTransaction"
|
||||
style="@style/Widget.MaterialComponents.FloatingActionButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:fabSize="auto"
|
||||
app:fabCustomSize="120dp"
|
||||
app:maxImageSize="70dp"
|
||||
app:elevation="@dimen/fab_elevation"
|
||||
app:borderWidth="0dp"
|
||||
android:backgroundTint="@color/colorSend"
|
||||
android:src="@drawable/ic_send"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
@ -93,7 +104,9 @@
|
|||
android:id="@+id/tvSendTransaction"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:text="@string/title_send"
|
||||
android:textAllCaps="true"
|
||||
android:textAppearance="@style/TextAppearance.Bitsy.Body1"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintTop_toBottomOf="@+id/fabSendTransaction"
|
||||
|
@ -104,7 +117,7 @@
|
|||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/gray"
|
||||
android:background="@color/colorSend"
|
||||
app:layout_constraintTop_toTopOf="@id/fabSendTransactionCamera"
|
||||
app:layout_constraintBottom_toBottomOf="@id/fabSendTransactionCamera"
|
||||
app:layout_constraintStart_toEndOf="@id/fabSendTransactionCamera"
|
||||
|
|
147
app/src/main/res/layout/fragment_import_brainkey.xml
Normal file
147
app/src/main/res/layout/fragment_import_brainkey.xml
Normal file
|
@ -0,0 +1,147 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
tools:context=".fragments.ImportBrainkeyFragment">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:clickable="true">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilPin"
|
||||
style="@style/Widget.Bitsy.TextInputLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/activity_vertical_margin"
|
||||
android:layout_marginStart="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||
android:hint="@string/text_field__6_digit_pin"
|
||||
app:passwordToggleEnabled="true">
|
||||
|
||||
<cy.agorise.bitsybitshareswallet.views.MyTextInputEditText
|
||||
android:id="@+id/tietPin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="numberPassword"
|
||||
android:singleLine="true"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilPinConfirmation"
|
||||
style="@style/Widget.Bitsy.TextInputLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_same_topic"
|
||||
android:layout_marginStart="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||
android:hint="@string/text_field__confirm_pin"
|
||||
app:passwordToggleEnabled="true">
|
||||
|
||||
<cy.agorise.bitsybitshareswallet.views.MyTextInputEditText
|
||||
android:id="@+id/tietPinConfirmation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="numberPassword"
|
||||
android:singleLine="true"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/tilBrainKey"
|
||||
style="@style/Widget.Bitsy.TextInputLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_same_topic"
|
||||
android:layout_marginStart="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||
android:hint="@string/text__brain_key">
|
||||
|
||||
<cy.agorise.bitsybitshareswallet.views.MyTextInputEditText
|
||||
android:id="@+id/tietBrainKey"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textMultiLine"
|
||||
android:gravity="top"
|
||||
android:lines="4"
|
||||
android:scrollHorizontally="false"
|
||||
android:imeOptions="actionDone"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnImport"
|
||||
style="@style/Widget.Bitsy.Button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_different_topic"
|
||||
android:layout_marginStart="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||
android:text="@string/button__import_existing_account"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/activity_horizontal_margin">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="@color/black"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvOR"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:background="?android:colorBackground"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:text="@string/text__or"
|
||||
android:textAllCaps="true"
|
||||
android:textAppearance="@style/TextAppearance.Bitsy.Body1"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnCreate"
|
||||
style="@style/Widget.Bitsy.Button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||
android:text="@string/button__create_new_account"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvNetworkStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/activity_vertical_margin"
|
||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||
android:text="@string/text__view_network_status"
|
||||
android:gravity="end|center_vertical"
|
||||
android:textAppearance="@style/TextAppearance.Bitsy.Body1"
|
||||
android:drawablePadding="8dp"
|
||||
android:drawableEnd="@drawable/ic_disconnected"/>
|
||||
|
||||
</LinearLayout>
|
|
@ -5,6 +5,9 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:clickable="true"
|
||||
tools:context=".fragments.ReceiveTransactionFragment">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
|
@ -27,7 +30,7 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/centeredVerticalGuideline">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
<cy.agorise.bitsybitshareswallet.views.MyTextInputEditText
|
||||
android:id="@+id/tietAmount"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -4,14 +4,17 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".fragments.SendTransactionFragment">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context=".fragments.SendTransactionFragment">
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:clickable="true">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/centeredVerticalGuideline"
|
||||
|
@ -37,7 +40,7 @@
|
|||
android:hint="@string/text__to"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
<cy.agorise.bitsybitshareswallet.views.MyTextInputEditText
|
||||
android:id="@+id/tietTo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -61,7 +64,7 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/centeredVerticalGuideline">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
<cy.agorise.bitsybitshareswallet.views.MyTextInputEditText
|
||||
android:id="@+id/tietAmount"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -113,7 +116,7 @@
|
|||
android:hint="@string/text__memo"
|
||||
app:layout_constraintTop_toBottomOf="@id/tilAmount">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
<cy.agorise.bitsybitshareswallet.views.MyTextInputEditText
|
||||
android:id="@+id/tietMemo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -152,9 +155,11 @@
|
|||
android:layout_height="wrap_content"
|
||||
app:fabCustomSize="32dp"
|
||||
app:maxImageSize="20dp"
|
||||
app:srcCompat="@drawable/ic_camera"
|
||||
app:borderWidth="0dp"
|
||||
android:backgroundTint="@color/colorSend"
|
||||
app:layout_constraintStart_toEndOf="@id/cameraVerticalGuideline"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tvScan"
|
||||
app:srcCompat="@drawable/ic_camera" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/tvScan"/>
|
||||
|
||||
<me.dm7.barcodescanner.zxing.ZXingScannerView
|
||||
android:id="@+id/cameraPreview"
|
||||
|
@ -171,7 +176,7 @@
|
|||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:background="@drawable/send_fab_background"
|
||||
android:background="@drawable/send_fab_background_disabled"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/viewCamera"
|
||||
app:layout_constraintBottom_toBottomOf="@id/viewCamera"/>
|
||||
|
@ -185,6 +190,7 @@
|
|||
app:fabCustomSize="90dp"
|
||||
app:maxImageSize="70dp"
|
||||
app:srcCompat="@drawable/ic_arrow_forward"
|
||||
app:borderWidth="0dp"
|
||||
app:layout_constraintEnd_toEndOf="@id/vSend"
|
||||
app:layout_constraintTop_toTopOf="@+id/vSend" />
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_same_topic"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:text="View Network Status"
|
||||
android:text="@string/text__view_network_status"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/TextAppearance.Bitsy.Body1"
|
||||
android:drawableEnd="@drawable/ic_disconnected"/>
|
||||
|
@ -83,7 +83,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_same_topic"
|
||||
android:text="@string/btn__view_and_copy"/>
|
||||
android:text="@string/button__view_and_copy"/>
|
||||
|
||||
<!-- Bugs or Ideas -->
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="@color/lightGray"
|
||||
android:background="@color/superLightGray"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvFrom"
|
||||
app:layout_constraintStart_toEndOf="@id/firstVerticalGuideline"
|
||||
app:layout_constraintEnd_toStartOf="@id/fourthVerticalGuideline" />
|
||||
|
@ -133,7 +133,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="@color/lightGray"/>
|
||||
android:background="@color/superLightGray"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
11
app/src/main/res/menu/menu_send_transaction.xml
Normal file
11
app/src/main/res/menu/menu_send_transaction.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item android:id="@+id/menu_info"
|
||||
android:icon="@drawable/ic_info"
|
||||
android:title="@string/title_info"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
</menu>
|
|
@ -35,6 +35,11 @@
|
|||
app:exitAnim="@anim/slide_out_left"
|
||||
app:popEnterAnim="@anim/slide_in_left"
|
||||
app:popExitAnim="@anim/slide_out_right"/>
|
||||
|
||||
<action
|
||||
android:id="@+id/license_action"
|
||||
app:destination="@id/license_dest"/>
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
|
@ -73,4 +78,48 @@
|
|||
android:defaultValue="false" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/license_dest"
|
||||
android:name="cy.agorise.bitsybitshareswallet.fragments.LicenseFragment"
|
||||
android:label="@string/app_name"
|
||||
tools:layout="@layout/fragment_license">
|
||||
|
||||
<action
|
||||
android:id="@+id/import_brainkey_action"
|
||||
app:destination="@id/import_brainkey_dest"/>
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/import_brainkey_dest"
|
||||
android:name="cy.agorise.bitsybitshareswallet.fragments.ImportBrainkeyFragment"
|
||||
android:label="@string/app_name"
|
||||
tools:layout="@layout/fragment_import_brainkey">
|
||||
|
||||
<action
|
||||
android:id="@+id/create_account_action"
|
||||
app:destination="@id/create_account_dest"
|
||||
app:enterAnim="@anim/slide_in_right"
|
||||
app:exitAnim="@anim/slide_out_left"
|
||||
app:popEnterAnim="@anim/slide_in_left"
|
||||
app:popExitAnim="@anim/slide_out_right"/>
|
||||
|
||||
<action
|
||||
android:id="@+id/home_action"
|
||||
app:popUpTo="@id/home_dest"/>
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/create_account_dest"
|
||||
android:name="cy.agorise.bitsybitshareswallet.fragments.CreateAccountFragment"
|
||||
android:label="@string/app_name"
|
||||
tools:layout="@layout/fragment_create_account">
|
||||
|
||||
<action
|
||||
android:id="@+id/home_action"
|
||||
app:popUpTo="@id/home_dest"/>
|
||||
|
||||
</fragment>
|
||||
|
||||
</navigation>
|
109
app/src/main/res/values-es/strings.xml
Normal file
109
app/src/main/res/values-es/strings.xml
Normal file
|
@ -0,0 +1,109 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">BiTSy</string>
|
||||
|
||||
<!-- License Activity -->
|
||||
<string name="button__agree">Aceptar</string>
|
||||
<string name="button__disagree">Cancelar</string>
|
||||
|
||||
<!-- Import Brainkey -->
|
||||
<string name="text_field__6_digit_pin">PIN de 6+ dígitos</string>
|
||||
<string name="error__pin_too_short">El PIN es muy corto</string>
|
||||
<string name="text_field__confirm_pin">Confirmar PIN</string>
|
||||
<string name="error__pin_mismatch">El PIN no concuerda</string>
|
||||
<string name="text__brain_key">BrainKey</string>
|
||||
<string name="error__enter_correct_brainkey">Por favor ingresa un brainkey correcto, debe de tener entre 12 y 16 palabras.</string>
|
||||
<string name="button__import_existing_account">Importar cuenta existente</string>
|
||||
<string name="text__or">O</string>
|
||||
<string name="button__create_new_account">Crear cuenta nueva</string>
|
||||
<string name="error__invalid_brainkey">No se encontraron cuentas controladas por el brainkey dado, por favor revisa tu brainkey por errores de escritura</string>
|
||||
<string name="error__try_again">Por favor intenta nuevamente después de 5 minutos</string>
|
||||
<string name="dialog__account_candidates_title">Por favor selecciona una cuenta</string>
|
||||
<string name="dialog__account_candidates_content">Las claves derivadas de este brainkey son usadas para controlar más de una cuenta, por favor selecciona la cuenta que deseas importar</string>
|
||||
|
||||
<!-- Create Account -->
|
||||
<string name="text__bitshares_account_name">Cuenta de BitShares</string>
|
||||
<string name="error__read_dict_file">Error al leer el archivo de diccionario</string>
|
||||
<string name="error__invalid_account_name">La cuenta debe de tener más de 8 caracteres, contener un número o no contener vocales. El guion bajo no es permitido. </string>
|
||||
<string name="text__verifying_account_availability">Verificando disponibilidad de cuenta…</string>
|
||||
<string name="error__account_not_available">Cuenta no disponible</string>
|
||||
<string name="text__account_is_available">Cuenta disponible</string>
|
||||
<string name="title_error">Error</string>
|
||||
<string name="error__faucet">El servidor regresó un error. Puede ser causado por una limitación a propósito para rechazar peticiones frecuentes provenientes de la misma dirección IP en un periodo corto de tiempo. Por favor espera 5 minutos e intenta de nuevo, o cambia a una red diferente, por ejemplo de WiFi a celular.</string>
|
||||
<string name="error__faucet_template">El faucet regresó un error. Msj: %1$s</string>
|
||||
<string name="error__created_account_not_found">La aplicación no pudo obtener la información sobre la cuenta recién creada</string>
|
||||
<string name="button__create">Crear</string>
|
||||
|
||||
<!-- Home -->
|
||||
<string name="title_transactions">Transacciones</string>
|
||||
<string name="title_merchants">Comerciantes</string>
|
||||
<string name="title_receive">Recibir</string>
|
||||
<string name="title_balances">Balances</string>
|
||||
<string name="title_send">Enviar</string>
|
||||
<string name="title_net_worth">Valor neto</string>
|
||||
<string name="text__coming_soon">Próximamente</string>
|
||||
|
||||
<!-- Transactions -->
|
||||
<string name="title_search">Buscar</string>
|
||||
<string name="title_filter">Filtrar</string>
|
||||
<string name="title_export">Exportar</string>
|
||||
|
||||
<!-- Transactions filter options -->
|
||||
<string name="title_filter_options">Opciones de filtrado</string>
|
||||
<string name="text__all">Todas</string>
|
||||
<string name="text__sent">Enviadas</string>
|
||||
<string name="text__received">Recibidas</string>
|
||||
<string name="text__date_range">Rango de fechas</string>
|
||||
<string name="text__fiat_amount">Monto fiat</string>
|
||||
<string name="text__ignore_network_fees">Ignorar cuotas de red</string>
|
||||
<string name="button__filter">Filtrar</string>
|
||||
|
||||
<!-- Send Transaction -->
|
||||
<string name="title_info">Info</string>
|
||||
<string name="text__to">A</string>
|
||||
<string name="text__amount">Cantidad</string>
|
||||
<string name="text__memo">Memo</string>
|
||||
<string name="text__scan_qr">Escanear QR</string>
|
||||
<string name="error__invalid_account">Cuenta inválida</string>
|
||||
<string name="error__not_enough_funds">Sin fondos suficientes</string>
|
||||
<string name="msg__camera_permission_necessary">El permiso de cámara es necesario para leer códigos QR.</string>
|
||||
<string name="text__transaction_sent">¡Transacción enviada!</string>
|
||||
<string name="msg__transaction_not_sent">No se pudo enviar la transacción</string>
|
||||
|
||||
<!-- Send Transaction info dialog -->
|
||||
<string name="msg__to_explanation">Escribe la cuenta BitShares de la persona a la que le deseas enviar fondos.\nPor ejemplo: agorise-faucet</string>
|
||||
<string name="text__asset_balance">Balance del activo</string>
|
||||
<string name="msg__asset_balance_explanation">Puedes tocar en el balance mostrado para enviar todo lo disponible de ese activo. Al hacerlo el campo Cantidad se llenará automáticamente por ti.</string>
|
||||
<string name="msg__memo_explanation">Agregar un Memo no es necesario, pero tomar notas sobre porqué enviaste fondos es útil como referencia. Los Memos solamente son visibles para la persona que envía y recibe los fondos.</string>
|
||||
<string name="text__network_fee">Cuota de red</string>
|
||||
<string name="msg__network_fee_explanation">La cuota de red está incluida en la cantidad que deseas enviar. Por ejemplo, si deseas enviar 50 BTS, BiTSy en realidad enviará ~50.21 BTS. El agregado 0.21 en este ejemplo es la cuota de transacción de Bitshares más 0.01% para el equipo de dessarrollo de BiTSy(típicamente ~1 centavo).</string>
|
||||
<string name="text__qr_code">Código QR</string>
|
||||
<string name="msg__qr_code_explanation">No es necesario que escanees un código QR para enviar fondos, pero ayuda para evitar cometer errores. Una vez que envías fondos desde tu cuenta, se han ido para siempre, así que siempre asegúrate de que la cuenta en el campo “A” es correcta.</string>
|
||||
|
||||
<!-- Receive Transaction -->
|
||||
<string name="text__asset">Activo</string>
|
||||
<string name="text__other">Otro…</string>
|
||||
<string name="template__please_send">Por favor enviar: %1$s %2$s</string>
|
||||
<string name="text__any_amount">Cualquier Cantidad</string>
|
||||
<string name="template__to">To: %1$s</string>
|
||||
<string name="msg__invoice_subject">Invoice BiTSy de %1$s</string>
|
||||
<string name="title_share">Compartir</string>
|
||||
<string name="text__share_with">Compartir con</string>
|
||||
<string name="msg__storage__permission__necessary">El permiso de almacenamiento es necesario para compartir imágenes.</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="title_settings">Ajustes</string>
|
||||
<string name="title__general">General</string>
|
||||
<string name="msg__close_timer">Cerrar BiTSy automáticamente después de 3 minutos de inactividad</string>
|
||||
<string name="msg__night_mode">Modo nocturno</string>
|
||||
<string name="text__view_network_status">Ver Estatus de Red</string>
|
||||
<string name="title__backup">Respaldo</string>
|
||||
<string name="msg__brainkey_description">BrainKey. Palabras para respaldar cuenta que pueden ser capturadas o copiadas, pero no editadas.</string>
|
||||
<string name="msg__brainkey_info">¡Escribe esto! Asegúrate de tener 2 copias de este BrainKey en 2 lugares seguros en caso de incendio o pérdida. ¡La seguridad primero! ¡Cualquiera con acceso a tu BrainKey puede acceder a los fondos en tu cuenta!</string>
|
||||
<string name="button__copied">Copiado</string>
|
||||
<string name="button__view_and_copy">Ver y Copiar</string>
|
||||
<string name="title__bugs_or_ideas">Errores o Ideas?</string>
|
||||
<string name="msg__bugs_or_ideas">Telegram: https://t.me/Agorise\nKeybase: https://keybase.io/team/Agorise</string>
|
||||
<string name="title__bitshares_nodes_dialog">Bloque: %1$s</string>
|
||||
|
||||
</resources>
|
|
@ -6,13 +6,18 @@
|
|||
|
||||
<!-- Dark theme -->
|
||||
<color name="colorBackgroundFloating">#424242</color>
|
||||
<color name="colorToolbarDark">#424242</color>
|
||||
<color name="colorStatusBarDark">#212121</color>
|
||||
|
||||
<color name="black">#000</color>
|
||||
<color name="gray">#888</color>
|
||||
<color name="ppGreen">#139657</color>
|
||||
<color name="lightGray">#e0e0e0</color>
|
||||
<color name="lightGray">#aaa</color>
|
||||
<color name="superLightGray">#d0d0d0</color>
|
||||
<color name="darkGray">#616161</color>
|
||||
<color name="colorReceive">#669900</color>
|
||||
<color name="colorSend">#DC473A</color>
|
||||
<color name="colorReceive">#388E3C</color>
|
||||
<color name="colorReceiveDark">#1B5E20</color>
|
||||
<color name="colorSend">#D32F2F</color>
|
||||
<color name="colorSendDark">#B71C1C</color>
|
||||
<color name="semiTransparent">#2888</color>
|
||||
</resources>
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
<dimen name="spacing_different_topic">24dp</dimen>
|
||||
<dimen name="spacing_different_section">40dp</dimen>
|
||||
|
||||
<!-- Home -->
|
||||
<dimen name="fab_elevation">16dp</dimen>
|
||||
|
||||
<!-- Initial setup -->
|
||||
<dimen name="logo_size">180dp</dimen>
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -17,7 +17,7 @@
|
|||
<style name="Theme.Bitsy.AppBarOverlay" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar"/>
|
||||
|
||||
<!-- Base application dark theme. -->
|
||||
<style name="Theme.Bitsy.Dark" parent="Theme.MaterialComponents">
|
||||
<style name="Theme.Bitsy.Dark" parent="Theme.MaterialComponents.Bridge">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.0'
|
||||
ext.kotlin_version = '1.3.11'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
|
@ -11,9 +11,9 @@ buildscript {
|
|||
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.3.0-rc02'
|
||||
classpath 'com.android.tools.build:gradle:3.3.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha08"
|
||||
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha09"
|
||||
classpath 'com.google.gms:google-services:4.2.0'
|
||||
classpath 'io.fabric.tools:gradle:1.27.0'
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 101b4a5aba31aa738bc92abeb47e9db66ab5af6a
|
||||
Subproject commit 4c7c7b29b2d403e8f44a2a955e0ba22169d02a48
|
Loading…
Reference in a new issue