- Make an UX improvement to let the user import the account directly from the keyboard when typing the brainkey in the TextField.
- Add the MaterialDialogs library and use it to let the user choose the desired account to import when the brainkey controls more than one account.
This commit is contained in:
parent
22a0735379
commit
847e8a8d7f
8 changed files with 331 additions and 34 deletions
|
@ -22,6 +22,11 @@ android {
|
||||||
resValue("string", "PORT_NUMBER", "8082")
|
resValue("string", "PORT_NUMBER", "8082")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
android.packagingOptions {
|
||||||
|
exclude 'lib/x86_64/darwin/libscrypt.dylib'
|
||||||
|
exclude 'lib/x86_64/freebsd/libscrypt.so'
|
||||||
|
exclude 'lib/x86_64/linux/libscrypt.so'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -34,9 +39,6 @@ dependencies {
|
||||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
|
||||||
implementation 'com.google.android.material:material:1.0.0'
|
implementation 'com.google.android.material:material:1.0.0'
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
|
||||||
implementation 'com.google.zxing:core:3.3.1'
|
|
||||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
|
||||||
|
|
||||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||||
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
|
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
|
||||||
|
@ -44,6 +46,11 @@ dependencies {
|
||||||
implementation "androidx.room:room-runtime:$room_version"
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
kapt "androidx.room:room-compiler:$room_version"
|
kapt "androidx.room:room-compiler:$room_version"
|
||||||
|
|
||||||
|
implementation 'org.bitcoinj:bitcoinj-core:0.14.3'
|
||||||
|
implementation 'com.google.zxing:core:3.3.1'
|
||||||
|
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||||
|
implementation 'com.afollestad.material-dialogs:core:2.0.0-rc1'
|
||||||
|
|
||||||
// Android Debug Database
|
// Android Debug Database
|
||||||
debugImplementation 'com.amitshekhar.android:debug-db:1.0.4'
|
debugImplementation 'com.amitshekhar.android:debug-db:1.0.4'
|
||||||
|
|
||||||
|
|
|
@ -23,12 +23,9 @@
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".activities.SettingsActivity"
|
android:name=".activities.SettingsActivity"
|
||||||
android:label="@string/title_settings">
|
android:label="@string/title_settings"/>
|
||||||
</activity>
|
<activity android:name=".activities.SendTransactionActivity"/>
|
||||||
<activity android:name=".activities.SendTransactionActivity">
|
<activity android:name=".activities.ReceiveTransactionActivity"/>
|
||||||
</activity>
|
|
||||||
<activity android:name=".activities.ReceiveTransactionActivity">
|
|
||||||
</activity>
|
|
||||||
<activity android:name=".activities.MainActivity"/>
|
<activity android:name=".activities.MainActivity"/>
|
||||||
<activity android:name=".activities.LicenseActivity"/>
|
<activity android:name=".activities.LicenseActivity"/>
|
||||||
<activity android:name=".activities.ImportBrainkeyActivity"/>
|
<activity android:name=".activities.ImportBrainkeyActivity"/>
|
||||||
|
|
|
@ -4,15 +4,21 @@ import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.preference.PreferenceManager
|
import android.os.PersistableBundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
import cy.agorise.graphenej.api.ConnectionStatusUpdate
|
||||||
import cy.agorise.graphenej.api.android.NetworkService
|
import cy.agorise.graphenej.api.android.NetworkService
|
||||||
import cy.agorise.graphenej.api.calls.GetFullAccounts
|
import cy.agorise.graphenej.api.android.RxBus
|
||||||
import java.util.ArrayList
|
import cy.agorise.graphenej.models.FullAccountDetails
|
||||||
|
import cy.agorise.graphenej.models.HistoryOperationDetail
|
||||||
|
import cy.agorise.graphenej.models.JsonRpcResponse
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class in charge of managing the connection to graphenej's NetworkService
|
* Class in charge of managing the connection to graphenej's NetworkService
|
||||||
|
@ -23,6 +29,11 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
||||||
|
|
||||||
private val mHandler = Handler()
|
private val mHandler = Handler()
|
||||||
|
|
||||||
|
// Disposable returned at the bus subscription
|
||||||
|
private var mDisposable: Disposable? = null
|
||||||
|
|
||||||
|
private var storedOpCount: Long = -1
|
||||||
|
|
||||||
/* Network service connection */
|
/* Network service connection */
|
||||||
protected var mNetworkService: NetworkService? = null
|
protected var mNetworkService: NetworkService? = null
|
||||||
|
|
||||||
|
@ -31,6 +42,48 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
||||||
*/
|
*/
|
||||||
private var mShouldUnbindNetwork: Boolean = false
|
private var mShouldUnbindNetwork: Boolean = false
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
mDisposable = RxBus.getBusInstance()
|
||||||
|
.asFlowable()
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { message ->
|
||||||
|
if (message is JsonRpcResponse<*>) {
|
||||||
|
// Generic processing taken care by subclasses
|
||||||
|
handleJsonRpcResponse(message)
|
||||||
|
// Payment detection focused responses
|
||||||
|
if (message.error == null) {
|
||||||
|
// if (message.result is List<*> && (message.result as List<*>).size > 0) {
|
||||||
|
// if ((message.result as List<*>)[0] is FullAccountDetails) {
|
||||||
|
// if (message.id == recurrentAccountUpdateId) {
|
||||||
|
// handleAccountDetails((message.result as List<*>)[0] as FullAccountDetails)
|
||||||
|
// } else if (message.id == postProcessingAccountUpdateId) {
|
||||||
|
// handleAccountUpdate((message.result as List<*>)[0] as FullAccountDetails)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else if (message.result is HistoryOperationDetail && message.id == accountOpRequestId) {
|
||||||
|
// handleNewOperations(message.result as HistoryOperationDetail)
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
// In case of error
|
||||||
|
Log.e(TAG, "Got error message from full node. Msg: " + message.error.message)
|
||||||
|
Toast.makeText(
|
||||||
|
this@ConnectedActivity,
|
||||||
|
String.format("Error from full node. Msg: %s", message.error.message),
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
// } else if (message is ConnectionStatusUpdate) {
|
||||||
|
// handleConnectionStatusUpdate(message)
|
||||||
|
// if (message.updateCode == ConnectionStatusUpdate.DISCONNECTED) {
|
||||||
|
// recurrentAccountUpdateId = -1
|
||||||
|
// accountOpRequestId = -1
|
||||||
|
// isProcessingTx = false
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
|
@ -81,4 +134,16 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
||||||
|
|
||||||
override fun onServiceDisconnected(name: ComponentName?) {
|
override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,13 +1,30 @@
|
||||||
package cy.agorise.bitsybitshareswallet.activities
|
package cy.agorise.bitsybitshareswallet.activities
|
||||||
|
|
||||||
|
import android.content.DialogInterface
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.preference.PreferenceManager
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.widget.Toast
|
||||||
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
import com.afollestad.materialdialogs.list.listItems
|
||||||
import cy.agorise.bitsybitshareswallet.R
|
import cy.agorise.bitsybitshareswallet.R
|
||||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||||
|
import cy.agorise.graphenej.Address
|
||||||
import cy.agorise.graphenej.BrainKey
|
import cy.agorise.graphenej.BrainKey
|
||||||
import cy.agorise.graphenej.UserAccount
|
import cy.agorise.graphenej.UserAccount
|
||||||
|
import cy.agorise.graphenej.api.ConnectionStatusUpdate
|
||||||
|
import cy.agorise.graphenej.api.calls.GetAccounts
|
||||||
|
import cy.agorise.graphenej.api.calls.GetKeyReferences
|
||||||
|
import cy.agorise.graphenej.models.AccountProperties
|
||||||
|
import cy.agorise.graphenej.models.JsonRpcResponse
|
||||||
import kotlinx.android.synthetic.main.activity_import_brainkey.*
|
import kotlinx.android.synthetic.main.activity_import_brainkey.*
|
||||||
|
import org.bitcoinj.core.ECKey
|
||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
class ImportBrainkeyActivity : ConnectedActivity() {
|
class ImportBrainkeyActivity : ConnectedActivity() {
|
||||||
|
private val TAG = "ImportBrainkeyActivity"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private variable that will hold an instance of the [BrainKey] class
|
* Private variable that will hold an instance of the [BrainKey] class
|
||||||
|
@ -34,13 +51,19 @@ class ImportBrainkeyActivity : ConnectedActivity() {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_import_brainkey)
|
setContentView(R.layout.activity_import_brainkey)
|
||||||
|
|
||||||
|
// Custom event to activate import account from the keyboard
|
||||||
|
tietBrainKey.setOnEditorActionListener { _, actionId, _ ->
|
||||||
|
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||||
|
importAccount()
|
||||||
|
true
|
||||||
|
} else
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
btnImport.setOnClickListener { importAccount() }
|
btnImport.setOnClickListener { importAccount() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun importAccount() {
|
private fun importAccount() {
|
||||||
val trimmedBrainKey = tietBrainKey.text!!.toString().trim { it <= ' ' }
|
|
||||||
tietBrainKey.setText(trimmedBrainKey)
|
|
||||||
|
|
||||||
tilPin.isErrorEnabled = false
|
tilPin.isErrorEnabled = false
|
||||||
tilPinConfirmation.isErrorEnabled = false
|
tilPinConfirmation.isErrorEnabled = false
|
||||||
tilBrainKey.isErrorEnabled = false
|
tilBrainKey.isErrorEnabled = false
|
||||||
|
@ -52,12 +75,166 @@ class ImportBrainkeyActivity : ConnectedActivity() {
|
||||||
else if (tietBrainKey.text!!.isEmpty() || !tietBrainKey.text.toString().contains(" "))
|
else if (tietBrainKey.text!!.isEmpty() || !tietBrainKey.text.toString().contains(" "))
|
||||||
tilBrainKey.error = getString(R.string.error__enter_correct_brainkey)
|
tilBrainKey.error = getString(R.string.error__enter_correct_brainkey)
|
||||||
else {
|
else {
|
||||||
val brainKey = tietBrainKey.text.toString().split(" ")
|
val brainKey = tietBrainKey.text.toString()
|
||||||
if (brainKey.size in 12..16) {
|
if (brainKey.split(" ").size in 12..16)
|
||||||
// TODO verify brainkey
|
verifyBrainKey(false)
|
||||||
} else
|
else
|
||||||
tilBrainKey.error = getString(R.string.error__enter_correct_brainkey)
|
tilBrainKey.error = getString(R.string.error__enter_correct_brainkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that will verify the provided brain key, and if valid will retrieve the account information
|
||||||
|
* associated to the user id.
|
||||||
|
*
|
||||||
|
* This method will perform a network lookup to look which accounts use the public key associated
|
||||||
|
* with the user provided brainkey.
|
||||||
|
*
|
||||||
|
* Some sources use brainkeys in capital letters, while others use lowercase. The activity should
|
||||||
|
* initially call this method with the 'switchCase' parameter as false, in order to try the
|
||||||
|
* brainkey as it was provided by the user.
|
||||||
|
*
|
||||||
|
* But in case this lookup fails, it is expected that the activity makes another attempt. This time
|
||||||
|
* with the 'switchCase' argument set to true.
|
||||||
|
*
|
||||||
|
* If both attempts fail, then we can be certain that the provided brainkey is not currently
|
||||||
|
* associated with any account.
|
||||||
|
*
|
||||||
|
* @param switchCase Whether to switch the case used in the brainkey or not.
|
||||||
|
*/
|
||||||
|
private fun verifyBrainKey(switchCase: Boolean) {
|
||||||
|
//showDialog("", getString(R.string.importing_your_wallet))
|
||||||
|
val brainKey = tietBrainKey.text.toString()
|
||||||
|
// Should we switch the brainkey case?
|
||||||
|
if (switchCase) {
|
||||||
|
if (Character.isUpperCase(brainKey.toCharArray()[brainKey.length - 1])) {
|
||||||
|
// If the last character is an uppercase, we assume the whole brainkey
|
||||||
|
// was given in capital letters and turn it to lowercase
|
||||||
|
getAccountFromBrainkey(brainKey.toLowerCase())
|
||||||
|
} else {
|
||||||
|
// Otherwise we turn the whole brainkey to capital letters
|
||||||
|
getAccountFromBrainkey(brainKey.toUpperCase())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If no case switching should take place, we perform the network call with
|
||||||
|
// the brainkey as it was provided to us.
|
||||||
|
getAccountFromBrainkey(brainKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that will send a network request asking for all the accounts that make use of the
|
||||||
|
* key derived from a give brain key.
|
||||||
|
*
|
||||||
|
* @param brainKey The brain key the user has just typed
|
||||||
|
*/
|
||||||
|
private fun getAccountFromBrainkey(brainKey: String) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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)
|
||||||
|
.listItems(items = candidates) { dialog, index, _ ->
|
||||||
|
if (index >= 0) {
|
||||||
|
// If one account was selected, we keep a reference to it and
|
||||||
|
// store the account properties
|
||||||
|
// TODO make sure this is reached
|
||||||
|
mUserAccount = mUserAccountCandidates!![index]
|
||||||
|
onAccountSelected(accountPropertiesList[index])
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.negativeButton(android.R.string.cancel) { mKeyReferencesAttempts = 0 }
|
||||||
|
.show()
|
||||||
|
} else if (accountPropertiesList.size == 1) {
|
||||||
|
onAccountSelected(accountPropertiesList[0])
|
||||||
|
} else {
|
||||||
|
Toast.makeText(applicationContext, R.string.error__try_again, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleConnectionStatusUpdate(connectionStatusUpdate: ConnectionStatusUpdate) {
|
||||||
|
Log.d(TAG, "handleConnectionStatusUpdate. code: " + connectionStatusUpdate.updateCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
private fun onAccountSelected(accountProperties: AccountProperties) {
|
||||||
|
mUserAccount!!.name = accountProperties.name
|
||||||
|
|
||||||
|
Toast.makeText(this, "Account: "+accountProperties.name, Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
|
val password = tietPin.text!!.toString()
|
||||||
|
|
||||||
|
// Stores the accounts this key refers to
|
||||||
|
// database.putOwnedUserAccounts(applicationContext, mUserAccount, password)
|
||||||
|
|
||||||
|
// Stores the id of the currently active user account
|
||||||
|
// PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||||
|
// .edit()
|
||||||
|
// .putString(Constants.KEY_CURRENT_ACCOUNT_ID, mUserAccount!!.objectId)
|
||||||
|
// .apply()
|
||||||
|
|
||||||
|
// Trying to store all possible authorities (owner, active and memo)
|
||||||
|
// for (i in 0..2) {
|
||||||
|
// mBrainKey.setSequenceNumber(i)
|
||||||
|
// saveAccountAuthorities(mBrainKey, accountProperties)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO move to MainActivity
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -9,6 +9,9 @@ object Constants {
|
||||||
/** Version of the currently used license */
|
/** Version of the currently used license */
|
||||||
const val CURRENT_LICENSE_VERSION = 1
|
const val CURRENT_LICENSE_VERSION = 1
|
||||||
|
|
||||||
|
/** 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 */
|
/** The minimum required length for a PIN number */
|
||||||
const val MIN_PIN_LENGTH = 6
|
const val MIN_PIN_LENGTH = 6
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -59,7 +59,7 @@
|
||||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||||
android:hint="@string/text__brain_key">
|
android:hint="@string/text__brain_key">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<cy.agorise.bitsybitshareswallet.views.MyTextInputEditText
|
||||||
android:id="@+id/tietBrainKey"
|
android:id="@+id/tietBrainKey"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
|
@ -16,6 +16,11 @@
|
||||||
<string name="error__enter_correct_brainkey">Please enter correct brainkey, it should have between 12 and 16 words.</string>
|
<string name="error__enter_correct_brainkey">Please enter correct brainkey, it should have between 12 and 16 words.</string>
|
||||||
<string name="button__import">Import</string>
|
<string name="button__import">Import</string>
|
||||||
<string name="button__create">Create</string>
|
<string name="button__create">Create</string>
|
||||||
|
<!-- TODO improve below error explanation -->
|
||||||
|
<string name="error__invalid_brainkey">Invalid account, please check your brainkey for typing errors</string>
|
||||||
|
<string name="error__try_again">Please try again after 5 minutes</string>
|
||||||
|
<string name="dialog__account_candidates_title">Please select an account</string>
|
||||||
|
<string name="dialog__account_candidates_content">The keys derived from this brainkey seem to be used to control more than one account, please select which account you wish to import</string>
|
||||||
|
|
||||||
<!-- Main Activity -->
|
<!-- Main Activity -->
|
||||||
<string name="title_activity_main">MainActivity</string>
|
<string name="title_activity_main">MainActivity</string>
|
||||||
|
|
Loading…
Reference in a new issue