This commit is contained in:
Ksenia Balistreri 2020-10-13 18:18:39 +02:00
commit 3779fa1628
18 changed files with 290 additions and 147 deletions

View File

@ -16,8 +16,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".ui.SettingsActivity" />
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<activity android:name=".ui.ChangePairingPasswordActivity" />
<activity android:name=".ui.ChangePUKActivity" />
<activity android:name=".ui.ShowMnemonicActivity" />
@ -32,12 +31,19 @@
<activity android:name=".ui.ReinstallActivity" />
<activity
android:name=".ui.MainActivity"
android:label="@string/app_name">
android:label="@string/app_name"
android:launchMode="singleTask"
android:alwaysRetainTaskState="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="wc" />
</intent-filter>
</activity>
<meta-data

View File

@ -13,6 +13,8 @@ import im.status.keycard.connect.data.PairingManager
import im.status.keycard.connect.data.SettingsManager
import im.status.keycard.connect.net.EthereumRPC
import im.status.keycard.connect.net.WalletConnect
import im.status.keycard.connect.net.WalletConnectListener
import org.walletconnect.Session
@SuppressLint("StaticFieldLeak")
object Registry {
@ -52,7 +54,7 @@ object Registry {
value.add(0, KotlinJsonAdapterFactory())
}
fun init(activity: Activity, listener: ScriptListener) {
fun init(activity: Activity, listener: ScriptListener, wcListener: WalletConnectListener) {
//TODO: remove this hack, it is needed now because KEthereum does not add the KotlinJsonAdapterFactory
moshiAddKotlin()
@ -70,6 +72,6 @@ object Registry {
cardManager.start()
ethereumRPC = EthereumRPC(settingsManager.rpcEndpoint)
walletConnect = WalletConnect(settingsManager.bip32Path, settingsManager.chainID)
walletConnect = WalletConnect(wcListener, settingsManager.bip32Path, settingsManager.chainID)
}
}

View File

@ -39,15 +39,16 @@ const val SETTINGS_BIP32_PATH = "bip32Path"
const val INFURA_API_KEY = "27efcb33f94e4bd0866d1aadf8e1a12d"
const val RPC_ENDPOINT_TEMPLATE = "https://%s.infura.io/v3/${INFURA_API_KEY}"
const val XDAI_ENDPOINT = "https://rpc.xdaichain.com"
const val CHAIN_ID_MAINNET = 1L
const val CHAIN_ID_ROPSTEN = 3L
const val CHAIN_ID_RINKEBY = 4L
const val CHAIN_ID_GOERLI = 5L
const val CHAIN_ID_KOVAN = 42L
const val CHAIN_ID_XDAI = 100L
val CHAIN_ID_TO_SHORTNAME = mapOf(CHAIN_ID_MAINNET to "mainnet", CHAIN_ID_ROPSTEN to "ropsten", CHAIN_ID_RINKEBY to "rinkeby", CHAIN_ID_GOERLI to "goerli", CHAIN_ID_KOVAN to "kovan")
val CHAIN_IDS = listOf(CHAIN_ID_MAINNET, CHAIN_ID_ROPSTEN, CHAIN_ID_RINKEBY, CHAIN_ID_GOERLI, CHAIN_ID_KOVAN)
val CHAIN_IDS = listOf(CHAIN_ID_MAINNET, CHAIN_ID_XDAI, CHAIN_ID_ROPSTEN, CHAIN_ID_RINKEBY, CHAIN_ID_GOERLI, CHAIN_ID_KOVAN)
const val DEFAULT_CHAIN_ID = CHAIN_ID_MAINNET
const val DEFAULT_BIP32_PATH = "m/44'/60'/0'/0/0"

View File

@ -14,8 +14,11 @@ class SettingsManager(context: Context) {
sharedPreferences = EncryptedSharedPreferences.create(context,"settings", masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM)
}
val rpcEndpoint
get() = String.format(RPC_ENDPOINT_TEMPLATE, CHAIN_ID_TO_SHORTNAME.getValue(sharedPreferences.getLong(SETTINGS_CHAIN_ID, DEFAULT_CHAIN_ID)))
val rpcEndpoint : String
get() {
val chainID = sharedPreferences.getLong(SETTINGS_CHAIN_ID, DEFAULT_CHAIN_ID)
return if (chainID == CHAIN_ID_XDAI) XDAI_ENDPOINT else String.format(RPC_ENDPOINT_TEMPLATE, CHAIN_ID_TO_SHORTNAME.getValue(chainID))
}
var chainID
get() = sharedPreferences.getLong(SETTINGS_CHAIN_ID, DEFAULT_CHAIN_ID)

View File

@ -34,7 +34,6 @@ import org.komputing.khex.extensions.toHexString
import org.komputing.khex.extensions.toNoPrefixHexString
import org.komputing.khex.model.HexString
import org.walletconnect.Session
import org.walletconnect.Session.Config.Companion.fromWCUri
import org.walletconnect.impls.FileWCSessionStore
import org.walletconnect.impls.MoshiPayloadAdapter
import org.walletconnect.impls.OkHttpTransport
@ -44,8 +43,7 @@ import pm.gnosis.eip712.adapters.moshi.MoshiAdapter
import pm.gnosis.eip712.typedDataHash
import java.io.File
class WalletConnect(var bip32Path: String, var chainID: Long) : ExportKeyCommand.Listener, SignCommand.Listener, Session.Callback {
class WalletConnect(private val wcListener : WalletConnectListener, private var bip32Path: String, private var chainID: Long) : ExportKeyCommand.Listener, SignCommand.Listener, Session.Callback {
private val scope = MainScope()
private val moshi = Moshi.Builder().build()
private val okHttpClient = OkHttpClient()
@ -54,21 +52,23 @@ class WalletConnect(var bip32Path: String, var chainID: Long) : ExportKeyCommand
private var requestId: Long = 0
private var uiAction: (Intent?) -> Unit = this::nop
private var signAction: (RecoverableSignature) -> Unit = this::nop
var currentAccount: String? = null
private set(value) {
field = value
wcListener.onAccountChanged(value)
}
override fun onStatus(status: Session.Status) {
when (status) {
is Session.Status.Error -> println("WalletConnect Error")
is Session.Status.Approved -> println("WalletConnect Approved")
is Session.Status.Connected -> println("WalletConnect Connected")
is Session.Status.Disconnected -> println("WalletConnect Disconnected")
is Session.Status.Closed -> session = null
when(status) {
Session.Status.Approved, Session.Status.Connected -> wcListener.onConnected()
is Session.Status.Error, Session.Status.Disconnected, Session.Status.Closed -> { wcListener.onDisconnected(); session = null; currentAccount = null }
}
}
override fun onMethodCall(call: Session.MethodCall) {
scope.launch(Dispatchers.IO) {
when (call) {
is Session.MethodCall.SessionRequest -> Registry.scriptExecutor.runScript(scriptWithAuthentication().plus(ExportKeyCommand(Registry.walletConnect, bip32Path)))
is Session.MethodCall.SessionRequest -> getAccountKeys()
is Session.MethodCall.SignMessage -> signText(call.id, call.message)
is Session.MethodCall.SendTransaction -> signTransaction(call.id, toTransaction(call), true)
is Session.MethodCall.Custom -> onCustomCall(call)
@ -77,6 +77,10 @@ class WalletConnect(var bip32Path: String, var chainID: Long) : ExportKeyCommand
}
}
private fun getAccountKeys() {
Registry.scriptExecutor.runScript(scriptWithAuthentication().plus(ExportKeyCommand(Registry.walletConnect, bip32Path)))
}
private inline fun <reified T> runOnValidParam(call: Session.MethodCall.Custom, index: Int, body: (T) -> Unit) {
val param = call.params?.getOrNull(index)
@ -94,7 +98,7 @@ class WalletConnect(var bip32Path: String, var chainID: Long) : ExportKeyCommand
private fun onCustomCall(call: Session.MethodCall.Custom) {
when(call.method) {
"personal_sign" -> runOnValidParam<String>(call, 0) { signText(call.id, it) }
"eth_signTypedData" -> { runOnValidParam<String>(call, 1) { @Suppress("UNCHECKED_CAST") signTypedData(call.id, it) } }
"eth_signTypedData" -> { runOnValidParam<String>(call, 1) { signTypedData(call.id, it) } }
"eth_signTransaction" -> { runOnValidParam<Map<*, *>>(call, 0) { signTransaction(call.id, toTransaction(toSendTransaction(call.id, it)), false)} }
"eth_sendRawTransaction" -> { runOnValidParam<String>(call, 0) { relayTX(call.id, it) } }
else -> session?.rejectRequest(call.id, 1L, "Not implemented")
@ -134,7 +138,7 @@ class WalletConnect(var bip32Path: String, var chainID: Long) : ExportKeyCommand
Registry.scriptExecutor.runScript(scriptWithAuthentication().plus(SignCommand(Registry.walletConnect, hash)))
}
signAction = { session?.approveRequest(requestId, "0x${it.r.toNoPrefixHexString()}${it.s.toNoPrefixHexString()}${encode((it.recId + 27).toByte())}") }
signAction = { session?.approveRequest(requestId, formatDataSignature(it)) }
val intent = Intent(Registry.mainActivity, SignMessageActivity::class.java).apply {
putExtra(SIGN_TEXT_MESSAGE, text)
@ -151,7 +155,7 @@ class WalletConnect(var bip32Path: String, var chainID: Long) : ExportKeyCommand
Registry.scriptExecutor.runScript(scriptWithAuthentication().plus(SignCommand(Registry.walletConnect, hash)))
}
signAction = { session?.approveRequest(requestId, "0x${it.r.toNoPrefixHexString()}${it.s.toNoPrefixHexString()}${encode((it.recId + 27).toByte())}") }
signAction = { session?.approveRequest(requestId, formatDataSignature(it)) }
val intent = Intent(Registry.mainActivity, SignMessageActivity::class.java).apply {
putExtra(SIGN_TEXT_MESSAGE, message)
@ -160,6 +164,8 @@ class WalletConnect(var bip32Path: String, var chainID: Long) : ExportKeyCommand
Registry.mainActivity.startActivityForResult(intent, REQ_WALLETCONNECT)
}
private fun formatDataSignature(sig: RecoverableSignature) : String = "0x${sig.r.toNoPrefixHexString()}${sig.s.toNoPrefixHexString()}${encode((sig.recId + 27).toByte())}"
private fun signTransaction(id: Long, tx: Transaction, send: Boolean) {
requestId = id
@ -215,12 +221,12 @@ class WalletConnect(var bip32Path: String, var chainID: Long) : ExportKeyCommand
uiAction = this::nop
}
fun connect(uri: String) {
fun connect(uri: Session.FullyQualifiedConfig) {
scope.launch(Dispatchers.IO) {
session?.kill()
session = WCSession(
fromWCUri(uri).toFullyQualifiedConfig(),
uri,
MoshiPayloadAdapter(moshi),
sessionStore,
OkHttpTransport.Builder(okHttpClient, moshi),
@ -232,10 +238,35 @@ class WalletConnect(var bip32Path: String, var chainID: Long) : ExportKeyCommand
}
}
fun disconnect() {
session?.kill()
}
fun updateChainAndDerivation(newBip32Path: String, newChainID: Long) {
if (newBip32Path == bip32Path) {
if (newChainID != chainID) {
this.chainID = newChainID
if (session != null && currentAccount != null) {
session?.update(listOf(currentAccount!!), this.chainID)
}
}
} else {
this.bip32Path = newBip32Path
this.chainID = newChainID
if (session != null && currentAccount != null) {
getAccountKeys()
}
}
}
override fun onResponse(keyPair: BIP32KeyPair) {
scope.launch(Dispatchers.IO) {
val addr = keyPair.toEthereumAddress().toHexString()
session?.approve(listOf(addr), chainID)
currentAccount = keyPair.toEthereumAddress().toHexString()
if (session?.approvedAccounts() != null) {
session?.update(listOf(currentAccount!!), chainID)
} else {
session?.approve(listOf(currentAccount!!), chainID)
}
}
}

View File

@ -0,0 +1,7 @@
package im.status.keycard.connect.net
interface WalletConnectListener {
fun onConnected()
fun onDisconnected()
fun onAccountChanged(account: String?)
}

View File

@ -48,6 +48,11 @@ class InitActivity : AppCompatActivity() {
finish()
}
override fun onBackPressed() {
setResult(Activity.RESULT_CANCELED)
finish()
}
private fun randomToken(length: Int): String {
return Base64.encodeToString(Crypto.randomBytes(length), NO_PADDING or NO_WRAP)
}

View File

@ -43,4 +43,9 @@ class LoadKeyActivity : AppCompatActivity() {
setResult(Activity.RESULT_CANCELED)
finish()
}
override fun onBackPressed() {
setResult(Activity.RESULT_CANCELED)
finish()
}
}

View File

@ -6,7 +6,7 @@ import android.nfc.NfcAdapter
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.widget.ViewSwitcher
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import com.google.zxing.client.android.Intents
import com.google.zxing.integration.android.IntentIntegrator
@ -14,10 +14,14 @@ import im.status.keycard.connect.R
import im.status.keycard.connect.Registry
import im.status.keycard.connect.card.*
import im.status.keycard.connect.data.*
import im.status.keycard.connect.net.WalletConnectListener
import org.walletconnect.Session.Config.Companion.fromWCUri
import kotlin.reflect.KClass
class MainActivity : AppCompatActivity(), ScriptListener {
class MainActivity : AppCompatActivity(), ScriptListener, WalletConnectListener {
private lateinit var viewSwitcher: ViewSwitcher
private lateinit var networkSpinner: Spinner
private lateinit var walletPath: EditText
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -28,8 +32,20 @@ class MainActivity : AppCompatActivity(), ScriptListener {
inflater.inflate(R.layout.activity_nfc, viewSwitcher)
setContentView(viewSwitcher)
Registry.init(this, this)
Registry.init(this, this, this)
Registry.scriptExecutor.defaultScript = cardCheckupScript()
networkSpinner = findViewById(R.id.networkSpinner)
walletPath = findViewById(R.id.walletPathText)
ArrayAdapter.createFromResource(this, R.array.networks, android.R.layout.simple_spinner_item).also {
it.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
networkSpinner.adapter = it
}
networkSpinner.setSelection(CHAIN_IDS.indexOf(Registry.settingsManager.chainID))
walletPath.setText(Registry.settingsManager.bip32Path)
handleIntent(intent)
}
override fun onResume() {
@ -42,6 +58,25 @@ class MainActivity : AppCompatActivity(), ScriptListener {
Registry.nfcAdapter.disableReaderMode(this)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
handleIntent(intent)
}
private fun handleIntent(intent: Intent?) {
if (intent?.action == Intent.ACTION_VIEW) {
handleWCURI(intent.data?.toString())
}
}
override fun onBackPressed() {
if (viewSwitcher.displayedChild == 0) {
moveTaskToBack(false)
} else {
Registry.scriptExecutor.cancelScript()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
@ -66,6 +101,17 @@ class MainActivity : AppCompatActivity(), ScriptListener {
}
}
fun updateConnection(@Suppress("UNUSED_PARAMETER") view: View) {
val chainID = CHAIN_IDS[networkSpinner.selectedItemPosition]
Registry.settingsManager.chainID = chainID
Registry.ethereumRPC.changeEndpoint(Registry.settingsManager.rpcEndpoint)
val bip32Path = walletPath.text.toString()
Registry.settingsManager.bip32Path = bip32Path
Registry.walletConnect.updateChainAndDerivation(bip32Path, chainID)
}
fun cancelNFC(@Suppress("UNUSED_PARAMETER") view: View) {
Registry.scriptExecutor.cancelScript()
}
@ -77,6 +123,10 @@ class MainActivity : AppCompatActivity(), ScriptListener {
integrator.initiateScan()
}
fun disconnectWallet(@Suppress("UNUSED_PARAMETER") view: View) {
Registry.walletConnect.disconnect()
}
fun changePIN(@Suppress("UNUSED_PARAMETER") view: View) {
startCommand(ChangePINActivity::class)
}
@ -110,10 +160,6 @@ class MainActivity : AppCompatActivity(), ScriptListener {
startCommand(ReinstallActivity::class)
}
fun settings(@Suppress("UNUSED_PARAMETER") view: View) {
startCommand(SettingsActivity::class)
}
private fun loadKeyHandler(resultCode: Int, data: Intent?) {
if (resultCode != Activity.RESULT_OK || data == null) return
@ -131,10 +177,31 @@ class MainActivity : AppCompatActivity(), ScriptListener {
private fun qrCodeScanned(resultCode: Int, data: Intent?) {
if (resultCode != Activity.RESULT_OK || data == null) return
val uri: String? = data.getStringExtra(Intents.Scan.RESULT)
handleWCURI(data.getStringExtra(Intents.Scan.RESULT))
if (uri != null && uri.startsWith("wc:")) {
Registry.walletConnect.connect(uri)
}
private fun handleWCURI(uri: String?) {
if (uri != null) {
try {
Registry.walletConnect.connect(fromWCUri(uri).toFullyQualifiedConfig())
} catch (e: Exception) {}
}
}
override fun onConnected() {
val button = findViewById<Button>(R.id.walletConnectButton)
button.setOnClickListener(this::disconnectWallet)
button.text = getString(R.string.disconnect_wallet)
}
override fun onDisconnected() {
val button = findViewById<Button>(R.id.walletConnectButton)
button.setOnClickListener(this::connectWallet)
button.text = getString(R.string.connect_wallet)
}
override fun onAccountChanged(account: String?) {
findViewById<TextView>(R.id.walletAddress).text = account
}
}

View File

@ -47,4 +47,9 @@ class PINActivity : AppCompatActivity() {
setResult(Activity.RESULT_CANCELED)
finish()
}
override fun onBackPressed() {
setResult(Activity.RESULT_CANCELED)
finish()
}
}

View File

@ -48,6 +48,11 @@ class PUKActivity : AppCompatActivity() {
finish()
}
override fun onBackPressed() {
setResult(Activity.RESULT_CANCELED)
finish()
}
private fun validateFields() {
val pukText = findViewById<EditText>(R.id.pukText).text.toString()
val pinText = findViewById<EditText>(R.id.newPINText).text.toString()

View File

@ -31,4 +31,9 @@ class PairingActivity : AppCompatActivity() {
setResult(Activity.RESULT_CANCELED)
finish()
}
override fun onBackPressed() {
setResult(Activity.RESULT_CANCELED)
finish()
}
}

View File

@ -1,49 +0,0 @@
package im.status.keycard.connect.ui
import android.os.Bundle
import android.view.View
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.Spinner
import androidx.appcompat.app.AppCompatActivity
import im.status.keycard.connect.R
import im.status.keycard.connect.Registry
import im.status.keycard.connect.data.CHAIN_IDS
class SettingsActivity : AppCompatActivity() {
lateinit var networkSpinner : Spinner
lateinit var walletPath : EditText
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
networkSpinner = findViewById(R.id.networkSpinner)
ArrayAdapter.createFromResource(this, R.array.networks, android.R.layout.simple_spinner_item).also {
it.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
networkSpinner.adapter = it
}
networkSpinner.setSelection(CHAIN_IDS.indexOf(Registry.settingsManager.chainID))
walletPath = findViewById(R.id.walletPathText)
walletPath.setText(Registry.settingsManager.bip32Path)
}
fun ok(@Suppress("UNUSED_PARAMETER") view: View) {
val chainID = CHAIN_IDS[networkSpinner.selectedItemPosition]
Registry.settingsManager.chainID = chainID
Registry.ethereumRPC.changeEndpoint(Registry.settingsManager.rpcEndpoint)
Registry.walletConnect.chainID = chainID
val bip32Path = walletPath.text.toString()
Registry.settingsManager.bip32Path = bip32Path
Registry.walletConnect.bip32Path = bip32Path
finish()
}
fun cancel(@Suppress("UNUSED_PARAMETER") view: View) {
finish()
}
}

View File

@ -26,4 +26,9 @@ class SignMessageActivity : AppCompatActivity() {
setResult(Activity.RESULT_CANCELED)
finish()
}
override fun onBackPressed() {
setResult(Activity.RESULT_CANCELED)
finish()
}
}

View File

@ -32,4 +32,9 @@ class SignTransactionActivity : AppCompatActivity() {
setResult(Activity.RESULT_CANCELED)
finish()
}
override fun onBackPressed() {
setResult(Activity.RESULT_CANCELED)
finish()
}
}

View File

@ -5,74 +5,32 @@
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize"
tools:context=".ui.MainActivity">
<Button
android:id="@+id/changePINButton"
android:layout_width="236dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:onClick="changePIN"
android:text="@string/change_pin"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/walletConnectButton" />
<Button
android:id="@+id/walletConnectButton"
android:layout_width="236dp"
android:layout_height="wrap_content"
android:layout_marginTop="188dp"
android:onClick="connectWallet"
android:text="@string/connect_wallet"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/changePUKButton"
android:layout_width="236dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:onClick="changePUK"
android:text="@string/change_puk"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/changePINButton" />
<Button
android:id="@+id/changePairingPasswordButton"
android:layout_width="236dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginTop="28dp"
android:onClick="changePairingPassword"
android:text="@string/change_pairing_password"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/changePUKButton" />
<Button
android:id="@+id/unpairButton"
android:layout_width="236dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:onClick="unpair"
android:text="@string/unpair"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/changePairingPasswordButton" />
<Button
android:id="@+id/unpairOthers"
android:layout_width="236dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:onClick="unpairOthers"
android:text="@string/unpair_others"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/unpairButton" />
<Button
android:id="@+id/changeKeyButton"
android:layout_width="236dp"
@ -81,20 +39,10 @@
android:onClick="changeKey"
android:text="@string/change_key"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.502"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/unpairOthers" />
<Button
android:id="@+id/removeKey"
android:layout_width="236dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:onClick="removeKey"
android:text="@string/remove_key"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/changeKeyButton" />
<Button
android:id="@+id/reinstall"
android:layout_width="236dp"
@ -108,13 +56,102 @@
app:layout_constraintTop_toBottomOf="@+id/removeKey" />
<Button
android:id="@+id/settingsButton"
android:id="@+id/unpairButton"
android:layout_width="138dp"
android:layout_height="38dp"
android:layout_marginTop="24dp"
android:onClick="unpair"
android:text="@string/unpair"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.131"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/changePairingPasswordButton" />
<Button
android:id="@+id/removeKey"
android:layout_width="236dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:onClick="settings"
android:text="@string/settings"
android:onClick="removeKey"
android:text="@string/remove_key"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.502"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/changeKeyButton" />
<Button
android:id="@+id/unpairOthers"
android:layout_width="163dp"
android:layout_height="42dp"
android:layout_marginTop="24dp"
android:onClick="unpairOthers"
android:text="@string/unpair_others"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.806"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/changePairingPasswordButton" />
<Button
android:id="@+id/changePUKButton"
android:layout_width="149dp"
android:layout_height="46dp"
android:layout_marginTop="296dp"
android:layout_marginEnd="60dp"
android:onClick="changePUK"
android:text="@string/change_puk"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/changePINButton"
android:layout_width="131dp"
android:layout_height="50dp"
android:layout_marginStart="16dp"
android:layout_marginTop="296dp"
android:onClick="changePIN"
android:text="@string/change_pin"
app:layout_constraintEnd_toStartOf="@+id/changePUKButton"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Spinner
android:id="@+id/networkSpinner"
android:layout_width="407dp"
android:layout_height="42dp"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/reinstall" />
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/walletAddress"
android:layout_width="375dp"
android:layout_height="36dp"
android:layout_marginStart="16dp"
android:layout_marginTop="80dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/networkSpinner" />
<EditText
android:id="@+id/walletPathText"
android:layout_width="249dp"
android:layout_height="42dp"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:ems="10"
android:inputType="textPersonName"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/networkSpinner" />
<Button
android:id="@+id/updateButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginEnd="32dp"
android:onClick="updateConnection"
android:text="@string/update_connection"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/networkSpinner" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -2,6 +2,7 @@
<resources>
<string-array name="networks">
<item>Ethereum Mainnet</item>
<item>xDAI Network</item>
<item>Ropsten PoW Testnet</item>
<item>Rinkeby PoA Testnet</item>
<item>Görli PoA Testnet</item>

View File

@ -45,4 +45,6 @@
<string name="reinstall_ndef_checkbox">Install NDEF applet</string>
<string name="reinstall_button">Select cap file and install</string>
<string name="btn_change">Change</string>
<string name="disconnect_wallet">Disconnect</string>
<string name="update_connection">Update</string>
</resources>