mirror of
https://github.com/yogeshpaliyal/KeyPass.git
synced 2026-01-06 09:09:44 -06:00
Migrate TOTP screen to Compose from XML (#616)
* migrate totp * formatting fixes
This commit is contained in:
committed by
GitHub
parent
9cc2ac9860
commit
9ec302908d
@@ -20,9 +20,6 @@
|
||||
<activity
|
||||
android:name=".ui.CrashActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ui.addTOTP.AddTOTPActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ui.addTOTP.ScannerActivity"
|
||||
android:exported="false" />
|
||||
|
||||
@@ -1,142 +1,202 @@
|
||||
package com.yogeshpaliyal.keypass.ui.addTOTP
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.Observer
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.zxing.integration.android.IntentIntegrator
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.ArrowBackIosNew
|
||||
import androidx.compose.material.icons.rounded.Delete
|
||||
import androidx.compose.material.icons.rounded.Done
|
||||
import androidx.compose.material3.BottomAppBar
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.yogeshpaliyal.common.data.AccountModel
|
||||
import com.yogeshpaliyal.common.utils.TOTPHelper
|
||||
import com.yogeshpaliyal.keypass.R
|
||||
import com.yogeshpaliyal.keypass.databinding.ActivityAddTotpactivityBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import com.yogeshpaliyal.keypass.ui.detail.DeleteConfirmation
|
||||
import com.yogeshpaliyal.keypass.ui.detail.KeyPassInputField
|
||||
import com.yogeshpaliyal.keypass.ui.detail.QRScanner
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.Action
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.GoBackAction
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.ToastAction
|
||||
import org.reduxkotlin.compose.rememberTypedDispatcher
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AddTOTPActivity : AppCompatActivity() {
|
||||
@Composable
|
||||
fun TOTPScreen(id: String? = null, viewModel: AddTOTPViewModel = hiltViewModel()) {
|
||||
val dispatchAction = rememberTypedDispatcher<Action>()
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARG_ACCOUNT_ID = "account_id"
|
||||
|
||||
@JvmStatic
|
||||
fun start(context: Context?, accountId: String? = null) {
|
||||
val starter = Intent(context, AddTOTPActivity::class.java)
|
||||
starter.putExtra(ARG_ACCOUNT_ID, accountId)
|
||||
context?.startActivity(starter)
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var binding: ActivityAddTotpactivityBinding
|
||||
|
||||
private val mViewModel by viewModels<AddTOTPViewModel>()
|
||||
|
||||
private val accountId by lazy {
|
||||
intent.extras?.getString(ARG_ACCOUNT_ID)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityAddTotpactivityBinding.inflate(layoutInflater)
|
||||
binding.mViewModel = mViewModel
|
||||
binding.lifecycleOwner = this
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(binding.toolbar)
|
||||
|
||||
binding.tilSecretKey.isVisible = accountId == null
|
||||
mViewModel.loadOldAccount(accountId)
|
||||
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
onBackPressed()
|
||||
}
|
||||
|
||||
binding.tilSecretKey.setEndIconOnClickListener {
|
||||
// ScannerActivity.start(this)
|
||||
IntentIntegrator(this).setPrompt("").initiateScan()
|
||||
}
|
||||
|
||||
mViewModel.error.observe(
|
||||
this,
|
||||
Observer {
|
||||
it?.getContentIfNotHandled()?.let {
|
||||
Snackbar.make(binding.root, it, Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
// task value state
|
||||
val (accountModel, setAccountModel) = remember {
|
||||
mutableStateOf(
|
||||
AccountModel()
|
||||
)
|
||||
}
|
||||
|
||||
mViewModel.goBack.observe(
|
||||
this,
|
||||
Observer {
|
||||
it.getContentIfNotHandled()?.let {
|
||||
onBackPressed()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
binding.btnSave.setOnClickListener {
|
||||
mViewModel.saveAccount(accountId)
|
||||
val launcher = rememberLauncherForActivityResult(QRScanner()) {
|
||||
it?.let {
|
||||
val totp = TOTPHelper(it)
|
||||
setAccountModel(
|
||||
accountModel.copy(
|
||||
password = totp.secret,
|
||||
title = totp.label,
|
||||
username = totp.issuer
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
if (accountId != null) {
|
||||
menuInflater.inflate(R.menu.menu_delete, menu)
|
||||
}
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
val goBack: () -> Unit = {
|
||||
dispatchAction(GoBackAction)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == R.id.action_delete) {
|
||||
deleteAccount()
|
||||
// Set initial object
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
viewModel.loadOldAccount(id) {
|
||||
setAccountModel(it.copy())
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun deleteAccount() {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(getString(R.string.delete_account_title))
|
||||
.setMessage(getString(R.string.delete_account_msg))
|
||||
.setPositiveButton(
|
||||
getString(R.string.delete)
|
||||
) { dialog, which ->
|
||||
dialog?.dismiss()
|
||||
|
||||
if (accountId != null) {
|
||||
mViewModel.deleteAccount(accountId!!) {
|
||||
onBackPressed()
|
||||
Scaffold(bottomBar = {
|
||||
BottomBar(
|
||||
backPressed = goBack,
|
||||
showDeleteButton = accountModel.uniqueId != null,
|
||||
onDeletePressed = {
|
||||
accountModel.uniqueId?.let {
|
||||
viewModel.deleteAccount(it) {
|
||||
goBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(getString(R.string.cancel)) { dialog, which ->
|
||||
dialog.dismiss()
|
||||
}.show()
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
val result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
|
||||
|
||||
if (result != null) {
|
||||
if (result.contents != null) {
|
||||
try {
|
||||
val totp = TOTPHelper(result.contents)
|
||||
totp.secret?.let {
|
||||
mViewModel.setSecretKey(it)
|
||||
}
|
||||
mViewModel.setAccountName(totp.label)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
) {
|
||||
if (accountModel.password.isNullOrEmpty()) {
|
||||
dispatchAction(ToastAction(R.string.alert_black_secret_key))
|
||||
return@BottomBar
|
||||
}
|
||||
|
||||
if (accountModel.title.isNullOrEmpty()) {
|
||||
dispatchAction(ToastAction(R.string.alert_black_account_name))
|
||||
return@BottomBar
|
||||
}
|
||||
|
||||
viewModel.saveAccount(accountModel, goBack)
|
||||
}
|
||||
}) { paddingValues ->
|
||||
Surface(modifier = Modifier.padding(paddingValues)) {
|
||||
Fields(accountModel = accountModel, updateAccountModel = { newAccountModel ->
|
||||
setAccountModel(newAccountModel)
|
||||
}) {
|
||||
launcher.launch(null)
|
||||
}
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Fields(
|
||||
modifier: Modifier = Modifier,
|
||||
accountModel: AccountModel,
|
||||
updateAccountModel: (newAccountModel: AccountModel) -> Unit,
|
||||
scanClicked: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
if (accountModel.uniqueId == null) {
|
||||
KeyPassInputField(
|
||||
modifier = Modifier.testTag("secretKey"),
|
||||
placeholder = R.string.secret_key,
|
||||
value = accountModel.password,
|
||||
setValue = {
|
||||
updateAccountModel(accountModel.copy(password = it))
|
||||
},
|
||||
trailingIcon = {
|
||||
IconButton(onClick = {
|
||||
scanClicked()
|
||||
}) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_twotone_qr_code_scanner_24),
|
||||
contentDescription = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
KeyPassInputField(
|
||||
modifier = Modifier.testTag("accountName"),
|
||||
placeholder = R.string.account_name,
|
||||
value = accountModel.title,
|
||||
setValue = {
|
||||
updateAccountModel(accountModel.copy(title = it))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BottomBar(
|
||||
backPressed: () -> Unit,
|
||||
showDeleteButton: Boolean,
|
||||
onDeletePressed: () -> Unit,
|
||||
onSaveClicked: () -> Unit
|
||||
) {
|
||||
val (openDialog, setOpenDialog) = remember { mutableStateOf(false) }
|
||||
|
||||
BottomAppBar(actions = {
|
||||
IconButton(onClick = backPressed) {
|
||||
Icon(
|
||||
painter = rememberVectorPainter(image = Icons.Rounded.ArrowBackIosNew),
|
||||
contentDescription = "Go Back",
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
if (showDeleteButton) {
|
||||
IconButton(onClick = {
|
||||
setOpenDialog(true)
|
||||
}) {
|
||||
Icon(
|
||||
painter = rememberVectorPainter(image = Icons.Rounded.Delete),
|
||||
contentDescription = "Delete",
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
}
|
||||
}, floatingActionButton = {
|
||||
FloatingActionButton(modifier = Modifier.testTag("save"), onClick = onSaveClicked) {
|
||||
Icon(
|
||||
painter = rememberVectorPainter(image = Icons.Rounded.Done),
|
||||
contentDescription = "Save Changes"
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
DeleteConfirmation(
|
||||
openDialog,
|
||||
updateDialogVisibility = {
|
||||
setOpenDialog(it)
|
||||
},
|
||||
onDeletePressed
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
package com.yogeshpaliyal.keypass.ui.addTOTP
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.yogeshpaliyal.common.AppDatabase
|
||||
import com.yogeshpaliyal.common.constants.AccountType
|
||||
import com.yogeshpaliyal.common.data.AccountModel
|
||||
import com.yogeshpaliyal.common.utils.Event
|
||||
import com.yogeshpaliyal.keypass.R
|
||||
import com.yogeshpaliyal.common.utils.getRandomString
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -18,64 +15,32 @@ import javax.inject.Inject
|
||||
class AddTOTPViewModel @Inject constructor(private val appDatabase: AppDatabase) :
|
||||
ViewModel() {
|
||||
|
||||
private val _goBack = MutableLiveData<Event<Unit>>()
|
||||
val goBack: LiveData<Event<Unit>> = _goBack
|
||||
|
||||
private val _error = MutableLiveData<Event<Int>>()
|
||||
val error: LiveData<Event<Int>> = _error
|
||||
|
||||
val secretKey = MutableLiveData<String>("")
|
||||
|
||||
val accountName = MutableLiveData<String>("")
|
||||
|
||||
fun loadOldAccount(accountId: String?) {
|
||||
fun loadOldAccount(accountId: String?, loadAccount: (accountModel: AccountModel) -> Unit) {
|
||||
accountId ?: return
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
appDatabase.getDao().getAccount(accountId)?.let { accountModel ->
|
||||
accountName.postValue(accountModel.title ?: "")
|
||||
loadAccount(accountModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun saveAccount(accountId: String?) {
|
||||
fun saveAccount(accountModel: AccountModel, onComplete: () -> Unit) {
|
||||
viewModelScope.launch {
|
||||
val secretKey = secretKey.value
|
||||
val accountName = accountName.value
|
||||
|
||||
if (accountId == null) {
|
||||
if (secretKey.isNullOrEmpty()) {
|
||||
_error.postValue(Event(R.string.alert_black_secret_key))
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
||||
if (accountName.isNullOrEmpty()) {
|
||||
_error.postValue(Event(R.string.alert_black_account_name))
|
||||
return@launch
|
||||
}
|
||||
|
||||
val accountModel = if (accountId == null) {
|
||||
AccountModel(password = secretKey, title = accountName, type = AccountType.TOTP)
|
||||
val accountModelDb = if (accountModel.uniqueId == null) {
|
||||
AccountModel(uniqueId = getRandomString(), password = accountModel.password, title = accountModel.title, type = AccountType.TOTP)
|
||||
} else {
|
||||
appDatabase.getDao().getAccount(accountId)?.also {
|
||||
it.title = accountName
|
||||
appDatabase.getDao().getAccount(accountModel.uniqueId)?.also {
|
||||
it.title = accountModel.title
|
||||
it.password = accountModel.password
|
||||
}
|
||||
}
|
||||
|
||||
accountModel?.let { appDatabase.getDao().insertOrUpdateAccount(it) }
|
||||
_goBack.postValue(Event(Unit))
|
||||
accountModelDb?.let { appDatabase.getDao().insertOrUpdateAccount(it) }
|
||||
onComplete()
|
||||
}
|
||||
}
|
||||
|
||||
fun setSecretKey(secretKey: String) {
|
||||
this.secretKey.value = secretKey
|
||||
}
|
||||
|
||||
fun setAccountName(accountName: String) {
|
||||
this.accountName.value = accountName
|
||||
}
|
||||
|
||||
fun deleteAccount(accountId: String, onDeleted: () -> Unit) {
|
||||
viewModelScope.launch {
|
||||
appDatabase.getDao().deleteAccount(accountId)
|
||||
|
||||
@@ -51,9 +51,9 @@ import com.yogeshpaliyal.common.constants.AccountType
|
||||
import com.yogeshpaliyal.common.data.AccountModel
|
||||
import com.yogeshpaliyal.keypass.R
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.CopyToClipboard
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.IntentNavigation
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.NavigationAction
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.AccountDetailState
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.TotpDetailState
|
||||
import kotlinx.coroutines.delay
|
||||
import org.reduxkotlin.compose.rememberDispatcher
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
@@ -77,7 +77,7 @@ fun AccountsList(accounts: List<AccountModel>? = null) {
|
||||
account,
|
||||
onClick = {
|
||||
if (it.type == AccountType.TOTP) {
|
||||
dispatch(IntentNavigation.AddTOTP(it.uniqueId))
|
||||
dispatch(NavigationAction(TotpDetailState(it.uniqueId)))
|
||||
} else {
|
||||
dispatch(NavigationAction(AccountDetailState(it.id)))
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.TextUnitType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.yogeshpaliyal.keypass.BuildConfig
|
||||
import com.yogeshpaliyal.keypass.ui.addTOTP.TOTPScreen
|
||||
import com.yogeshpaliyal.keypass.ui.auth.AuthScreen
|
||||
import com.yogeshpaliyal.keypass.ui.backup.BackupScreen
|
||||
import com.yogeshpaliyal.keypass.ui.changeDefaultPasswordLength.ChangeDefaultPasswordLengthScreen
|
||||
@@ -171,6 +172,7 @@ fun CurrentPage() {
|
||||
}
|
||||
|
||||
is TotpDetailState -> {
|
||||
TOTPScreen(it.accountId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.yogeshpaliyal.keypass.R
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.IntentNavigation
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.NavigationAction
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.HomeState
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.TotpDetailState
|
||||
|
||||
object NavigationModel {
|
||||
|
||||
@@ -31,7 +32,7 @@ object NavigationModel {
|
||||
icon = R.drawable.ic_twotone_totp,
|
||||
titleRes = R.string.add_totp,
|
||||
checked = false,
|
||||
action = IntentNavigation.AddTOTP()
|
||||
action = NavigationAction(TotpDetailState())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,5 +4,4 @@ sealed interface IntentNavigation : Action {
|
||||
object GeneratePassword : IntentNavigation
|
||||
object BackupActivity : IntentNavigation
|
||||
object ShareApp : IntentNavigation
|
||||
data class AddTOTP(val accountId: String? = null) : IntentNavigation
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.yogeshpaliyal.keypass.ui.redux.middlewares
|
||||
import android.content.Intent
|
||||
import com.yogeshpaliyal.keypass.BuildConfig
|
||||
import com.yogeshpaliyal.keypass.R
|
||||
import com.yogeshpaliyal.keypass.ui.addTOTP.AddTOTPActivity
|
||||
import com.yogeshpaliyal.keypass.ui.generate.GeneratePasswordActivity
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.IntentNavigation
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.KeyPassState
|
||||
@@ -18,10 +17,6 @@ val intentNavigationMiddleware = middleware<KeyPassState> { store, next, action
|
||||
state.context?.startActivity(intent)
|
||||
}
|
||||
|
||||
is IntentNavigation.AddTOTP -> {
|
||||
AddTOTPActivity.start(state.context, action.accountId)
|
||||
}
|
||||
|
||||
is IntentNavigation.BackupActivity -> {
|
||||
// BackupActivity.start(state.context)
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="mViewModel"
|
||||
type="com.yogeshpaliyal.keypass.ui.addTOTP.AddTOTPViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.addTOTP.AddTOTPActivity">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:id="@+id/toolbar"
|
||||
app:navigationIcon="@drawable/ic_baseline_arrow_back_ios_24"/>
|
||||
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:endIconDrawable="@drawable/ic_twotone_qr_code_scanner_24"
|
||||
app:endIconMode="custom"
|
||||
android:id="@+id/tilSecretKey"
|
||||
android:layout_marginTop="@dimen/grid_3"
|
||||
android:layout_marginHorizontal="@dimen/grid_2">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Secret Key"
|
||||
android:text="@={mViewModel.secretKey}"
|
||||
android:id="@+id/etSecretKey"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/tilSecretKey"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:endIconMode="custom"
|
||||
android:id="@+id/tilAccountName"
|
||||
android:layout_marginTop="@dimen/grid_2"
|
||||
android:layout_marginHorizontal="@dimen/grid_2">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/account_name"
|
||||
android:text="@={mViewModel.accountName}"
|
||||
android:id="@+id/etAccount"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_margin="@dimen/grid_2"
|
||||
android:text="@string/save"
|
||||
android:id="@+id/btnSave"
|
||||
app:icon="@drawable/ic_round_done_24"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
||||
@@ -107,5 +107,6 @@
|
||||
<string name="authentication_error">Authentication Error %s</string>
|
||||
<string name="change_password_length">Change password length</string>
|
||||
<string name="default_password_length">Default password length</string>
|
||||
<string name="secret_key">Secret Key</string>
|
||||
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user