mirror of
https://github.com/bitwarden/android.git
synced 2025-12-11 04:39:19 -06:00
Draft - Set cipher values and QR Code update
This commit is contained in:
parent
605e0ef023
commit
6ec84fbd46
@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
@ -32,6 +33,8 @@ import com.x8bit.bitwarden.ui.platform.base.util.cardStyle
|
|||||||
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||||
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
||||||
import com.x8bit.bitwarden.ui.platform.components.dropdown.BitwardenMultiSelectButton
|
import com.x8bit.bitwarden.ui.platform.components.dropdown.BitwardenMultiSelectButton
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
|
||||||
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
|
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
|
||||||
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||||
@ -96,10 +99,25 @@ fun ViewAsQrCodeScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
//TODO debug - remove this
|
||||||
|
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||||
|
BitwardenTextField(
|
||||||
|
label = "Debug - QRCode Content",
|
||||||
|
value = state.qrCodeContent,
|
||||||
|
onValueChange = { },
|
||||||
|
readOnly = true,
|
||||||
|
singleLine = false,
|
||||||
|
textFieldTestTag = "LoginUsernameEntry",
|
||||||
|
cardStyle = CardStyle.Full,
|
||||||
|
modifier = Modifier
|
||||||
|
.testTag("QRCodeType")
|
||||||
|
.standardHorizontalMargin()
|
||||||
|
.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
|
||||||
// QR Code type selector
|
// QR Code type selector
|
||||||
val resources = LocalContext.current.resources
|
val resources = LocalContext.current.resources
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
BitwardenMultiSelectButton(
|
BitwardenMultiSelectButton(
|
||||||
label = stringResource(id = R.string.qr_code_type),
|
label = stringResource(id = R.string.qr_code_type),
|
||||||
options = viewState.qrCodeTypes.map { it.displayName() }.toImmutableList(),
|
options = viewState.qrCodeTypes.map { it.displayName() }.toImmutableList(),
|
||||||
@ -119,7 +137,15 @@ fun ViewAsQrCodeScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
//QR Code Type dropdowns
|
//QR Code Type dropdowns
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||||
|
BitwardenListHeaderText(
|
||||||
|
label = stringResource(id = R.string.data_to_share),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.standardHorizontalMargin()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||||
viewState.qrCodeTypeFields.forEachIndexed { i, field ->
|
viewState.qrCodeTypeFields.forEachIndexed { i, field ->
|
||||||
val cipherFieldsTextList =
|
val cipherFieldsTextList =
|
||||||
viewState.cipherFields.map { it() }.toImmutableList()
|
viewState.cipherFields.map { it() }.toImmutableList()
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import kotlinx.parcelize.Parcelize
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val KEY_STATE = "state"
|
private const val KEY_STATE = "state"
|
||||||
|
private const val DEFAULT_QR_CODE_DATA = "https://bitwarden.com"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ViewModel responsible for handling user interactions in the attachments screen.
|
* ViewModel responsible for handling user interactions in the attachments screen.
|
||||||
@ -45,13 +46,13 @@ class ViewAsQrCodeViewModel @Inject constructor(
|
|||||||
ViewAsQrCodeState(
|
ViewAsQrCodeState(
|
||||||
cipherId = args.vaultItemId,
|
cipherId = args.vaultItemId,
|
||||||
cipherType = args.vaultItemCipherType,
|
cipherType = args.vaultItemCipherType,
|
||||||
qrCodeBitmap = QrCodeGenerator.generateQrCodeBitmap("↑, ↑, ↓, ↓, ←, →, ←, →, B, A,↑, ↑, ↓, ↓, ←, →, ←, →, B, A,↑, ↑, ↓, ↓, ←, →, ←, →, B, A,↑, ↑, ↓, ↓, ←, →, ←, →, B, A,↑, ↑, ↓, ↓, ←, →, ←, →, B, A,"),
|
qrCodeBitmap = QrCodeGenerator.generateQrCodeBitmap(DEFAULT_QR_CODE_DATA),
|
||||||
selectedQrCodeType = selectedQrCodeType,
|
selectedQrCodeType = selectedQrCodeType,
|
||||||
qrCodeTypes = qrCodeTypes,
|
qrCodeTypes = qrCodeTypes,
|
||||||
qrCodeTypeFields = selectedQrCodeType.fields,
|
qrCodeTypeFields = selectedQrCodeType.fields,
|
||||||
cipherFields = emptyList(),
|
cipherFields = emptyList(),
|
||||||
cipher = null,
|
cipher = null,
|
||||||
|
qrCodeContent = DEFAULT_QR_CODE_DATA,
|
||||||
// viewState = ViewAsQrCodeState.ViewState.Loading,
|
// viewState = ViewAsQrCodeState.ViewState.Loading,
|
||||||
// dialogState = null,
|
// dialogState = null,
|
||||||
)
|
)
|
||||||
@ -61,11 +62,11 @@ class ViewAsQrCodeViewModel @Inject constructor(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
//TODO get args.vaultItemCipherType and auto-map
|
//TODO get args.vaultItemCipherType and auto-map
|
||||||
mutableStateFlow.update {
|
// mutableStateFlow.update {
|
||||||
it.copy(
|
// it.copy(
|
||||||
cipherFields = cipherFieldsFor(it.cipherType, null),
|
// cipherFields = cipherFieldsFor(it.cipherType, null),
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
vaultRepository
|
vaultRepository
|
||||||
.getVaultItemStateFlow(args.vaultItemId)
|
.getVaultItemStateFlow(args.vaultItemId)
|
||||||
.map { ViewAsQrCodeAction.Internal.CipherReceive(it) }
|
.map { ViewAsQrCodeAction.Internal.CipherReceive(it) }
|
||||||
@ -118,61 +119,32 @@ class ViewAsQrCodeViewModel @Inject constructor(
|
|||||||
is DataState.Loading -> {}
|
is DataState.Loading -> {}
|
||||||
is DataState.NoNetwork -> {}
|
is DataState.NoNetwork -> {}
|
||||||
is DataState.Pending -> {}
|
is DataState.Pending -> {}
|
||||||
// is DataState.Error -> {
|
|
||||||
// mutableStateFlow.update {
|
|
||||||
// it.copy(
|
|
||||||
// viewState = ViewAsQrCodeState.ViewState.Error(
|
|
||||||
// message = R.string.generic_error_message.asText(),
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// is DataState.Loaded -> {
|
|
||||||
// mutableStateFlow.update {
|
|
||||||
// it.copy(
|
|
||||||
// viewState = dataState
|
|
||||||
// .data
|
|
||||||
// ?.toViewState()
|
|
||||||
// ?: ViewAsQrCodeState.ViewState.Error(
|
|
||||||
// message = R.string.generic_error_message.asText(),
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// DataState.Loading -> {
|
|
||||||
// mutableStateFlow.update {
|
|
||||||
// it.copy(viewState = ViewAsQrCodeState.ViewState.Loading)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
|
|
||||||
// is DataState.Pending -> {
|
|
||||||
// mutableStateFlow.update {
|
|
||||||
// it.copy(
|
|
||||||
// viewState = dataState
|
|
||||||
// .data
|
|
||||||
// ?.toViewState()
|
|
||||||
// ?: ViewAsQrCodeState.ViewState.Error(
|
|
||||||
// message = R.string.generic_error_message.asText(),
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleQrCodeTypeSelect(action: ViewAsQrCodeAction.QrCodeTypeSelect) {
|
private fun handleQrCodeTypeSelect(action: ViewAsQrCodeAction.QrCodeTypeSelect) {
|
||||||
|
|
||||||
|
val updatedFields = autoMapFields(
|
||||||
|
action.qrCodeType.fields,
|
||||||
|
state.cipherType,
|
||||||
|
state.cipher
|
||||||
|
)
|
||||||
|
|
||||||
|
setCipherValues(state.cipher, state.selectedQrCodeType, updatedFields)
|
||||||
|
|
||||||
|
val updatedQrCodeContent = if (validateRequiredFields(updatedFields))
|
||||||
|
QrCodeGenerator.createContentFor(state.selectedQrCodeType, updatedFields)
|
||||||
|
else
|
||||||
|
DEFAULT_QR_CODE_DATA
|
||||||
|
|
||||||
|
val updatedQrCodeBitmap = QrCodeGenerator.generateQrCodeBitmap(updatedQrCodeContent)
|
||||||
|
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
selectedQrCodeType = action.qrCodeType,
|
selectedQrCodeType = action.qrCodeType,
|
||||||
qrCodeTypeFields = autoMapFields(
|
qrCodeTypeFields = updatedFields,
|
||||||
action.qrCodeType.fields,
|
qrCodeBitmap = updatedQrCodeBitmap,
|
||||||
state.cipherType,
|
qrCodeContent = updatedQrCodeContent,
|
||||||
state.cipher
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,7 +167,7 @@ class ViewAsQrCodeViewModel @Inject constructor(
|
|||||||
val selectedText = value.asText()
|
val selectedText = value.asText()
|
||||||
|
|
||||||
//TODO should we transition qrCodeTypeFields to a map again*2 and update using key?
|
//TODO should we transition qrCodeTypeFields to a map again*2 and update using key?
|
||||||
val updatedFields = state.qrCodeTypeFields.map { currentField ->
|
var updatedFields = state.qrCodeTypeFields.map { currentField ->
|
||||||
if (currentField.key == field.key) {
|
if (currentField.key == field.key) {
|
||||||
currentField.copy(value = selectedText)
|
currentField.copy(value = selectedText)
|
||||||
} else {
|
} else {
|
||||||
@ -203,11 +175,56 @@ class ViewAsQrCodeViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatedFields = setCipherValues(state.cipher, state.selectedQrCodeType, updatedFields)
|
||||||
|
|
||||||
|
val updatedQrCodeContent = if (validateRequiredFields(updatedFields))
|
||||||
|
QrCodeGenerator.createContentFor(state.selectedQrCodeType, updatedFields)
|
||||||
|
else
|
||||||
|
DEFAULT_QR_CODE_DATA
|
||||||
|
|
||||||
|
val updatedQrCodeBitmap = QrCodeGenerator.generateQrCodeBitmap(updatedQrCodeContent)
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(qrCodeTypeFields = updatedFields)
|
it.copy(
|
||||||
|
qrCodeTypeFields = updatedFields,
|
||||||
|
qrCodeBitmap = updatedQrCodeBitmap,
|
||||||
|
qrCodeContent = updatedQrCodeContent
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setCipherValues(
|
||||||
|
cipher: CipherView?,
|
||||||
|
qrCodeType: QrCodeType,
|
||||||
|
qrCodeTypeFields: List<QrCodeTypeField>,
|
||||||
|
): List<QrCodeTypeField> {
|
||||||
|
if (cipher == null) {
|
||||||
|
return qrCodeTypeFields
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO figure a way to reuse autoMapField code, by returning (field Text + Cipher value) perhaps
|
||||||
|
return qrCodeTypeFields.map { field ->
|
||||||
|
field.copy(cipherValue = getCipherValueForField(cipher, field))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCipherValueForField(cipher: CipherView, qrCodeField: QrCodeTypeField): String {
|
||||||
|
//TODO refactor - currently failing because asText() creates a different object
|
||||||
|
return when (qrCodeField.value) {
|
||||||
|
R.string.name.asText() -> cipher.name
|
||||||
|
R.string.username.asText() -> cipher.login?.username ?: ""
|
||||||
|
R.string.password.asText() -> cipher.login?.username ?: ""
|
||||||
|
R.string.notes.asText() -> cipher.notes ?: ""
|
||||||
|
"Custom: SSID".asText() -> cipher.fields?.find { it.name == "Custom: SSID" }?.value
|
||||||
|
?: ""
|
||||||
|
|
||||||
|
else -> "TODO()" //TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateRequiredFields(qrCodeTypeFields: List<QrCodeTypeField>): Boolean {
|
||||||
|
return true // TODO
|
||||||
|
}
|
||||||
|
|
||||||
private fun autoMapField(
|
private fun autoMapField(
|
||||||
qrCodeTypeField: QrCodeTypeField,
|
qrCodeTypeField: QrCodeTypeField,
|
||||||
cipherType: VaultItemCipherType,
|
cipherType: VaultItemCipherType,
|
||||||
@ -304,7 +321,8 @@ data class ViewAsQrCodeState(
|
|||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
val cipherFields: List<Text> = emptyList(),
|
val cipherFields: List<Text> = emptyList(),
|
||||||
@IgnoredOnParcel
|
@IgnoredOnParcel
|
||||||
val cipher: CipherView? = null, //TODO do we need to use null?
|
val cipher: CipherView? = null,
|
||||||
|
val qrCodeContent: String = "",
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -104,5 +104,6 @@ data class QrCodeTypeField(
|
|||||||
val key: String,
|
val key: String,
|
||||||
val displayName: Text,
|
val displayName: Text,
|
||||||
val isRequired: Boolean = false,
|
val isRequired: Boolean = false,
|
||||||
var value: Text = "".asText(),
|
val value: Text = "".asText(),
|
||||||
|
val cipherValue: String = "",
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import com.google.zxing.EncodeHintType
|
|||||||
import com.google.zxing.MultiFormatWriter
|
import com.google.zxing.MultiFormatWriter
|
||||||
import com.google.zxing.common.BitMatrix
|
import com.google.zxing.common.BitMatrix
|
||||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||||
|
import com.x8bit.bitwarden.ui.vault.feature.viewasqrcode.model.QrCodeType
|
||||||
|
import com.x8bit.bitwarden.ui.vault.feature.viewasqrcode.model.QrCodeTypeField
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for generating QR codes.
|
* Utility class for generating QR codes.
|
||||||
@ -19,6 +21,104 @@ object QrCodeGenerator {
|
|||||||
|
|
||||||
private const val QR_CODE_SIZE = 512 + 256
|
private const val QR_CODE_SIZE = 512 + 256
|
||||||
private const val UTF_8 = "UTF-8"
|
private const val UTF_8 = "UTF-8"
|
||||||
|
|
||||||
|
fun createContentFor(qrCodeType: QrCodeType, qrCodeFields: List<QrCodeTypeField>): String {
|
||||||
|
return when (qrCodeType) {
|
||||||
|
QrCodeType.WIFI -> createContentForWifi(qrCodeFields)
|
||||||
|
QrCodeType.URL -> createContentForText(qrCodeFields)
|
||||||
|
QrCodeType.PLAIN_TEXT -> createContentForText(qrCodeFields)
|
||||||
|
QrCodeType.EMAIL -> createContentForEmail(qrCodeFields)
|
||||||
|
QrCodeType.PHONE -> createContentForPhone(qrCodeFields)
|
||||||
|
QrCodeType.CONTACT_VCARD -> createContentForContactVcard(qrCodeFields)
|
||||||
|
QrCodeType.CONTACT_MECARD -> createContentForContactMecard(qrCodeFields)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateQrCode(qrCodeType: QrCodeType, qrCodeFields: List<QrCodeTypeField>): Bitmap {
|
||||||
|
val content = createContentFor(qrCodeType, qrCodeFields)
|
||||||
|
return generateQrCodeBitmap(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createContentForContactMecard(qrCodeFields: List<QrCodeTypeField>): String {
|
||||||
|
val firstName = "firstName"
|
||||||
|
val lastName = "lastName"
|
||||||
|
val phone = "phone"
|
||||||
|
val email = "email"
|
||||||
|
val address = "address"
|
||||||
|
|
||||||
|
val addName = lastName.isNotEmpty() || firstName.isNotEmpty()
|
||||||
|
return buildString {
|
||||||
|
append("MECARD:")
|
||||||
|
if (addName) {
|
||||||
|
append("N:")
|
||||||
|
if (lastName.isNotEmpty()) append(lastName)
|
||||||
|
if (firstName.isNotEmpty()) append(",$firstName")
|
||||||
|
append(";")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (phone.isNotEmpty()) append("TEL:$phone;")
|
||||||
|
if (email.isNotEmpty()) append("EMAIL:$email;")
|
||||||
|
if (address.isNotEmpty()) append("ADR:$address;")
|
||||||
|
append(";")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createContentForContactVcard(qrCodeFields: List<QrCodeTypeField>): String {
|
||||||
|
val name = "name"
|
||||||
|
val phone = "phone"
|
||||||
|
val email = "email"
|
||||||
|
val organization = "organization"
|
||||||
|
val address = "address"
|
||||||
|
|
||||||
|
return buildString {
|
||||||
|
append("BEGIN:VCARD\n")
|
||||||
|
append("VERSION:3.0\n")
|
||||||
|
if (name.isNotEmpty()) append("N:$name\n")
|
||||||
|
if (name.isNotEmpty()) append("FN:$name\n")
|
||||||
|
if (organization.isNotEmpty()) append("ORG:$organization\n")
|
||||||
|
if (phone.isNotEmpty()) append("TEL:$phone\n")
|
||||||
|
if (email.isNotEmpty()) append("EMAIL:$email\n")
|
||||||
|
if (address.isNotEmpty()) append("ADR:;;$address\n")
|
||||||
|
append("END:VCARD")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createContentForWifi(qrCodeFields: List<QrCodeTypeField>): String {
|
||||||
|
var ssid = String()
|
||||||
|
var password = String()
|
||||||
|
var additionalOptions = String()
|
||||||
|
|
||||||
|
qrCodeFields.forEach {
|
||||||
|
when (it.key) {
|
||||||
|
"ssid" -> ssid = it.cipherValue
|
||||||
|
"password" -> password = it.cipherValue
|
||||||
|
"additionalOptions" -> additionalOptions = it.cipherValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildString {
|
||||||
|
append("WIFI:")
|
||||||
|
if (password.isNotEmpty() && !additionalOptions.contains("T:")) append("T:WPA;")
|
||||||
|
append("ssid:$ssid;")
|
||||||
|
if (password.isNotEmpty()) append("P:$password;")
|
||||||
|
if (additionalOptions.isNotEmpty()) append(additionalOptions)
|
||||||
|
append(";")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createContentForText(qrCodeFields: List<QrCodeTypeField>): String {
|
||||||
|
return qrCodeFields.firstOrNull()?.cipherValue ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createContentForEmail(qrCodeFields: List<QrCodeTypeField>): String {
|
||||||
|
val value = qrCodeFields.firstOrNull()?.cipherValue ?: ""
|
||||||
|
return "email:$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createContentForPhone(qrCodeFields: List<QrCodeTypeField>): String {
|
||||||
|
val value = qrCodeFields.firstOrNull()?.cipherValue ?: ""
|
||||||
|
return "phone:$value"
|
||||||
|
}
|
||||||
//
|
//
|
||||||
// /**
|
// /**
|
||||||
// * Generate a QR code bitmap from the given configuration.
|
// * Generate a QR code bitmap from the given configuration.
|
||||||
@ -140,7 +240,7 @@ object QrCodeGenerator {
|
|||||||
val contentColor = "#165DDC".toColorInt() // bitwarden blue
|
val contentColor = "#165DDC".toColorInt() // bitwarden blue
|
||||||
val finderPatternColor = "#030E65".toColorInt() // dark blue
|
val finderPatternColor = "#030E65".toColorInt() // dark blue
|
||||||
val backgroundColor = Color.WHITE
|
val backgroundColor = Color.WHITE
|
||||||
|
|
||||||
for (x in 0 until width) {
|
for (x in 0 until width) {
|
||||||
for (y in 0 until height) {
|
for (y in 0 until height) {
|
||||||
val bit = bitMatrix[x, y]
|
val bit = bitMatrix[x, y]
|
||||||
|
|||||||
@ -1231,6 +1231,7 @@ Do you want to switch to this account?</string>
|
|||||||
<string name="share_error_details">Share error details</string>
|
<string name="share_error_details">Share error details</string>
|
||||||
|
|
||||||
<string name="view_as_qr_code">View as QR code</string>
|
<string name="view_as_qr_code">View as QR code</string>
|
||||||
|
<string name="data_to_share">Data to share</string>
|
||||||
<string name="qr_code">QR code</string>
|
<string name="qr_code">QR code</string>
|
||||||
<string name="qr_code_type">QR code type</string>
|
<string name="qr_code_type">QR code type</string>
|
||||||
<string name="url">URL</string>
|
<string name="url">URL</string>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user