Merge branch 'main' into dain/restricted-nfc

This commit is contained in:
Adam Velebil 2024-08-22 09:28:06 +02:00
commit bba92b8b54
No known key found for this signature in database
GPG Key ID: C9B1E4A3CBBD2E10
79 changed files with 3008 additions and 1209 deletions

View File

@ -7,23 +7,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Clone yubikit-android
uses: actions/checkout@v4
with:
repository: Yubico/yubikit-android
ref: dain/scp
path: kit
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Build yubikit-android
run: ./gradlew --stacktrace build publishToMavenLocal
working-directory: ./kit
- uses: actions/checkout@v4
with:
path: 'app'

View File

@ -33,23 +33,12 @@ jobs:
languages: ${{ matrix.language }}
setup-python-dependencies: false
- name: Clone yubikit-android
uses: actions/checkout@v4
with:
repository: Yubico/yubikit-android
ref: dain/scp
path: kit
- name: set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Build yubikit-android
run: ./gradlew --stacktrace build publishToMavenLocal
working-directory: ./kit
- uses: actions/checkout@v4
with:
path: 'app'

View File

@ -1,2 +1,2 @@
FLUTTER=3.22.2
PYVER=3.12.4
FLUTTER=3.24.0
PYVER=3.12.5

View File

@ -52,11 +52,17 @@ jobs:
- uses: subosito/flutter-action@v2
with:
channel: 'stable'
architecture: 'x64'
flutter-version: ${{ env.FLUTTER }}
- run: flutter config --enable-macos-desktop
- run: flutter --version
- name: Apply Flutter Patch
run: |
cd $FLUTTER_ROOT
git apply $GITHUB_WORKSPACE/macos_assemble.patch
env:
GITHUB_WORKSPACE: ${{ github.workspace }}
- name: Run lints/tests
env:
SKIP: ${{ steps.cache-helper.outputs.cache-hit == 'true' && 'mypy,flake8,black,bandit' || ''}}

View File

@ -26,7 +26,8 @@ enum class FidoActionDescription(private val value: Int) {
DeleteFingerprint(4),
RenameFingerprint(5),
RegisterFingerprint(6),
ActionFailure(7);
EnableEnterpriseAttestation(7),
ActionFailure(8);
val id: Int
get() = value + dialogDescriptionFidoIndex

View File

@ -42,6 +42,7 @@ import com.yubico.yubikit.core.smartcard.SmartCardConnection
import com.yubico.yubikit.core.util.Result
import com.yubico.yubikit.fido.ctap.BioEnrollment
import com.yubico.yubikit.fido.ctap.ClientPin
import com.yubico.yubikit.fido.ctap.Config
import com.yubico.yubikit.fido.ctap.CredentialManagement
import com.yubico.yubikit.fido.ctap.Ctap2Session.InfoData
import com.yubico.yubikit.fido.ctap.FingerprintBioEnrollment
@ -159,6 +160,8 @@ class FidoManager(
"cancelRegisterFingerprint" -> cancelRegisterFingerprint()
"enableEnterpriseAttestation" -> enableEnterpriseAttestation()
else -> throw NotImplementedError()
}
}
@ -603,6 +606,42 @@ class FidoManager(
).toString()
}
private suspend fun enableEnterpriseAttestation(): String =
connectionHelper.useSession(FidoActionDescription.EnableEnterpriseAttestation) { fidoSession ->
try {
val uvAuthProtocol = getPreferredPinUvAuthProtocol(fidoSession.cachedInfo)
val clientPin = ClientPin(fidoSession, uvAuthProtocol)
val token = if (pinStore.hasPin()) {
clientPin.getPinToken(
pinStore.getPin(),
ClientPin.PIN_PERMISSION_ACFG,
null
)
} else null
val config = Config(fidoSession, uvAuthProtocol, token)
config.enableEnterpriseAttestation()
fidoViewModel.setSessionState(
Session(
fidoSession.info,
pinStore.hasPin(),
pinRetries
)
)
return@useSession JSONObject(
mapOf(
"success" to true,
)
).toString()
} catch (e: Exception) {
logger.error("Failed to enable enterprise attestation. ", e)
return@useSession JSONObject(
mapOf(
"success" to false,
)
).toString()
}
}
override fun onDisconnected() {
if (!resetHelper.inProgress) {

View File

@ -29,7 +29,8 @@ data class Options(
val credMgmt: Boolean,
val credentialMgmtPreview: Boolean,
val bioEnroll: Boolean?,
val alwaysUv: Boolean
val alwaysUv: Boolean,
val ep: Boolean?,
) {
constructor(infoData: InfoData) : this(
infoData.getOptionsBoolean("clientPin") == true,
@ -37,6 +38,7 @@ data class Options(
infoData.getOptionsBoolean("credentialMgmtPreview") == true,
infoData.getOptionsBoolean("bioEnroll"),
infoData.getOptionsBoolean("alwaysUv") == true,
infoData.getOptionsBoolean("ep"),
)
companion object {

View File

@ -2,7 +2,6 @@ allprojects {
repositories {
google()
mavenCentral()
mavenLocal()
}
project.ext {
@ -10,7 +9,7 @@ allprojects {
targetSdkVersion = 34
compileSdkVersion = 34
yubiKitVersion = "2.6.1-SNAPSHOT"
yubiKitVersion = "2.7.0-alpha01"
junitVersion = "4.13.2"
mockitoVersion = "5.12.0"
}

View File

@ -543,16 +543,17 @@ class ScpConnectionNode(ConnectionNode):
self.fips_capable = info.fips_capable
self.scp_params = None
try:
scp = SecurityDomainSession(connection)
if self.fips_capable != 0:
scp = SecurityDomainSession(connection)
for ref in scp.get_key_information().keys():
if ref.kid == 0x13:
chain = scp.get_certificate_bundle(ref)
if chain:
pub_key = chain[-1].public_key()
assert isinstance(pub_key, EllipticCurvePublicKey) # nosec
self.scp_params = Scp11KeyParams(ref, pub_key)
break
for ref in scp.get_key_information().keys():
if ref.kid == 0x13:
chain = scp.get_certificate_bundle(ref)
if chain:
pub_key = chain[-1].public_key()
assert isinstance(pub_key, EllipticCurvePublicKey) # nosec
self.scp_params = Scp11KeyParams(ref, pub_key)
break
except NotSupportedError:
pass

View File

@ -23,7 +23,7 @@ from .base import (
PinComplexityException,
)
from fido2.ctap import CtapError
from fido2.ctap2 import Ctap2, ClientPin
from fido2.ctap2 import Ctap2, ClientPin, Config
from fido2.ctap2.credman import CredentialManagement
from fido2.ctap2.bio import BioEnrollment, FPBioEnrollment, CaptureError
from fido2.pcsc import CtapPcscDevice
@ -200,6 +200,8 @@ class Ctap2Node(RpcNode):
permissions |= ClientPin.PERMISSION.CREDENTIAL_MGMT
if BioEnrollment.is_supported(self._info):
permissions |= ClientPin.PERMISSION.BIO_ENROLL
if Config.is_supported(self._info):
permissions |= ClientPin.PERMISSION.AUTHENTICATOR_CFG
try:
if permissions:
self._token = self.client_pin.get_pin_token(pin, permissions)
@ -229,6 +231,14 @@ class Ctap2Node(RpcNode):
except CtapError as e:
return _handle_pin_error(e, self.client_pin)
@action(condition=lambda self: Config.is_supported(self._info))
def enable_ep_attestation(self, params, event, signal):
if self._info.options["clientPin"] and not self._token:
raise AuthRequiredException()
config = Config(self.ctap, self.client_pin.protocol, self._token)
config._call(Config.CMD.ENABLE_ENTERPRISE_ATT)
return dict()
@child(condition=lambda self: BioEnrollment.is_supported(self._info))
def fingerprints(self):
if not self._token:
@ -391,8 +401,10 @@ class FingerprintNode(RpcNode):
self.bio.set_name(self.template_id, name)
self.name = name
self.refresh()
return dict()
@action
def delete(self, params, event, signal):
self.bio.remove_enrollment(self.template_id)
self.refresh()
return dict()

View File

@ -135,11 +135,18 @@ class PivNode(RpcNode):
pin_attempts = self.session.get_pin_attempts()
metadata = None
try:
self.session.get_bio_metadata()
supports_bio = True
except NotSupportedError:
supports_bio = False
return dict(
version=self.session.version,
authenticated=self._authenticated,
derived_key=self._pivman_data.has_derived_key,
stored_key=self._pivman_data.has_stored_key,
supports_bio=supports_bio,
chuid=self._get_object(OBJECT_ID.CHUID),
ccc=self._get_object(OBJECT_ID.CAPABILITY),
pin_attempts=pin_attempts,

359
helper/poetry.lock generated
View File

@ -28,63 +28,78 @@ testing = ["jaraco.test", "pytest (!=8.0.*)", "pytest (>=6,!=8.1.*)", "pytest-ch
[[package]]
name = "cffi"
version = "1.16.0"
version = "1.17.0"
description = "Foreign Function Interface for Python calling C code."
optional = false
python-versions = ">=3.8"
files = [
{file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"},
{file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"},
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"},
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"},
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"},
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"},
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"},
{file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"},
{file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"},
{file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"},
{file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"},
{file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"},
{file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"},
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"},
{file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"},
{file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"},
{file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"},
{file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"},
{file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"},
{file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"},
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"},
{file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"},
{file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"},
{file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"},
{file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"},
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"},
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"},
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"},
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"},
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"},
{file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"},
{file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"},
{file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"},
{file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"},
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"},
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"},
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"},
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"},
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"},
{file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"},
{file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"},
{file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"},
{file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"},
{file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"},
{file = "cffi-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb"},
{file = "cffi-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a"},
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42"},
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d"},
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2"},
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab"},
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b"},
{file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206"},
{file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa"},
{file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f"},
{file = "cffi-1.17.0-cp310-cp310-win32.whl", hash = "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc"},
{file = "cffi-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2"},
{file = "cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720"},
{file = "cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9"},
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb"},
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424"},
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d"},
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8"},
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6"},
{file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91"},
{file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8"},
{file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb"},
{file = "cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9"},
{file = "cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0"},
{file = "cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc"},
{file = "cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59"},
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb"},
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195"},
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e"},
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828"},
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150"},
{file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a"},
{file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885"},
{file = "cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492"},
{file = "cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2"},
{file = "cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118"},
{file = "cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7"},
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377"},
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb"},
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555"},
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204"},
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f"},
{file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0"},
{file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4"},
{file = "cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a"},
{file = "cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7"},
{file = "cffi-1.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c"},
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e"},
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b"},
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e"},
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401"},
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c"},
{file = "cffi-1.17.0-cp38-cp38-win32.whl", hash = "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499"},
{file = "cffi-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c"},
{file = "cffi-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2"},
{file = "cffi-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759"},
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4"},
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82"},
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf"},
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058"},
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932"},
{file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693"},
{file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3"},
{file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4"},
{file = "cffi-1.17.0-cp39-cp39-win32.whl", hash = "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb"},
{file = "cffi-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29"},
{file = "cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76"},
]
[package.dependencies]
@ -117,43 +132,38 @@ files = [
[[package]]
name = "cryptography"
version = "42.0.8"
version = "43.0.0"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = false
python-versions = ">=3.7"
files = [
{file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"},
{file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"},
{file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"},
{file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"},
{file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"},
{file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"},
{file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"},
{file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"},
{file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"},
{file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"},
{file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"},
{file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"},
{file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"},
{file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"},
{file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"},
{file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"},
{file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"},
{file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"},
{file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"},
{file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"},
{file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"},
{file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"},
{file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"},
{file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"},
{file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"},
{file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"},
{file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"},
{file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"},
{file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"},
{file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"},
{file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"},
{file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"},
{file = "cryptography-43.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74"},
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895"},
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22"},
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47"},
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf"},
{file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55"},
{file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431"},
{file = "cryptography-43.0.0-cp37-abi3-win32.whl", hash = "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc"},
{file = "cryptography-43.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778"},
{file = "cryptography-43.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66"},
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5"},
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e"},
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5"},
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f"},
{file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0"},
{file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b"},
{file = "cryptography-43.0.0-cp39-abi3-win32.whl", hash = "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf"},
{file = "cryptography-43.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709"},
{file = "cryptography-43.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70"},
{file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66"},
{file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f"},
{file = "cryptography-43.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f"},
{file = "cryptography-43.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2"},
{file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947"},
{file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069"},
{file = "cryptography-43.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1"},
{file = "cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e"},
]
[package.dependencies]
@ -166,18 +176,18 @@ nox = ["nox"]
pep8test = ["check-sdist", "click", "mypy", "ruff"]
sdist = ["build"]
ssh = ["bcrypt (>=3.1.5)"]
test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
test = ["certifi", "cryptography-vectors (==43.0.0)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
test-randomorder = ["pytest-randomly"]
[[package]]
name = "exceptiongroup"
version = "1.2.1"
version = "1.2.2"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
files = [
{file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
{file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"},
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
]
[package.extras]
@ -202,13 +212,13 @@ pcsc = ["pyscard (>=1.9,<3)"]
[[package]]
name = "importlib-metadata"
version = "8.0.0"
version = "8.2.0"
description = "Read metadata from Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "importlib_metadata-8.0.0-py3-none-any.whl", hash = "sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f"},
{file = "importlib_metadata-8.0.0.tar.gz", hash = "sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812"},
{file = "importlib_metadata-8.2.0-py3-none-any.whl", hash = "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369"},
{file = "importlib_metadata-8.2.0.tar.gz", hash = "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d"},
]
[package.dependencies]
@ -221,21 +231,21 @@ test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "p
[[package]]
name = "importlib-resources"
version = "6.4.0"
version = "6.4.2"
description = "Read resources from Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "importlib_resources-6.4.0-py3-none-any.whl", hash = "sha256:50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c"},
{file = "importlib_resources-6.4.0.tar.gz", hash = "sha256:cdb2b453b8046ca4e3798eb1d84f3cce1446a0e8e7b5ef4efb600f19fc398145"},
{file = "importlib_resources-6.4.2-py3-none-any.whl", hash = "sha256:8bba8c54a8a3afaa1419910845fa26ebd706dc716dd208d9b158b4b6966f5c5c"},
{file = "importlib_resources-6.4.2.tar.gz", hash = "sha256:6cbfbefc449cc6e2095dd184691b7a12a04f40bc75dd4c55d31c34f174cdf57a"},
]
[package.dependencies]
zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"]
[[package]]
name = "iniconfig"
@ -286,21 +296,21 @@ testing = ["portend", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytes
[[package]]
name = "jaraco-functools"
version = "4.0.1"
version = "4.0.2"
description = "Functools like those found in stdlib"
optional = false
python-versions = ">=3.8"
files = [
{file = "jaraco.functools-4.0.1-py3-none-any.whl", hash = "sha256:3b24ccb921d6b593bdceb56ce14799204f473976e2a9d4b15b04d0f2c2326664"},
{file = "jaraco_functools-4.0.1.tar.gz", hash = "sha256:d33fa765374c0611b52f8b3a795f8900869aa88c84769d4d1746cd68fb28c3e8"},
{file = "jaraco.functools-4.0.2-py3-none-any.whl", hash = "sha256:c9d16a3ed4ccb5a889ad8e0b7a343401ee5b2a71cee6ed192d3f68bc351e94e3"},
{file = "jaraco_functools-4.0.2.tar.gz", hash = "sha256:3460c74cd0d32bf82b9576bbb3527c4364d5b27a21f5158a62aed6c4b42e23f5"},
]
[package.dependencies]
more-itertools = "*"
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
testing = ["jaraco.classes", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
test = ["jaraco.classes", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
[[package]]
name = "jeepney"
@ -319,13 +329,13 @@ trio = ["async_generator", "trio"]
[[package]]
name = "keyring"
version = "25.2.1"
version = "25.3.0"
description = "Store and access your passwords safely."
optional = false
python-versions = ">=3.8"
files = [
{file = "keyring-25.2.1-py3-none-any.whl", hash = "sha256:2458681cdefc0dbc0b7eb6cf75d0b98e59f9ad9b2d4edd319d18f68bdca95e50"},
{file = "keyring-25.2.1.tar.gz", hash = "sha256:daaffd42dbda25ddafb1ad5fec4024e5bbcfe424597ca1ca452b299861e49f1b"},
{file = "keyring-25.3.0-py3-none-any.whl", hash = "sha256:8d963da00ccdf06e356acd9bf3b743208878751032d8599c6cc89eb51310ffae"},
{file = "keyring-25.3.0.tar.gz", hash = "sha256:8d85a1ea5d6db8515b59e1c5d1d1678b03cf7fc8b8dcfb1651e8c4a524eb42ef"},
]
[package.dependencies]
@ -340,8 +350,8 @@ SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""}
[package.extras]
completion = ["shtab (>=1.1.0)"]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
testing = ["pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
test = ["pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
[[package]]
name = "macholib"
@ -359,13 +369,13 @@ altgraph = ">=0.17"
[[package]]
name = "more-itertools"
version = "10.3.0"
version = "10.4.0"
description = "More routines for operating on iterables, beyond itertools"
optional = false
python-versions = ">=3.8"
files = [
{file = "more-itertools-10.3.0.tar.gz", hash = "sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463"},
{file = "more_itertools-10.3.0-py3-none-any.whl", hash = "sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320"},
{file = "more-itertools-10.4.0.tar.gz", hash = "sha256:fe0e63c4ab068eac62410ab05cccca2dc71ec44ba8ef29916a0090df061cf923"},
{file = "more_itertools-10.4.0-py3-none-any.whl", hash = "sha256:0f7d9f83a0a8dcfa8a2694a770590d98a67ea943e3d9f5298309a484758c4e27"},
]
[[package]]
@ -381,44 +391,44 @@ files = [
[[package]]
name = "mypy"
version = "1.10.1"
version = "1.11.1"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "mypy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02"},
{file = "mypy-1.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7"},
{file = "mypy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a"},
{file = "mypy-1.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9"},
{file = "mypy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d"},
{file = "mypy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a"},
{file = "mypy-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84"},
{file = "mypy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f"},
{file = "mypy-1.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b"},
{file = "mypy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e"},
{file = "mypy-1.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7"},
{file = "mypy-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3"},
{file = "mypy-1.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e"},
{file = "mypy-1.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04"},
{file = "mypy-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31"},
{file = "mypy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c"},
{file = "mypy-1.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade"},
{file = "mypy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37"},
{file = "mypy-1.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7"},
{file = "mypy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d"},
{file = "mypy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3"},
{file = "mypy-1.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf"},
{file = "mypy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531"},
{file = "mypy-1.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3"},
{file = "mypy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f"},
{file = "mypy-1.10.1-py3-none-any.whl", hash = "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a"},
{file = "mypy-1.10.1.tar.gz", hash = "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0"},
{file = "mypy-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c"},
{file = "mypy-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411"},
{file = "mypy-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03"},
{file = "mypy-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4"},
{file = "mypy-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58"},
{file = "mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5"},
{file = "mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca"},
{file = "mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de"},
{file = "mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809"},
{file = "mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72"},
{file = "mypy-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8"},
{file = "mypy-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a"},
{file = "mypy-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417"},
{file = "mypy-1.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e"},
{file = "mypy-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525"},
{file = "mypy-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2"},
{file = "mypy-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b"},
{file = "mypy-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0"},
{file = "mypy-1.11.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd"},
{file = "mypy-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb"},
{file = "mypy-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe"},
{file = "mypy-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c"},
{file = "mypy-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69"},
{file = "mypy-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74"},
{file = "mypy-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b"},
{file = "mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54"},
{file = "mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08"},
]
[package.dependencies]
mypy-extensions = ">=1.0.0"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing-extensions = ">=4.1.0"
typing-extensions = ">=4.6.0"
[package.extras]
dmypy = ["psutil (>=4.0)"]
@ -584,23 +594,23 @@ files = [
[[package]]
name = "pyinstaller"
version = "6.8.0"
version = "6.10.0"
description = "PyInstaller bundles a Python application and all its dependencies into a single package."
optional = false
python-versions = "<3.13,>=3.8"
python-versions = "<3.14,>=3.8"
files = [
{file = "pyinstaller-6.8.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:5ff6bc2784c1026f8e2f04aa3760cbed41408e108a9d4cf1dd52ee8351a3f6e1"},
{file = "pyinstaller-6.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:39ac424d2ee2457d2ab11a5091436e75a0cccae207d460d180aa1fcbbafdd528"},
{file = "pyinstaller-6.8.0-py3-none-manylinux2014_i686.whl", hash = "sha256:355832a3acc7de90a255ecacd4b9f9e166a547a79c8905d49f14e3a75c1acdb9"},
{file = "pyinstaller-6.8.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:6303c7a009f47e6a96ef65aed49f41e36ece8d079b9193ca92fe807403e5fe80"},
{file = "pyinstaller-6.8.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2b71509468c811968c0b5decb5bbe85b6292ea52d7b1f26313d2aabb673fa9a5"},
{file = "pyinstaller-6.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ff31c5b99e05a4384bbe2071df67ec8b2b347640a375eae9b40218be2f1754c6"},
{file = "pyinstaller-6.8.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:000c36b13fe4cd8d0d8c2bc855b1ddcf39867b5adf389e6b5ca45b25fa3e619d"},
{file = "pyinstaller-6.8.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:fe0af018d7d5077180e3144ada89a4da5df8d07716eb7e9482834a56dc57a4e8"},
{file = "pyinstaller-6.8.0-py3-none-win32.whl", hash = "sha256:d257f6645c7334cbd66f38a4fac62c3ad614cc46302b2b5d9f8cc48c563bce0e"},
{file = "pyinstaller-6.8.0-py3-none-win_amd64.whl", hash = "sha256:81cccfa9b16699b457f4788c5cc119b50f3cd4d0db924955f15c33f2ad27a50d"},
{file = "pyinstaller-6.8.0-py3-none-win_arm64.whl", hash = "sha256:1c3060a263758cf7f0144ab4c016097b20451b2469d468763414665db1bb743d"},
{file = "pyinstaller-6.8.0.tar.gz", hash = "sha256:3f4b6520f4423fe19bcc2fd63ab7238851ae2bdcbc98f25bc5d2f97cc62012e9"},
{file = "pyinstaller-6.10.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:d60fb22859e11483af735aec115fdde09467cdbb29edd9844839f2c920b748c0"},
{file = "pyinstaller-6.10.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:46d75359668993ddd98630a3669dc5249f3c446e35239b43bc7f4155bc574748"},
{file = "pyinstaller-6.10.0-py3-none-manylinux2014_i686.whl", hash = "sha256:3398a98fa17d47ccb31f8779ecbdacec025f7adb2f22757a54b706ac8b4fe906"},
{file = "pyinstaller-6.10.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e9989f354ae4ed8a3bec7bdb37ae0d170751d6520e500f049c7cd0632d31d5c3"},
{file = "pyinstaller-6.10.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:b7c90c91921b3749083115b28f30f40abf2bb481ceff196d2b2ce0eaa2b3d429"},
{file = "pyinstaller-6.10.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6cf876d7d93b8b4f28d1ad57fa24645cf43119c79e985dd5e5f7a801245e6f53"},
{file = "pyinstaller-6.10.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:db05e3f2f10f9f78c56f1fb163d9cb453433429fe4281218ebaf1ebfd39ba942"},
{file = "pyinstaller-6.10.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:28eca3817f176fdc19747e1afcf434f13bb9f17a644f611be2c5a61b1f498ed7"},
{file = "pyinstaller-6.10.0-py3-none-win32.whl", hash = "sha256:703e041718987e46ba0568a2c71ecf2459fddef57cf9edf3efeed4a53e3dae3f"},
{file = "pyinstaller-6.10.0-py3-none-win_amd64.whl", hash = "sha256:95b55966e563e8b8f31a43882aea10169e9a11fdf38e626d86a2907b640c0701"},
{file = "pyinstaller-6.10.0-py3-none-win_arm64.whl", hash = "sha256:308e0a8670c9c9ac0cebbf1bbb492e71b6675606f2ec78bc4adfc830d209e087"},
{file = "pyinstaller-6.10.0.tar.gz", hash = "sha256:143840f8056ff7b910bf8f16f6cd92cc10a6c2680bb76d0a25d558d543d21270"},
]
[package.dependencies]
@ -609,7 +619,7 @@ importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""}
macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""}
packaging = ">=22.0"
pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""}
pyinstaller-hooks-contrib = ">=2024.6"
pyinstaller-hooks-contrib = ">=2024.8"
pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""}
setuptools = ">=42.0.0"
@ -619,13 +629,13 @@ hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"]
[[package]]
name = "pyinstaller-hooks-contrib"
version = "2024.7"
version = "2024.8"
description = "Community maintained hooks for PyInstaller"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "pyinstaller_hooks_contrib-2024.7-py2.py3-none-any.whl", hash = "sha256:8bf0775771fbaf96bcd2f4dfd6f7ae6c1dd1b1efe254c7e50477b3c08e7841d8"},
{file = "pyinstaller_hooks_contrib-2024.7.tar.gz", hash = "sha256:fd5f37dcf99bece184e40642af88be16a9b89613ecb958a8bd1136634fc9fac5"},
{file = "pyinstaller_hooks_contrib-2024.8-py3-none-any.whl", hash = "sha256:0057fe9a5c398d3f580e73e58793a1d4a8315ca91c3df01efea1c14ed557825a"},
{file = "pyinstaller_hooks_contrib-2024.8.tar.gz", hash = "sha256:29b68d878ab739e967055b56a93eb9b58e529d5b054fbab7a2f2bacf80cef3e2"},
]
[package.dependencies]
@ -662,13 +672,13 @@ pyro = ["Pyro"]
[[package]]
name = "pytest"
version = "8.2.2"
version = "8.3.2"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"},
{file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"},
{file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"},
{file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"},
]
[package.dependencies]
@ -676,7 +686,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=1.5,<2.0"
pluggy = ">=1.5,<2"
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras]
@ -707,13 +717,13 @@ files = [
[[package]]
name = "pywin32-ctypes"
version = "0.2.2"
version = "0.2.3"
description = "A (partial) reimplementation of pywin32 using ctypes/cffi"
optional = false
python-versions = ">=3.6"
files = [
{file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"},
{file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"},
{file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"},
{file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"},
]
[[package]]
@ -733,18 +743,19 @@ jeepney = ">=0.6"
[[package]]
name = "setuptools"
version = "70.2.0"
version = "72.2.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "setuptools-70.2.0-py3-none-any.whl", hash = "sha256:b8b8060bb426838fbe942479c90296ce976249451118ef566a5a0b7d8b78fb05"},
{file = "setuptools-70.2.0.tar.gz", hash = "sha256:bd63e505105011b25c3c11f753f7e3b8465ea739efddaccef8f0efac2137bac1"},
{file = "setuptools-72.2.0-py3-none-any.whl", hash = "sha256:f11dd94b7bae3a156a95ec151f24e4637fb4fa19c878e4d191bfb8b2d82728c4"},
{file = "setuptools-72.2.0.tar.gz", hash = "sha256:80aacbf633704e9c8bfa1d99fa5dd4dc59573efcf9e4042c13d3bcef91ac2ef9"},
]
[package.extras]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
name = "tomli"
@ -800,13 +811,13 @@ pywin32 = {version = ">=223", markers = "sys_platform == \"win32\""}
[[package]]
name = "zipp"
version = "3.19.2"
version = "3.20.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
optional = false
python-versions = ">=3.8"
files = [
{file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"},
{file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"},
{file = "zipp-3.20.0-py3-none-any.whl", hash = "sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d"},
{file = "zipp-3.20.0.tar.gz", hash = "sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31"},
]
[package.extras]

View File

@ -94,9 +94,10 @@ class RpcShell(cmd.Cmd):
cmd = target.pop() if target else ""
node = self.get_node(target)
if node:
names = [n + "/" for n in node.get("children", [])]
body = node.get("body", {})
names = [n + "/" for n in body.get("children", [])]
if not nodes_only:
actions = node.get("actions", [])
actions = body.get("actions", [])
if "get" in actions:
actions.remove("get")
names += actions
@ -104,10 +105,10 @@ class RpcShell(cmd.Cmd):
return res
return []
def completedefault(self, cmd, text, *args):
def completedefault(self, cmd, text, *args): # type: ignore
return self.completepath(text)
def completenames(self, cmd, text, *ignored):
def completenames(self, cmd, text, *ignored): # type: ignore
return self.completepath(text)
def emptyline(self):

View File

@ -161,6 +161,29 @@ class _FidoStateNotifier extends FidoStateNotifier {
throw decodedException;
}
}
@override
Future<void> enableEnterpriseAttestation() async {
try {
final response = jsonDecode(await _methods.invokeMethod(
'enableEnterpriseAttestation',
));
if (response['success'] == true) {
_log.debug('Enterprise attestation enabled');
}
} on PlatformException catch (pe) {
var decodedException = pe.decode();
if (decodedException is CancellationException) {
_log.debug('User cancelled unlock FIDO operation');
throw decodedException;
}
_log.debug(
'Platform exception during enable enterprise attestation: $pe');
rethrow;
}
}
}
final androidFingerprintProvider = AsyncNotifierProvider.autoDispose

View File

@ -80,6 +80,8 @@ enum _DDesc {
fidoDeleteCredential,
fidoDeleteFingerprint,
fidoRenameFingerprint,
fidoRegisterFingerprint,
fidoEnableEnterpriseAttestation,
fidoActionFailure,
// Others
invalid;
@ -105,7 +107,9 @@ enum _DDesc {
dialogDescriptionFidoIndex + 3: fidoDeleteCredential,
dialogDescriptionFidoIndex + 4: fidoDeleteFingerprint,
dialogDescriptionFidoIndex + 5: fidoRenameFingerprint,
dialogDescriptionFidoIndex + 6: fidoActionFailure,
dialogDescriptionFidoIndex + 6: fidoRegisterFingerprint,
dialogDescriptionFidoIndex + 7: fidoEnableEnterpriseAttestation,
dialogDescriptionFidoIndex + 8: fidoActionFailure,
}[id] ??
_DDesc.invalid;
}

View File

@ -32,7 +32,7 @@ Future<T?> showBlurDialog<T>({
required BuildContext context,
required Widget Function(BuildContext) builder,
RouteSettings? routeSettings,
Color barrierColor = const Color(0x00cccccc),
Color barrierColor = const Color(0x33000000),
}) async =>
await showGeneralDialog<T>(
context: context,

View File

@ -20,7 +20,9 @@ mixin _$YubiKeyData {
String get name => throw _privateConstructorUsedError;
DeviceInfo get info => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of YubiKeyData
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$YubiKeyDataCopyWith<YubiKeyData> get copyWith =>
throw _privateConstructorUsedError;
}
@ -47,6 +49,8 @@ class _$YubiKeyDataCopyWithImpl<$Res, $Val extends YubiKeyData>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of YubiKeyData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -70,6 +74,8 @@ class _$YubiKeyDataCopyWithImpl<$Res, $Val extends YubiKeyData>
) as $Val);
}
/// Create a copy of YubiKeyData
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$DeviceNodeCopyWith<$Res> get node {
@ -78,6 +84,8 @@ class _$YubiKeyDataCopyWithImpl<$Res, $Val extends YubiKeyData>
});
}
/// Create a copy of YubiKeyData
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$DeviceInfoCopyWith<$Res> get info {
@ -111,6 +119,8 @@ class __$$YubiKeyDataImplCopyWithImpl<$Res>
_$YubiKeyDataImpl _value, $Res Function(_$YubiKeyDataImpl) _then)
: super(_value, _then);
/// Create a copy of YubiKeyData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -165,7 +175,9 @@ class _$YubiKeyDataImpl implements _YubiKeyData {
@override
int get hashCode => Object.hash(runtimeType, node, name, info);
@JsonKey(ignore: true)
/// Create a copy of YubiKeyData
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$YubiKeyDataImplCopyWith<_$YubiKeyDataImpl> get copyWith =>
@ -183,8 +195,11 @@ abstract class _YubiKeyData implements YubiKeyData {
String get name;
@override
DeviceInfo get info;
/// Create a copy of YubiKeyData
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$YubiKeyDataImplCopyWith<_$YubiKeyDataImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -238,7 +253,9 @@ mixin _$DeviceNode {
}) =>
throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of DeviceNode
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$DeviceNodeCopyWith<DeviceNode> get copyWith =>
throw _privateConstructorUsedError;
}
@ -262,6 +279,8 @@ class _$DeviceNodeCopyWithImpl<$Res, $Val extends DeviceNode>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of DeviceNode
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -302,6 +321,8 @@ class __$$UsbYubiKeyNodeImplCopyWithImpl<$Res>
_$UsbYubiKeyNodeImpl _value, $Res Function(_$UsbYubiKeyNodeImpl) _then)
: super(_value, _then);
/// Create a copy of DeviceNode
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -330,6 +351,8 @@ class __$$UsbYubiKeyNodeImplCopyWithImpl<$Res>
));
}
/// Create a copy of DeviceNode
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$DeviceInfoCopyWith<$Res>? get info {
@ -376,7 +399,9 @@ class _$UsbYubiKeyNodeImpl extends UsbYubiKeyNode {
@override
int get hashCode => Object.hash(runtimeType, path, name, pid, info);
@JsonKey(ignore: true)
/// Create a copy of DeviceNode
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$UsbYubiKeyNodeImplCopyWith<_$UsbYubiKeyNodeImpl> get copyWith =>
@ -463,8 +488,11 @@ abstract class UsbYubiKeyNode extends DeviceNode {
String get name;
UsbPid get pid;
DeviceInfo? get info;
/// Create a copy of DeviceNode
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$UsbYubiKeyNodeImplCopyWith<_$UsbYubiKeyNodeImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -488,6 +516,8 @@ class __$$NfcReaderNodeImplCopyWithImpl<$Res>
_$NfcReaderNodeImpl _value, $Res Function(_$NfcReaderNodeImpl) _then)
: super(_value, _then);
/// Create a copy of DeviceNode
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -534,7 +564,9 @@ class _$NfcReaderNodeImpl extends NfcReaderNode {
@override
int get hashCode => Object.hash(runtimeType, path, name);
@JsonKey(ignore: true)
/// Create a copy of DeviceNode
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$NfcReaderNodeImplCopyWith<_$NfcReaderNodeImpl> get copyWith =>
@ -618,8 +650,11 @@ abstract class NfcReaderNode extends DeviceNode {
DevicePath get path;
@override
String get name;
/// Create a copy of DeviceNode
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$NfcReaderNodeImplCopyWith<_$NfcReaderNodeImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -636,7 +671,9 @@ mixin _$ActionItem {
Key? get key => throw _privateConstructorUsedError;
Feature? get feature => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of ActionItem
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ActionItemCopyWith<ActionItem> get copyWith =>
throw _privateConstructorUsedError;
}
@ -669,6 +706,8 @@ class _$ActionItemCopyWithImpl<$Res, $Val extends ActionItem>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ActionItem
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -751,6 +790,8 @@ class __$$ActionItemImplCopyWithImpl<$Res>
_$ActionItemImpl _value, $Res Function(_$ActionItemImpl) _then)
: super(_value, _then);
/// Create a copy of ActionItem
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -867,7 +908,9 @@ class _$ActionItemImpl implements _ActionItem {
int get hashCode => Object.hash(runtimeType, icon, title, subtitle, shortcut,
trailing, intent, actionStyle, key, feature);
@JsonKey(ignore: true)
/// Create a copy of ActionItem
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ActionItemImplCopyWith<_$ActionItemImpl> get copyWith =>
@ -904,8 +947,11 @@ abstract class _ActionItem implements ActionItem {
Key? get key;
@override
Feature? get feature;
/// Create a copy of ActionItem
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ActionItemImplCopyWith<_$ActionItemImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -917,7 +963,9 @@ mixin _$WindowState {
bool get active => throw _privateConstructorUsedError;
bool get hidden => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of WindowState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$WindowStateCopyWith<WindowState> get copyWith =>
throw _privateConstructorUsedError;
}
@ -941,6 +989,8 @@ class _$WindowStateCopyWithImpl<$Res, $Val extends WindowState>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of WindowState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -989,6 +1039,8 @@ class __$$WindowStateImplCopyWithImpl<$Res>
_$WindowStateImpl _value, $Res Function(_$WindowStateImpl) _then)
: super(_value, _then);
/// Create a copy of WindowState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1057,7 +1109,9 @@ class _$WindowStateImpl implements _WindowState {
int get hashCode =>
Object.hash(runtimeType, focused, visible, active, hidden);
@JsonKey(ignore: true)
/// Create a copy of WindowState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$WindowStateImplCopyWith<_$WindowStateImpl> get copyWith =>
@ -1079,8 +1133,11 @@ abstract class _WindowState implements WindowState {
bool get active;
@override
bool get hidden;
/// Create a copy of WindowState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$WindowStateImplCopyWith<_$WindowStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -1098,8 +1155,12 @@ mixin _$KeyCustomization {
@_ColorConverter()
Color? get color => throw _privateConstructorUsedError;
/// Serializes this KeyCustomization to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of KeyCustomization
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$KeyCustomizationCopyWith<KeyCustomization> get copyWith =>
throw _privateConstructorUsedError;
}
@ -1126,6 +1187,8 @@ class _$KeyCustomizationCopyWithImpl<$Res, $Val extends KeyCustomization>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of KeyCustomization
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1172,6 +1235,8 @@ class __$$KeyCustomizationImplCopyWithImpl<$Res>
$Res Function(_$KeyCustomizationImpl) _then)
: super(_value, _then);
/// Create a copy of KeyCustomization
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1232,11 +1297,13 @@ class _$KeyCustomizationImpl implements _KeyCustomization {
(identical(other.color, color) || other.color == color));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, serial, name, color);
@JsonKey(ignore: true)
/// Create a copy of KeyCustomization
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$KeyCustomizationImplCopyWith<_$KeyCustomizationImpl> get copyWith =>
@ -1271,8 +1338,11 @@ abstract class _KeyCustomization implements KeyCustomization {
@JsonKey(includeIfNull: false)
@_ColorConverter()
Color? get color;
/// Create a copy of KeyCustomization
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$KeyCustomizationImplCopyWith<_$KeyCustomizationImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -30,8 +30,10 @@ class AppListItem<T> extends ConsumerStatefulWidget {
final String? semanticTitle;
final Widget? trailing;
final List<ActionItem> Function(BuildContext context)? buildPopupActions;
final Widget Function(BuildContext context)? itemBuilder;
final Intent? tapIntent;
final Intent? doubleTapIntent;
final Color? tileColor;
final bool selected;
const AppListItem(
@ -43,8 +45,10 @@ class AppListItem<T> extends ConsumerStatefulWidget {
this.subtitle,
this.trailing,
this.buildPopupActions,
this.itemBuilder,
this.tapIntent,
this.doubleTapIntent,
this.tileColor,
this.selected = false,
});
@ -78,7 +82,7 @@ class _AppListItemState<T> extends ConsumerState<AppListItem> {
item: widget.item,
child: InkWell(
focusNode: _focusNode,
borderRadius: BorderRadius.circular(48),
borderRadius: BorderRadius.circular(16),
onSecondaryTapDown: buildPopupActions == null
? null
: (details) {
@ -118,57 +122,62 @@ class _AppListItemState<T> extends ConsumerState<AppListItem> {
: () {
Actions.invoke(context, doubleTapIntent);
},
child: Stack(
alignment: AlignmentDirectional.center,
children: [
const SizedBox(height: 64),
ListTile(
mouseCursor:
widget.tapIntent != null ? SystemMouseCursors.click : null,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(48)),
selectedTileColor: colorScheme.secondaryContainer,
selectedColor: colorScheme.onSecondaryContainer,
selected: widget.selected,
leading: widget.leading,
title: subtitle == null
// We use SizedBox to fill entire space
? SizedBox(
height: 48,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
widget.title,
overflow: TextOverflow.fade,
maxLines: 1,
softWrap: false,
),
),
)
: Text(
widget.title,
overflow: TextOverflow.fade,
maxLines: 1,
softWrap: false,
),
subtitle: subtitle != null
? Text(
subtitle,
overflow: TextOverflow.fade,
maxLines: 1,
softWrap: false,
)
: null,
trailing: trailing == null
? null
: Focus(
skipTraversal: true,
descendantsAreTraversable: false,
child: trailing,
),
),
],
),
child: widget.itemBuilder != null
? widget.itemBuilder!.call(context)
: Stack(
alignment: AlignmentDirectional.center,
children: [
const SizedBox(height: 64),
ListTile(
mouseCursor: widget.tapIntent != null
? SystemMouseCursors.click
: null,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16)),
selectedTileColor: colorScheme.secondaryContainer,
selectedColor: colorScheme.onSecondaryContainer,
tileColor: widget.tileColor,
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
selected: widget.selected,
leading: widget.leading,
title: subtitle == null
// We use SizedBox to fill entire space
? SizedBox(
height: 48,
child: Align(
alignment: Alignment.centerLeft,
child: Text(
widget.title,
overflow: TextOverflow.fade,
maxLines: 1,
softWrap: false,
),
),
)
: Text(
widget.title,
overflow: TextOverflow.fade,
maxLines: 1,
softWrap: false,
),
subtitle: subtitle != null
? Text(
subtitle,
overflow: TextOverflow.fade,
maxLines: 1,
softWrap: false,
)
: null,
trailing: trailing == null
? null
: Focus(
skipTraversal: true,
descendantsAreTraversable: false,
child: trailing,
),
),
],
),
),
),
);

View File

@ -22,6 +22,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../core/state.dart';
import '../../management/models.dart';
@ -29,18 +30,29 @@ import '../../widgets/delayed_visibility.dart';
import '../../widgets/file_drop_target.dart';
import '../message.dart';
import '../shortcuts.dart';
import '../state.dart';
import 'fs_dialog.dart';
import 'keys.dart';
import 'navigation.dart';
final _navigationProvider = StateNotifierProvider<_NavigationProvider, bool>(
(ref) => _NavigationProvider());
final _navigationVisibilityProvider =
StateNotifierProvider<_VisibilityNotifier, bool>((ref) =>
_VisibilityNotifier('NAVIGATION_VISIBILITY', ref.watch(prefProvider)));
class _NavigationProvider extends StateNotifier<bool> {
_NavigationProvider() : super(true);
final _detailViewVisibilityProvider =
StateNotifierProvider<_VisibilityNotifier, bool>((ref) =>
_VisibilityNotifier('DETAIL_VIEW_VISIBILITY', ref.watch(prefProvider)));
class _VisibilityNotifier extends StateNotifier<bool> {
final String _key;
final SharedPreferences _prefs;
_VisibilityNotifier(this._key, this._prefs)
: super(_prefs.getBool(_key) ?? true);
void toggleExpanded() {
state = !state;
final newValue = !state;
state = newValue;
_prefs.setBool(_key, newValue);
}
}
@ -308,14 +320,17 @@ class _AppPageState extends ConsumerState<AppPage> {
Widget? _buildAppBarTitle(
BuildContext context, bool hasRail, bool hasManage, bool fullyExpanded) {
final showNavigation = ref.watch(_navigationProvider);
final showNavigation = ref.watch(_navigationVisibilityProvider);
final showDetailView = ref.watch(_detailViewVisibilityProvider);
EdgeInsets padding;
if (fullyExpanded) {
padding = EdgeInsets.only(left: showNavigation ? 280 : 72, right: 320);
padding = EdgeInsets.only(
left: showNavigation ? 280 : 72, right: showDetailView ? 320 : 0.0);
} else if (!hasRail && hasManage) {
padding = const EdgeInsets.only(right: 320);
} else if (hasRail && hasManage) {
padding = const EdgeInsets.only(left: 72, right: 320);
padding = EdgeInsets.only(left: 72, right: showDetailView ? 320 : 0.0);
} else if (hasRail && !hasManage) {
padding = const EdgeInsets.only(left: 72);
} else {
@ -344,21 +359,23 @@ class _AppPageState extends ConsumerState<AppPage> {
}
Widget _buildMainContent(BuildContext context, bool expanded) {
final actions = widget.actionsBuilder?.call(context, expanded) ?? [];
final showDetailView = ref.watch(_detailViewVisibilityProvider);
final actions =
widget.actionsBuilder?.call(context, expanded && showDetailView) ?? [];
final content = Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: widget.centered
? CrossAxisAlignment.center
: CrossAxisAlignment.start,
children: [
widget.builder(context, expanded),
widget.builder(context, expanded && showDetailView),
if (actions.isNotEmpty)
Align(
alignment:
widget.centered ? Alignment.center : Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(
top: 16, bottom: 0, left: 16, right: 16),
top: 16, bottom: 0, left: 18, right: 18),
child: Wrap(
spacing: 8,
runSpacing: 4,
@ -369,7 +386,7 @@ class _AppPageState extends ConsumerState<AppPage> {
if (widget.footnote != null)
Padding(
padding:
const EdgeInsets.only(bottom: 16, top: 33, left: 16, right: 16),
const EdgeInsets.only(bottom: 16, top: 33, left: 18, right: 18),
child: Opacity(
opacity: 0.6,
child: Text(
@ -399,7 +416,7 @@ class _AppPageState extends ConsumerState<AppPage> {
alignment: Alignment.topLeft,
child: Padding(
padding: const EdgeInsets.only(
left: 16.0, right: 16.0, bottom: 24.0, top: 4.0),
left: 18.0, right: 18.0, bottom: 24.0, top: 4.0),
child: _buildTitle(context),
),
),
@ -452,7 +469,7 @@ class _AppPageState extends ConsumerState<AppPage> {
child: Padding(
key: _sliverTitleWrapperGlobalKey,
padding: const EdgeInsets.only(
left: 16.0, right: 16.0, bottom: 12.0, top: 4.0),
left: 18.0, right: 18.0, bottom: 12.0, top: 4.0),
child: _buildTitle(context),
),
),
@ -499,7 +516,8 @@ class _AppPageState extends ConsumerState<AppPage> {
BuildContext context, bool hasDrawer, bool hasRail, bool hasManage) {
final l10n = AppLocalizations.of(context)!;
final fullyExpanded = !hasDrawer && hasRail && hasManage;
final showNavigation = ref.watch(_navigationProvider);
final showNavigation = ref.watch(_navigationVisibilityProvider);
final showDetailView = ref.watch(_detailViewVisibilityProvider);
final hasDetailsOrKeyActions =
widget.detailViewBuilder != null || widget.keyActionsBuilder != null;
var body = _buildMainContent(context, hasManage);
@ -518,187 +536,211 @@ class _AppPageState extends ConsumerState<AppPage> {
);
}
if (hasRail || hasManage) {
body = Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (hasRail && (!fullyExpanded || !showNavigation))
SizedBox(
width: 72,
child: _VisibilityListener(
targetKey: _navKey,
controller: _navController,
child: SingleChildScrollView(
child: NavigationContent(
key: _navKey,
shouldPop: false,
extended: false,
body = GestureDetector(
behavior: HitTestBehavior.deferToChild,
onTap: () {
Actions.invoke(context, const EscapeIntent());
FocusManager.instance.primaryFocus?.unfocus();
},
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (hasRail && (!fullyExpanded || !showNavigation))
SizedBox(
width: 72,
child: _VisibilityListener(
targetKey: _navKey,
controller: _navController,
child: SingleChildScrollView(
child: NavigationContent(
key: _navKey,
shouldPop: false,
extended: false,
),
),
),
),
),
if (fullyExpanded && showNavigation)
SizedBox(
width: 280,
child: _VisibilityListener(
controller: _navController,
targetKey: _navExpandedKey,
child: SingleChildScrollView(
child: Material(
type: MaterialType.transparency,
child: NavigationContent(
key: _navExpandedKey,
shouldPop: false,
extended: true,
if (fullyExpanded && showNavigation)
SizedBox(
width: 280,
child: _VisibilityListener(
controller: _navController,
targetKey: _navExpandedKey,
child: SingleChildScrollView(
child: Material(
type: MaterialType.transparency,
child: NavigationContent(
key: _navExpandedKey,
shouldPop: false,
extended: true,
),
),
),
)),
const SizedBox(width: 8),
Expanded(child: body),
if (hasManage &&
!hasDetailsOrKeyActions &&
widget.capabilities != null &&
widget.capabilities?.first != Capability.u2f)
// Add a placeholder for the Manage/Details column. Exceptions are:
// - the "Security Key" because it does not have any actions/details.
// - pages without Capabilities
const SizedBox(width: 336), // simulate column
if (hasManage && hasDetailsOrKeyActions && showDetailView)
_VisibilityListener(
controller: _detailsController,
targetKey: _detailsViewGlobalKey,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: SizedBox(
width: 320,
child: Column(
key: _detailsViewGlobalKey,
children: [
if (widget.detailViewBuilder != null)
widget.detailViewBuilder!(context),
if (widget.keyActionsBuilder != null)
widget.keyActionsBuilder!(context),
],
),
),
),
)),
const SizedBox(width: 8),
Expanded(
child: GestureDetector(
behavior: HitTestBehavior.deferToChild,
onTap: () {
Actions.invoke(context, const EscapeIntent());
},
child: Stack(children: [
Container(
color: Colors.transparent,
),
body
]),
)),
if (hasManage &&
!hasDetailsOrKeyActions &&
widget.capabilities != null &&
widget.capabilities?.first != Capability.u2f)
// Add a placeholder for the Manage/Details column. Exceptions are:
// - the "Security Key" because it does not have any actions/details.
// - pages without Capabilities
const SizedBox(width: 336), // simulate column
if (hasManage && hasDetailsOrKeyActions)
_VisibilityListener(
controller: _detailsController,
targetKey: _detailsViewGlobalKey,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: SizedBox(
width: 320,
child: Column(
key: _detailsViewGlobalKey,
children: [
if (widget.detailViewBuilder != null)
widget.detailViewBuilder!(context),
if (widget.keyActionsBuilder != null)
widget.keyActionsBuilder!(context),
],
),
),
),
),
),
],
],
),
);
}
return Scaffold(
key: scaffoldGlobalKey,
appBar: AppBar(
bottom: PreferredSize(
preferredSize: const Size.fromHeight(1.0),
child: ListenableBuilder(
listenable: _scrolledUnderController,
builder: (context, child) {
final visible = _scrolledUnderController.someIsScrolledUnder;
return AnimatedOpacity(
opacity: visible ? 1 : 0,
duration: const Duration(milliseconds: 300),
child: Container(
color: Theme.of(context).colorScheme.secondaryContainer,
height: 1.0,
),
);
},
appBar: _GestureDetectorAppBar(
onTap: () {
Actions.invoke(context, const EscapeIntent());
FocusManager.instance.primaryFocus?.unfocus();
},
appBar: AppBar(
bottom: PreferredSize(
preferredSize: const Size.fromHeight(1.0),
child: ListenableBuilder(
listenable: _scrolledUnderController,
builder: (context, child) {
final visible = _scrolledUnderController.someIsScrolledUnder;
return AnimatedOpacity(
opacity: visible ? 1 : 0,
duration: const Duration(milliseconds: 300),
child: Container(
color: Theme.of(context).hoverColor,
height: 1.0,
),
);
},
),
),
),
scrolledUnderElevation: 0.0,
leadingWidth: hasRail ? 84 : null,
backgroundColor: Theme.of(context).colorScheme.surface,
title: _buildAppBarTitle(
context,
hasRail,
hasManage,
fullyExpanded,
),
leading: hasRail
? Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: IconButton(
icon: Icon(Symbols.menu, semanticLabel: navigationText),
tooltip: navigationText,
onPressed: fullyExpanded
? () {
ref
.read(_navigationProvider.notifier)
.toggleExpanded();
}
: () {
scaffoldGlobalKey.currentState?.openDrawer();
},
),
)),
const SizedBox(width: 12),
],
)
: Builder(
builder: (context) {
// Need to wrap with builder to get Scaffold context
return IconButton(
onPressed: () => Scaffold.of(context).openDrawer(),
icon: const Icon(Symbols.menu),
);
},
),
actions: [
if (widget.actionButtonBuilder == null &&
(widget.keyActionsBuilder != null && !hasManage))
Padding(
padding: const EdgeInsets.only(left: 4),
child: IconButton(
key: actionsIconButtonKey,
onPressed: () {
showBlurDialog(
context: context,
barrierColor: Colors.transparent,
builder: (context) => FsDialog(
child: Padding(
padding: const EdgeInsets.only(top: 32),
child: widget.keyActionsBuilder!(context),
scrolledUnderElevation: 0.0,
leadingWidth: hasRail ? 84 : null,
backgroundColor: Theme.of(context).colorScheme.surface,
title: _buildAppBarTitle(
context,
hasRail,
hasManage,
fullyExpanded,
),
centerTitle: true,
leading: hasRail
? Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: IconButton(
icon: Icon(Symbols.menu, semanticLabel: navigationText),
tooltip: navigationText,
onPressed: fullyExpanded
? () {
ref
.read(
_navigationVisibilityProvider.notifier)
.toggleExpanded();
}
: () {
scaffoldGlobalKey.currentState?.openDrawer();
},
),
),
);
},
icon: widget.keyActionsBadge
? Badge(
child: Icon(Symbols.more_vert,
semanticLabel: l10n.s_configure_yk),
)
: Icon(Symbols.more_vert,
semanticLabel: l10n.s_configure_yk),
iconSize: 24,
tooltip: l10n.s_configure_yk,
padding: const EdgeInsets.all(12),
)),
const SizedBox(width: 12),
],
)
: Builder(
builder: (context) {
// Need to wrap with builder to get Scaffold context
return IconButton(
onPressed: () => Scaffold.of(context).openDrawer(),
icon: const Icon(Symbols.menu),
);
},
),
actions: [
if (widget.actionButtonBuilder == null &&
(widget.keyActionsBuilder != null &&
(!hasManage || !showDetailView)))
Padding(
padding: const EdgeInsets.only(left: 4),
child: IconButton(
key: actionsIconButtonKey,
onPressed: () {
showBlurDialog(
context: context,
barrierColor: Colors.transparent,
builder: (context) => FsDialog(
child: Padding(
padding: const EdgeInsets.only(top: 32),
child: widget.keyActionsBuilder!(context),
),
),
);
},
icon: widget.keyActionsBadge
? Badge(
child: Icon(Symbols.more_vert,
semanticLabel: l10n.s_configure_yk),
)
: Icon(Symbols.more_vert,
semanticLabel: l10n.s_configure_yk),
iconSize: 24,
tooltip: l10n.s_configure_yk,
padding: const EdgeInsets.all(12),
),
),
),
if (widget.actionButtonBuilder != null)
Padding(
padding: const EdgeInsets.only(right: 12),
child: widget.actionButtonBuilder!.call(context),
),
],
if (hasManage &&
(widget.keyActionsBuilder != null ||
widget.detailViewBuilder != null))
Padding(
padding: const EdgeInsets.only(left: 4),
child: IconButton(
key: toggleDetailViewIconButtonKey,
onPressed: () {
ref
.read(_detailViewVisibilityProvider.notifier)
.toggleExpanded();
},
icon: const Icon(Symbols.view_sidebar),
iconSize: 24,
tooltip: showDetailView
? l10n.s_collapse_sidebar
: l10n.s_expand_sidebar,
padding: const EdgeInsets.all(12),
),
),
if (widget.actionButtonBuilder != null)
Padding(
padding: const EdgeInsets.only(right: 12),
child: widget.actionButtonBuilder!.call(context),
),
],
),
),
drawer: hasDrawer ? _buildDrawer(context) : null,
body: body,
@ -706,23 +748,66 @@ class _AppPageState extends ConsumerState<AppPage> {
}
}
class CapabilityBadge extends StatelessWidget {
final Capability capability;
class _GestureDetectorAppBar extends StatelessWidget
implements PreferredSizeWidget {
final AppBar appBar;
final void Function() onTap;
const CapabilityBadge(this.capability, {super.key});
const _GestureDetectorAppBar({required this.appBar, required this.onTap});
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.deferToChild, onTap: onTap, child: appBar);
}
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}
class CapabilityBadge extends ConsumerWidget {
final Capability capability;
final bool noTooltip;
const CapabilityBadge(this.capability, {super.key, this.noTooltip = false});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!;
final colorScheme = Theme.of(context).colorScheme;
final text = Text(capability.getDisplayName(l10n));
final (fipsCapable, fipsApproved) = ref
.watch(currentDeviceDataProvider)
.valueOrNull
?.info
.getFipsStatus(capability) ??
(false, false);
final label = fipsCapable
? Row(
children: [
Icon(
Symbols.shield,
color: colorScheme.onSecondaryContainer,
size: 12,
fill: fipsApproved ? 1 : 0,
),
const SizedBox(width: 4),
text,
],
)
: text;
return Badge(
backgroundColor: colorScheme.secondaryContainer,
textColor: colorScheme.onSecondaryContainer,
padding: const EdgeInsets.symmetric(horizontal: 6),
largeSize: MediaQuery.of(context).textScaler.scale(20),
label: Text(
capability.getDisplayName(l10n),
),
label: fipsCapable && !noTooltip
? Tooltip(
message:
fipsApproved ? l10n.l_fips_approved : l10n.l_fips_capable,
child: label,
)
: label,
);
}
}

View File

@ -25,6 +25,8 @@ const _prefix = 'app.keys';
const deviceInfoListTile = Key('$_prefix.device_info_list_tile');
const noDeviceAvatar = Key('$_prefix.no_device_avatar');
const actionsIconButtonKey = Key('$_prefix.actions_icon_button');
const toggleDetailViewIconButtonKey =
Key('$_prefix.toggle_detail_view_icon_button');
// drawer items
const homeDrawer = Key('$_prefix.drawer.home');

View File

@ -71,9 +71,9 @@ class MessagePage extends StatelessWidget {
delayedContent: delayedContent,
builder: (context, _) => Padding(
padding: EdgeInsets.only(
left: 16.0,
left: 18.0,
top: 0.0,
right: 16.0,
right: 18.0,
bottom: centered && actionsBuilder == null ? 96 : 0),
child: SizedBox(
width: centered ? 250 : 350,

View File

@ -163,6 +163,8 @@ class _ResetDialogState extends ConsumerState<ResetDialog> {
_subscription = null;
}, onError: (e) {
_log.error('Error performing FIDO reset', e);
if (!context.mounted) return;
Navigator.of(context).pop();
final String errorMessage;
// TODO: Make this cleaner than importing desktop specific RpcError.

View File

@ -175,7 +175,7 @@ UserInteractionController _dialogUserInteraction(
builder: (context) {
return PopScope(
canPop: onCancel != null,
onPopInvoked: (didPop) {
onPopInvokedWithResult: (didPop, _) {
if (didPop) {
wasPopped = true;
if (!completed && onCancel != null) {

View File

@ -20,7 +20,9 @@ mixin _$Version {
int get minor => throw _privateConstructorUsedError;
int get patch => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of Version
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$VersionCopyWith<Version> get copyWith => throw _privateConstructorUsedError;
}
@ -42,6 +44,8 @@ class _$VersionCopyWithImpl<$Res, $Val extends Version>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Version
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -84,6 +88,8 @@ class __$$VersionImplCopyWithImpl<$Res>
_$VersionImpl _value, $Res Function(_$VersionImpl) _then)
: super(_value, _then);
/// Create a copy of Version
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -140,7 +146,9 @@ class _$VersionImpl extends _Version {
@override
int get hashCode => Object.hash(runtimeType, major, minor, patch);
@JsonKey(ignore: true)
/// Create a copy of Version
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$VersionImplCopyWith<_$VersionImpl> get copyWith =>
@ -158,8 +166,11 @@ abstract class _Version extends Version {
int get minor;
@override
int get patch;
/// Create a copy of Version
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$VersionImplCopyWith<_$VersionImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -21,6 +21,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../app/models.dart';
import '../widgets/flex_box.dart';
bool get isDesktop => const [
TargetPlatform.windows,
@ -119,3 +120,20 @@ final featureProvider = Provider<FeatureProvider>((ref) {
return isEnabled;
});
class LayoutNotifier extends StateNotifier<FlexLayout> {
final String _key;
final SharedPreferences _prefs;
LayoutNotifier(this._key, this._prefs)
: super(_fromName(_prefs.getString(_key)));
void setLayout(FlexLayout layout) {
state = layout;
_prefs.setString(_key, layout.name);
}
static FlexLayout _fromName(String? name) => FlexLayout.values.firstWhere(
(element) => element.name == name,
orElse: () => FlexLayout.list,
);
}

View File

@ -87,15 +87,16 @@ class UsbDeviceNotifier extends StateNotifier<List<UsbYubiKeyNode>> {
return;
}
final pids = {
for (var e in (scan['pids'] as Map).entries)
UsbPid.fromValue(int.parse(e.key)): e.value as int
};
final numDevices = pids.values.fold<int>(0, (a, b) => a + b);
final numDevices =
(scan['pids'] as Map).values.fold<int>(0, (a, b) => a + b as int);
if (_usbState != scan['state'] || state.length != numDevices) {
var usbResult = await rpc.command('get', ['usb']);
_log.info('USB state change', jsonEncode(usbResult));
_usbState = usbResult['data']['state'];
final pids = {
for (var e in (usbResult['data']['pids'] as Map).entries)
UsbPid.fromValue(int.parse(e.key)): e.value as int
};
List<UsbYubiKeyNode> usbDevices = [];
for (String id in (usbResult['children'] as Map).keys) {

View File

@ -184,6 +184,12 @@ class _DesktopFidoStateNotifier extends FidoStateNotifier {
rethrow;
}
}
@override
Future<void> enableEnterpriseAttestation() async {
await _session.command('enable_ep_attestation');
ref.invalidateSelf();
}
}
final desktopFingerprintProvider = AsyncNotifierProvider.autoDispose

View File

@ -81,8 +81,13 @@ mixin _$RpcResponse {
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
/// Serializes this RpcResponse to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of RpcResponse
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$RpcResponseCopyWith<RpcResponse> get copyWith =>
throw _privateConstructorUsedError;
}
@ -106,6 +111,8 @@ class _$RpcResponseCopyWithImpl<$Res, $Val extends RpcResponse>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of RpcResponse
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -139,6 +146,8 @@ class __$$SuccessImplCopyWithImpl<$Res>
_$SuccessImpl _value, $Res Function(_$SuccessImpl) _then)
: super(_value, _then);
/// Create a copy of RpcResponse
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -203,14 +212,16 @@ class _$SuccessImpl implements Success {
const DeepCollectionEquality().equals(other._flags, _flags));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(_body),
const DeepCollectionEquality().hash(_flags));
@JsonKey(ignore: true)
/// Create a copy of RpcResponse
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SuccessImplCopyWith<_$SuccessImpl> get copyWith =>
@ -306,8 +317,11 @@ abstract class Success implements RpcResponse {
@override
Map<String, dynamic> get body;
List<String> get flags;
/// Create a copy of RpcResponse
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SuccessImplCopyWith<_$SuccessImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -331,6 +345,8 @@ class __$$SignalImplCopyWithImpl<$Res>
_$SignalImpl _value, $Res Function(_$SignalImpl) _then)
: super(_value, _then);
/// Create a copy of RpcResponse
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -388,12 +404,14 @@ class _$SignalImpl implements Signal {
const DeepCollectionEquality().equals(other._body, _body));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType, status, const DeepCollectionEquality().hash(_body));
@JsonKey(ignore: true)
/// Create a copy of RpcResponse
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SignalImplCopyWith<_$SignalImpl> get copyWith =>
@ -489,8 +507,11 @@ abstract class Signal implements RpcResponse {
String get status;
@override
Map<String, dynamic> get body;
/// Create a copy of RpcResponse
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SignalImplCopyWith<_$SignalImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -514,6 +535,8 @@ class __$$RpcErrorImplCopyWithImpl<$Res>
_$RpcErrorImpl _value, $Res Function(_$RpcErrorImpl) _then)
: super(_value, _then);
/// Create a copy of RpcResponse
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -579,12 +602,14 @@ class _$RpcErrorImpl implements RpcError {
const DeepCollectionEquality().equals(other._body, _body));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType, status, message, const DeepCollectionEquality().hash(_body));
@JsonKey(ignore: true)
/// Create a copy of RpcResponse
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$RpcErrorImplCopyWith<_$RpcErrorImpl> get copyWith =>
@ -682,8 +707,11 @@ abstract class RpcError implements RpcResponse {
String get message;
@override
Map<String, dynamic> get body;
/// Create a copy of RpcResponse
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$RpcErrorImplCopyWith<_$RpcErrorImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -697,8 +725,12 @@ mixin _$RpcState {
String get version => throw _privateConstructorUsedError;
bool get isAdmin => throw _privateConstructorUsedError;
/// Serializes this RpcState to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of RpcState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$RpcStateCopyWith<RpcState> get copyWith =>
throw _privateConstructorUsedError;
}
@ -721,6 +753,8 @@ class _$RpcStateCopyWithImpl<$Res, $Val extends RpcState>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of RpcState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -759,6 +793,8 @@ class __$$RpcStateImplCopyWithImpl<$Res>
_$RpcStateImpl _value, $Res Function(_$RpcStateImpl) _then)
: super(_value, _then);
/// Create a copy of RpcState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -805,11 +841,13 @@ class _$RpcStateImpl implements _RpcState {
(identical(other.isAdmin, isAdmin) || other.isAdmin == isAdmin));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, version, isAdmin);
@JsonKey(ignore: true)
/// Create a copy of RpcState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$RpcStateImplCopyWith<_$RpcStateImpl> get copyWith =>
@ -834,8 +872,11 @@ abstract class _RpcState implements RpcState {
String get version;
@override
bool get isAdmin;
/// Create a copy of RpcState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$RpcStateImplCopyWith<_$RpcStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -21,6 +21,8 @@ final actions = fido.feature('actions');
final actionsPin = actions.feature('pin');
final actionsAddFingerprint = actions.feature('addFingerprint');
final actionsReset = actions.feature('reset');
final enableEnterpriseAttestation =
actions.feature('enableEnterpriseAttestation');
final credentials = fido.feature('credentials');

View File

@ -25,6 +25,8 @@ const _credentialInfo = '$_prefix.credential.info';
// Key actions
const managePinAction = Key('$_keyAction.manage_pin');
const addFingerprintAction = Key('$_keyAction.add_fingerprint');
const enableEnterpriseAttestation =
Key('$_keyAction.enable_enterprise_attestation');
const newPin = Key('$_keyAction.new_pin');
const confirmPin = Key('$_keyAction.confirm_pin');
const currentPin = Key('$_keyAction.current_pin');

View File

@ -50,6 +50,8 @@ class FidoState with _$FidoState {
bool get forcePinChange => info['force_pin_change'] == true;
bool get pinBlocked => pinRetries == 0;
bool? get enterpriseAttestation => info['options']['ep'];
}
@freezed

View File

@ -24,8 +24,12 @@ mixin _$FidoState {
bool get unlocked => throw _privateConstructorUsedError;
int? get pinRetries => throw _privateConstructorUsedError;
/// Serializes this FidoState to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of FidoState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$FidoStateCopyWith<FidoState> get copyWith =>
throw _privateConstructorUsedError;
}
@ -48,6 +52,8 @@ class _$FidoStateCopyWithImpl<$Res, $Val extends FidoState>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of FidoState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -91,6 +97,8 @@ class __$$FidoStateImplCopyWithImpl<$Res>
_$FidoStateImpl _value, $Res Function(_$FidoStateImpl) _then)
: super(_value, _then);
/// Create a copy of FidoState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -158,12 +166,14 @@ class _$FidoStateImpl extends _FidoState {
other.pinRetries == pinRetries));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,
const DeepCollectionEquality().hash(_info), unlocked, pinRetries);
@JsonKey(ignore: true)
/// Create a copy of FidoState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$FidoStateImplCopyWith<_$FidoStateImpl> get copyWith =>
@ -193,8 +203,11 @@ abstract class _FidoState extends FidoState {
bool get unlocked;
@override
int? get pinRetries;
/// Create a copy of FidoState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$FidoStateImplCopyWith<_$FidoStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -256,6 +269,9 @@ class _$PinResultCopyWithImpl<$Res, $Val extends PinResult>
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of PinResult
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
@ -272,6 +288,9 @@ class __$$PinSuccessImplCopyWithImpl<$Res>
__$$PinSuccessImplCopyWithImpl(
_$PinSuccessImpl _value, $Res Function(_$PinSuccessImpl) _then)
: super(_value, _then);
/// Create a copy of PinResult
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
@ -379,6 +398,8 @@ class __$$PinFailureImplCopyWithImpl<$Res>
_$PinFailureImpl _value, $Res Function(_$PinFailureImpl) _then)
: super(_value, _then);
/// Create a copy of PinResult
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -392,6 +413,8 @@ class __$$PinFailureImplCopyWithImpl<$Res>
));
}
/// Create a copy of PinResult
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$FidoPinFailureReasonCopyWith<$Res> get reason {
@ -425,7 +448,9 @@ class _$PinFailureImpl implements _PinFailure {
@override
int get hashCode => Object.hash(runtimeType, reason);
@JsonKey(ignore: true)
/// Create a copy of PinResult
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$PinFailureImplCopyWith<_$PinFailureImpl> get copyWith =>
@ -498,7 +523,10 @@ abstract class _PinFailure implements PinResult {
factory _PinFailure(final FidoPinFailureReason reason) = _$PinFailureImpl;
FidoPinFailureReason get reason;
@JsonKey(ignore: true)
/// Create a copy of PinResult
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$PinFailureImplCopyWith<_$PinFailureImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -562,6 +590,9 @@ class _$FidoPinFailureReasonCopyWithImpl<$Res,
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of FidoPinFailureReason
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
@ -581,6 +612,8 @@ class __$$FidoInvalidPinImplCopyWithImpl<$Res>
_$FidoInvalidPinImpl _value, $Res Function(_$FidoInvalidPinImpl) _then)
: super(_value, _then);
/// Create a copy of FidoPinFailureReason
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -628,7 +661,9 @@ class _$FidoInvalidPinImpl implements FidoInvalidPin {
@override
int get hashCode => Object.hash(runtimeType, retries, authBlocked);
@JsonKey(ignore: true)
/// Create a copy of FidoPinFailureReason
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$FidoInvalidPinImplCopyWith<_$FidoInvalidPinImpl> get copyWith =>
@ -704,7 +739,10 @@ abstract class FidoInvalidPin implements FidoPinFailureReason {
int get retries;
bool get authBlocked;
@JsonKey(ignore: true)
/// Create a copy of FidoPinFailureReason
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$FidoInvalidPinImplCopyWith<_$FidoInvalidPinImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -723,6 +761,9 @@ class __$$FidoWeakPinImplCopyWithImpl<$Res>
__$$FidoWeakPinImplCopyWithImpl(
_$FidoWeakPinImpl _value, $Res Function(_$FidoWeakPinImpl) _then)
: super(_value, _then);
/// Create a copy of FidoPinFailureReason
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
@ -820,8 +861,12 @@ mixin _$Fingerprint {
String get templateId => throw _privateConstructorUsedError;
String? get name => throw _privateConstructorUsedError;
/// Serializes this Fingerprint to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of Fingerprint
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$FingerprintCopyWith<Fingerprint> get copyWith =>
throw _privateConstructorUsedError;
}
@ -845,6 +890,8 @@ class _$FingerprintCopyWithImpl<$Res, $Val extends Fingerprint>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of Fingerprint
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -883,6 +930,8 @@ class __$$FingerprintImplCopyWithImpl<$Res>
_$FingerprintImpl _value, $Res Function(_$FingerprintImpl) _then)
: super(_value, _then);
/// Create a copy of Fingerprint
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -930,11 +979,13 @@ class _$FingerprintImpl extends _Fingerprint {
(identical(other.name, name) || other.name == name));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, templateId, name);
@JsonKey(ignore: true)
/// Create a copy of Fingerprint
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$FingerprintImplCopyWith<_$FingerprintImpl> get copyWith =>
@ -960,8 +1011,11 @@ abstract class _Fingerprint extends Fingerprint {
String get templateId;
@override
String? get name;
/// Create a copy of Fingerprint
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$FingerprintImplCopyWith<_$FingerprintImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -1030,6 +1084,9 @@ class _$FingerprintEventCopyWithImpl<$Res, $Val extends FingerprintEvent>
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of FingerprintEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
@ -1049,6 +1106,8 @@ class __$$EventCaptureImplCopyWithImpl<$Res>
_$EventCaptureImpl _value, $Res Function(_$EventCaptureImpl) _then)
: super(_value, _then);
/// Create a copy of FingerprintEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1088,7 +1147,9 @@ class _$EventCaptureImpl implements _EventCapture {
@override
int get hashCode => Object.hash(runtimeType, remaining);
@JsonKey(ignore: true)
/// Create a copy of FingerprintEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$EventCaptureImplCopyWith<_$EventCaptureImpl> get copyWith =>
@ -1167,7 +1228,10 @@ abstract class _EventCapture implements FingerprintEvent {
factory _EventCapture(final int remaining) = _$EventCaptureImpl;
int get remaining;
@JsonKey(ignore: true)
/// Create a copy of FingerprintEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$EventCaptureImplCopyWith<_$EventCaptureImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -1191,6 +1255,8 @@ class __$$EventCompleteImplCopyWithImpl<$Res>
_$EventCompleteImpl _value, $Res Function(_$EventCompleteImpl) _then)
: super(_value, _then);
/// Create a copy of FingerprintEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1204,6 +1270,8 @@ class __$$EventCompleteImplCopyWithImpl<$Res>
));
}
/// Create a copy of FingerprintEvent
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$FingerprintCopyWith<$Res> get fingerprint {
@ -1238,7 +1306,9 @@ class _$EventCompleteImpl implements _EventComplete {
@override
int get hashCode => Object.hash(runtimeType, fingerprint);
@JsonKey(ignore: true)
/// Create a copy of FingerprintEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$EventCompleteImplCopyWith<_$EventCompleteImpl> get copyWith =>
@ -1317,7 +1387,10 @@ abstract class _EventComplete implements FingerprintEvent {
factory _EventComplete(final Fingerprint fingerprint) = _$EventCompleteImpl;
Fingerprint get fingerprint;
@JsonKey(ignore: true)
/// Create a copy of FingerprintEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$EventCompleteImplCopyWith<_$EventCompleteImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -1339,6 +1412,8 @@ class __$$EventErrorImplCopyWithImpl<$Res>
_$EventErrorImpl _value, $Res Function(_$EventErrorImpl) _then)
: super(_value, _then);
/// Create a copy of FingerprintEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1377,7 +1452,9 @@ class _$EventErrorImpl implements _EventError {
@override
int get hashCode => Object.hash(runtimeType, code);
@JsonKey(ignore: true)
/// Create a copy of FingerprintEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$EventErrorImplCopyWith<_$EventErrorImpl> get copyWith =>
@ -1456,7 +1533,10 @@ abstract class _EventError implements FingerprintEvent {
factory _EventError(final int code) = _$EventErrorImpl;
int get code;
@JsonKey(ignore: true)
/// Create a copy of FingerprintEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$EventErrorImplCopyWith<_$EventErrorImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -1473,8 +1553,12 @@ mixin _$FidoCredential {
String get userName => throw _privateConstructorUsedError;
String? get displayName => throw _privateConstructorUsedError;
/// Serializes this FidoCredential to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of FidoCredential
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$FidoCredentialCopyWith<FidoCredential> get copyWith =>
throw _privateConstructorUsedError;
}
@ -1503,6 +1587,8 @@ class _$FidoCredentialCopyWithImpl<$Res, $Val extends FidoCredential>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of FidoCredential
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1561,6 +1647,8 @@ class __$$FidoCredentialImplCopyWithImpl<$Res>
_$FidoCredentialImpl _value, $Res Function(_$FidoCredentialImpl) _then)
: super(_value, _then);
/// Create a copy of FidoCredential
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1639,12 +1727,14 @@ class _$FidoCredentialImpl implements _FidoCredential {
other.displayName == displayName));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType, rpId, credentialId, userId, userName, displayName);
@JsonKey(ignore: true)
/// Create a copy of FidoCredential
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$FidoCredentialImplCopyWith<_$FidoCredentialImpl> get copyWith =>
@ -1680,8 +1770,11 @@ abstract class _FidoCredential implements FidoCredential {
String get userName;
@override
String? get displayName;
/// Create a copy of FidoCredential
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$FidoCredentialImplCopyWith<_$FidoCredentialImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -18,6 +18,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../app/models.dart';
import '../core/state.dart';
import '../widgets/flex_box.dart';
import 'models.dart';
final passkeysSearchProvider =
@ -32,6 +33,11 @@ class PasskeysSearchNotifier extends StateNotifier<String> {
}
}
final passkeysLayoutProvider =
StateNotifierProvider<LayoutNotifier, FlexLayout>(
(ref) => LayoutNotifier('FIDO_PASSKEYS_LAYOUT', ref.watch(prefProvider)),
);
final fidoStateProvider = AsyncNotifierProvider.autoDispose
.family<FidoStateNotifier, FidoState, DevicePath>(
() => throw UnimplementedError(),
@ -41,6 +47,7 @@ abstract class FidoStateNotifier extends ApplicationStateNotifier<FidoState> {
Stream<InteractionEvent> reset();
Future<PinResult> setPin(String newPin, {String? oldPin});
Future<PinResult> unlock(String pin);
Future<void> enableEnterpriseAttestation();
}
final fingerprintProvider = AsyncNotifierProvider.autoDispose

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2022-2023 Yubico.
* Copyright (C) 2022-2024 Yubico.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -123,6 +123,8 @@ class _AddFingerprintDialogState extends ConsumerState<AddFingerprintDialog>
});
}, onError: (error, stacktrace) {
_log.error('Error adding fingerprint', error, stacktrace);
if (!mounted) return;
Navigator.of(context).pop();
final l10n = AppLocalizations.of(context)!;
final String errorMessage;
@ -255,7 +257,11 @@ class _AddFingerprintDialogState extends ConsumerState<AddFingerprintDialog>
});
},
onFieldSubmitted: (_) {
_submit();
if (_label.isNotEmpty) {
_submit();
} else {
_nameFocus.requestFocus();
}
},
).init(),
)

View File

@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../app/message.dart';
import '../../app/models.dart';
import '../../app/state.dart';
import '../../widgets/responsive_dialog.dart';
import '../state.dart';
class EnableEnterpriseAttestationDialog extends ConsumerWidget {
final DevicePath devicePath;
const EnableEnterpriseAttestationDialog(this.devicePath, {super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!;
return ResponsiveDialog(
title: Text(l10n.s_enable_ep_attestation),
actions: [
TextButton(
onPressed: () async {
await ref
.read(fidoStateProvider(devicePath).notifier)
.enableEnterpriseAttestation();
await ref.read(withContextProvider)((context) async {
Navigator.of(context).pop();
showMessage(context, l10n.s_ep_attestation_enabled);
});
},
child: Text(l10n.s_enable),
),
],
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 18.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.p_enable_ep_attestation_desc),
]
.map((e) => Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: e,
))
.toList(),
),
),
);
}
}

View File

@ -30,6 +30,7 @@ import '../../app/views/app_list_item.dart';
import '../../app/views/app_page.dart';
import '../../app/views/message_page.dart';
import '../../app/views/message_page_not_initialized.dart';
import '../../core/models.dart';
import '../../core/state.dart';
import '../../exception/no_data_exception.dart';
import '../../management/models.dart';
@ -44,6 +45,14 @@ import 'key_actions.dart';
import 'pin_dialog.dart';
import 'pin_entry_form.dart';
List<Capability> _getCapabilities(YubiKeyData deviceData) => [
Capability.fido2,
if (deviceData.info.config.enabledCapabilities[Transport.usb]! &
Capability.piv.value !=
0)
Capability.piv
];
class FingerprintsScreen extends ConsumerWidget {
final YubiKeyData deviceData;
@ -52,10 +61,11 @@ class FingerprintsScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!;
final capabilities = _getCapabilities(deviceData);
return ref.watch(fidoStateProvider(deviceData.node.path)).when(
loading: () => AppPage(
title: l10n.s_fingerprints,
capabilities: const [Capability.fido2],
capabilities: capabilities,
centered: true,
delayedContent: true,
builder: (context, _) => const CircularProgressIndicator(),
@ -64,7 +74,7 @@ class FingerprintsScreen extends ConsumerWidget {
if (error is NoDataException) {
return MessagePageNotInitialized(
title: l10n.s_fingerprints,
capabilities: const [Capability.fido2],
capabilities: capabilities,
);
}
final enabled = deviceData
@ -73,7 +83,7 @@ class FingerprintsScreen extends ConsumerWidget {
if (Capability.fido2.value & enabled == 0) {
return MessagePage(
title: l10n.s_fingerprints,
capabilities: const [Capability.fido2],
capabilities: capabilities,
header: l10n.s_fido_disabled,
message: l10n.l_webauthn_req_fido2,
);
@ -85,17 +95,17 @@ class FingerprintsScreen extends ConsumerWidget {
},
data: (fidoState) {
return fidoState.unlocked
? _FidoUnlockedPage(deviceData.node, fidoState)
: _FidoLockedPage(deviceData.node, fidoState);
? _FidoUnlockedPage(deviceData, fidoState)
: _FidoLockedPage(deviceData, fidoState);
});
}
}
class _FidoLockedPage extends ConsumerWidget {
final DeviceNode node;
final YubiKeyData deviceData;
final FidoState state;
const _FidoLockedPage(this.node, this.state);
const _FidoLockedPage(this.deviceData, this.state);
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -103,6 +113,14 @@ class _FidoLockedPage extends ConsumerWidget {
final hasFeature = ref.watch(featureProvider);
final hasActions = hasFeature(features.actions);
final capabilities = [
Capability.fido2,
if (deviceData.info.config.enabledCapabilities[Transport.usb]! &
Capability.piv.value !=
0)
Capability.piv
];
if (!state.hasPin) {
return MessagePage(
actionsBuilder: (context, expanded) => [
@ -112,13 +130,14 @@ class _FidoLockedPage extends ConsumerWidget {
onPressed: () async {
await showBlurDialog(
context: context,
builder: (context) => FidoPinDialog(node.path, state));
builder: (context) =>
FidoPinDialog(deviceData.node.path, state));
},
avatar: const Icon(Symbols.pin),
)
],
title: l10n.s_fingerprints,
capabilities: const [Capability.fido2],
capabilities: capabilities,
header: l10n.s_fingerprints_get_started,
message: l10n.p_set_fingerprints_desc,
keyActionsBuilder: hasActions ? _buildActions : null,
@ -129,7 +148,7 @@ class _FidoLockedPage extends ConsumerWidget {
if (state.forcePinChange) {
return MessagePage(
title: l10n.s_fingerprints,
capabilities: const [Capability.fido2],
capabilities: capabilities,
header: l10n.s_pin_change_required,
message: l10n.l_pin_change_required_desc,
keyActionsBuilder: hasActions ? _buildActions : null,
@ -141,7 +160,8 @@ class _FidoLockedPage extends ConsumerWidget {
onPressed: () async {
await showBlurDialog(
context: context,
builder: (context) => FidoPinDialog(node.path, state));
builder: (context) =>
FidoPinDialog(deviceData.node.path, state));
},
avatar: const Icon(Symbols.pin),
)
@ -151,25 +171,26 @@ class _FidoLockedPage extends ConsumerWidget {
return AppPage(
title: l10n.s_fingerprints,
capabilities: const [Capability.fido2],
capabilities: capabilities,
keyActionsBuilder: hasActions ? _buildActions : null,
builder: (context, _) => Column(
children: [
PinEntryForm(state, node),
PinEntryForm(state, deviceData.node),
],
),
);
}
Widget _buildActions(BuildContext context) =>
fingerprintsBuildActions(context, node, state, -1);
fingerprintsBuildActions(context, deviceData.node, state, -1);
}
class _FidoUnlockedPage extends ConsumerStatefulWidget {
final DeviceNode node;
final YubiKeyData deviceData;
final FidoState state;
_FidoUnlockedPage(this.node, this.state) : super(key: ObjectKey(node.path));
_FidoUnlockedPage(this.deviceData, this.state)
: super(key: ObjectKey(deviceData.node.path));
@override
ConsumerState<ConsumerStatefulWidget> createState() =>
@ -184,10 +205,12 @@ class _FidoUnlockedPageState extends ConsumerState<_FidoUnlockedPage> {
final l10n = AppLocalizations.of(context)!;
final hasFeature = ref.watch(featureProvider);
final hasActions = hasFeature(features.actions);
final capabilities = _getCapabilities(widget.deviceData);
final data = ref.watch(fingerprintProvider(widget.node.path)).asData;
final data =
ref.watch(fingerprintProvider(widget.deviceData.node.path)).asData;
if (data == null) {
return _buildLoadingPage(context);
return _buildLoadingPage(context, capabilities);
}
final fingerprints = data.value;
if (fingerprints.isEmpty) {
@ -200,18 +223,18 @@ class _FidoUnlockedPageState extends ConsumerState<_FidoUnlockedPage> {
await showBlurDialog(
context: context,
builder: (context) =>
AddFingerprintDialog(widget.node.path));
AddFingerprintDialog(widget.deviceData.node.path));
},
avatar: const Icon(Symbols.fingerprint),
)
],
title: l10n.s_fingerprints,
capabilities: const [Capability.fido2],
capabilities: capabilities,
header: l10n.s_fingerprints_get_started,
message: l10n.l_add_one_or_more_fps,
keyActionsBuilder: hasActions
? (context) =>
fingerprintsBuildActions(context, widget.node, widget.state, 0)
? (context) => fingerprintsBuildActions(
context, widget.deviceData.node, widget.state, 0)
: null,
keyActionsBadge: fingerprintsShowActionsNotifier(widget.state),
);
@ -219,7 +242,7 @@ class _FidoUnlockedPageState extends ConsumerState<_FidoUnlockedPage> {
final fingerprint = _selected;
return FidoActions(
devicePath: widget.node.path,
devicePath: widget.deviceData.node.path,
actions: (context) => {
EscapeIntent: CallbackAction<EscapeIntent>(onInvoke: (intent) {
if (_selected != null) {
@ -266,7 +289,7 @@ class _FidoUnlockedPageState extends ConsumerState<_FidoUnlockedPage> {
},
builder: (context) => AppPage(
title: l10n.s_fingerprints,
capabilities: const [Capability.fido2],
capabilities: capabilities,
detailViewBuilder: fingerprint != null
? (context) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
@ -306,8 +329,8 @@ class _FidoUnlockedPageState extends ConsumerState<_FidoUnlockedPage> {
)
: null,
keyActionsBuilder: hasActions
? (context) => fingerprintsBuildActions(
context, widget.node, widget.state, fingerprints.length)
? (context) => fingerprintsBuildActions(context,
widget.deviceData.node, widget.state, fingerprints.length)
: null,
keyActionsBadge: fingerprintsShowActionsNotifier(widget.state),
builder: (context, expanded) {
@ -331,24 +354,29 @@ class _FidoUnlockedPageState extends ConsumerState<_FidoUnlockedPage> {
}),
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: fingerprints
.map((fp) => _FingerprintListItem(
fp,
expanded: expanded,
selected: fp == _selected,
))
.toList()),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: fingerprints
.map((fp) => _FingerprintListItem(
fp,
expanded: expanded,
selected: fp == _selected,
))
.toList()),
),
);
},
),
);
}
Widget _buildLoadingPage(BuildContext context) => AppPage(
Widget _buildLoadingPage(
BuildContext context, List<Capability> capabilities) =>
AppPage(
title: AppLocalizations.of(context)!.s_fingerprints,
capabilities: const [Capability.fido2],
capabilities: capabilities,
centered: true,
delayedContent: true,
builder: (context, _) => const CircularProgressIndicator(),

View File

@ -25,6 +25,7 @@ import '../features.dart' as features;
import '../keys.dart' as keys;
import '../models.dart';
import 'add_fingerprint_dialog.dart';
import 'enterprise_attestation_dialog.dart';
import 'pin_dialog.dart';
bool passkeysShowActionsNotifier(FidoState state) {
@ -50,6 +51,14 @@ Widget _fidoBuildActions(BuildContext context, DeviceNode node, FidoState state,
Theme.of(context).colorScheme;
final authBlocked = state.pinBlocked;
final enterpriseAttestation = state.enterpriseAttestation;
final showEnterpriseAttestation = enterpriseAttestation != null &&
!(state.alwaysUv && !state.hasPin) &&
!(!state.unlocked && state.hasPin) &&
fingerprints == null;
final canEnableEnterpriseAttestation =
enterpriseAttestation == false && showEnterpriseAttestation;
return Column(
children: [
if (fingerprints != null)
@ -115,8 +124,27 @@ Widget _fidoBuildActions(BuildContext context, DeviceNode node, FidoState state,
}
: null,
),
if (showEnterpriseAttestation)
ActionListItem(
key: keys.enableEnterpriseAttestation,
feature: features.enableEnterpriseAttestation,
icon: const Icon(Symbols.local_police),
title: l10n.s_ep_attestation,
subtitle:
enterpriseAttestation ? l10n.s_enabled : l10n.s_disabled,
onTap: canEnableEnterpriseAttestation
? (context) {
Navigator.of(context).popUntil((route) => route.isFirst);
showBlurDialog(
context: context,
builder: (context) =>
EnableEnterpriseAttestationDialog(node.path),
);
}
: null,
)
],
)
),
],
);
}

View File

@ -38,6 +38,7 @@ import '../../exception/no_data_exception.dart';
import '../../management/models.dart';
import '../../widgets/app_input_decoration.dart';
import '../../widgets/app_text_form_field.dart';
import '../../widgets/flex_box.dart';
import '../../widgets/list_title.dart';
import '../features.dart' as features;
import '../models.dart';
@ -219,6 +220,7 @@ class _FidoUnlockedPageState extends ConsumerState<_FidoUnlockedPage> {
late FocusNode searchFocus;
late TextEditingController searchController;
FidoCredential? _selected;
bool _canRequestFocus = true;
@override
void initState() {
@ -374,60 +376,103 @@ class _FidoUnlockedPageState extends ConsumerState<_FidoUnlockedPage> {
}
return KeyEventResult.ignored;
},
child: Builder(builder: (context) {
child: LayoutBuilder(builder: (context, constraints) {
final textTheme = Theme.of(context).textTheme;
return Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: AppTextFormField(
key: searchField,
controller: searchController,
focusNode: searchFocus,
// Use the default style, but with a smaller font size:
style: textTheme.titleMedium
?.copyWith(fontSize: textTheme.titleSmall?.fontSize),
decoration: AppInputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(48),
borderSide: BorderSide(
width: 0,
style: searchFocus.hasFocus
? BorderStyle.solid
: BorderStyle.none,
final width = constraints.maxWidth;
final showLayoutOptions = width > 600;
return Consumer(
builder: (context, ref, child) {
final layout = ref.watch(passkeysLayoutProvider);
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10.0, vertical: 8.0),
child: AppTextFormField(
key: searchField,
controller: searchController,
canRequestFocus: _canRequestFocus,
focusNode: searchFocus,
// Use the default style, but with a smaller font size:
style: textTheme.titleMedium
?.copyWith(fontSize: textTheme.titleSmall?.fontSize),
decoration: AppInputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(48),
borderSide: BorderSide(
width: 0,
style: searchFocus.hasFocus
? BorderStyle.solid
: BorderStyle.none,
),
),
contentPadding: const EdgeInsets.all(16),
fillColor: Theme.of(context).hoverColor,
filled: true,
hintText: l10n.s_search_passkeys,
isDense: true,
prefixIcon: const Padding(
padding: EdgeInsetsDirectional.only(start: 8.0),
child: Icon(Icons.search_outlined),
),
suffixIcons: [
if (searchController.text.isNotEmpty)
IconButton(
icon: const Icon(Icons.clear),
iconSize: 16,
onPressed: () {
searchController.clear();
ref
.read(passkeysSearchProvider.notifier)
.setFilter('');
setState(() {});
},
),
if (searchController.text.isEmpty && showLayoutOptions)
...FlexLayout.values.map(
(e) => MouseRegion(
onEnter: (event) {
if (!searchFocus.hasFocus) {
setState(() {
_canRequestFocus = false;
});
}
},
onExit: (event) {
setState(() {
_canRequestFocus = true;
});
},
child: IconButton(
tooltip: e.getDisplayName(l10n),
onPressed: () {
ref
.read(passkeysLayoutProvider.notifier)
.setLayout(e);
},
icon: Icon(
e.icon,
color: e == layout
? Theme.of(context).colorScheme.primary
: null,
),
),
),
),
],
),
),
contentPadding: const EdgeInsets.all(16),
fillColor: Theme.of(context).hoverColor,
filled: true,
hintText: l10n.s_search_passkeys,
isDense: true,
prefixIcon: const Padding(
padding: EdgeInsetsDirectional.only(start: 8.0),
child: Icon(Icons.search_outlined),
),
suffixIcon: searchController.text.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
iconSize: 16,
onPressed: () {
searchController.clear();
ref
.read(passkeysSearchProvider.notifier)
.setFilter('');
setState(() {});
},
)
: null,
),
onChanged: (value) {
ref.read(passkeysSearchProvider.notifier).setFilter(value);
setState(() {});
},
textInputAction: TextInputAction.next,
onFieldSubmitted: (value) {
Focus.of(context).focusInDirection(TraversalDirection.down);
},
).init(),
onChanged: (value) {
ref
.read(passkeysSearchProvider.notifier)
.setFilter(value);
setState(() {});
},
textInputAction: TextInputAction.next,
onFieldSubmitted: (value) {
Focus.of(context)
.focusInDirection(TraversalDirection.down);
},
).init(),
);
},
);
}),
),
@ -483,21 +528,37 @@ class _FidoUnlockedPageState extends ConsumerState<_FidoUnlockedPage> {
}),
}
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (filteredCredentials.isEmpty)
Center(
child: Text(l10n.s_no_passkeys),
child: Consumer(
builder: (context, ref, child) {
final layout = ref.watch(passkeysLayoutProvider);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (filteredCredentials.isEmpty)
Center(
child: Text(l10n.s_no_passkeys),
),
FlexBox<FidoCredential>(
items: filteredCredentials,
itemBuilder: (cred) => _CredentialListItem(
cred,
expanded: expanded,
selected: _selected == cred,
tileColor: layout == FlexLayout.grid
? Theme.of(context).hoverColor
: null,
),
layout: layout,
cellMinWidth: 265,
spacing: layout == FlexLayout.grid ? 4.0 : 0.0,
runSpacing: layout == FlexLayout.grid ? 4.0 : 0.0,
)
],
),
...filteredCredentials.map(
(cred) => _CredentialListItem(
cred,
expanded: expanded,
selected: _selected == cred,
),
),
],
);
},
),
);
},
@ -518,9 +579,10 @@ class _CredentialListItem extends StatelessWidget {
final FidoCredential credential;
final bool selected;
final bool expanded;
final Color? tileColor;
const _CredentialListItem(this.credential,
{required this.expanded, required this.selected});
{required this.expanded, required this.selected, this.tileColor});
@override
Widget build(BuildContext context) {
@ -533,6 +595,7 @@ class _CredentialListItem extends StatelessWidget {
backgroundColor: colorScheme.secondary,
child: const Icon(Symbols.passkey),
),
tileColor: tileColor,
title: credential.rpId,
subtitle: credential.userName,
trailing: expanded

View File

@ -52,6 +52,7 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
final _currentPinFocus = FocusNode();
final _newPinController = TextEditingController();
final _newPinFocus = FocusNode();
final _confirmPinFocus = FocusNode();
String _confirmPin = '';
String? _currentPinError;
String? _newPinError;
@ -68,6 +69,7 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
_currentPinFocus.dispose();
_newPinController.dispose();
_newPinFocus.dispose();
_confirmPinFocus.dispose();
super.dispose();
}
@ -86,6 +88,9 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
final isValid =
currentPinLenOk && newPinLenOk && _newPinController.text == _confirmPin;
final newPinEnabled = !_isBlocked && currentPinLenOk;
final confirmPinEnabled = !_isBlocked && currentPinLenOk && newPinLenOk;
final deviceData = ref.read(currentDeviceDataProvider).valueOrNull;
final hasPinComplexity = deviceData?.info.pinComplexity ?? false;
@ -148,11 +153,19 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
_isObscureCurrent ? l10n.s_show_pin : l10n.s_hide_pin,
),
),
textInputAction: TextInputAction.next,
onChanged: (value) {
setState(() {
_currentIsWrong = false;
});
},
onFieldSubmitted: (_) {
if (_currentPinController.text.length < minPinLength) {
_currentPinFocus.requestFocus();
} else {
_newPinFocus.requestFocus();
}
},
).init(),
],
Text(hasPinComplexity
@ -172,31 +185,43 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
decoration: AppInputDecoration(
border: const OutlineInputBorder(),
labelText: l10n.s_new_pin,
enabled: !_isBlocked && currentPinLenOk,
enabled: newPinEnabled,
errorText: _newIsWrong ? _newPinError : null,
errorMaxLines: 3,
prefixIcon: const Icon(Symbols.pin),
suffixIcon: IconButton(
icon: Icon(_isObscureNew
? Symbols.visibility
: Symbols.visibility_off),
onPressed: () {
setState(() {
_isObscureNew = !_isObscureNew;
});
},
tooltip: _isObscureNew ? l10n.s_show_pin : l10n.s_hide_pin,
suffixIcon: ExcludeFocusTraversal(
excluding: !newPinEnabled,
child: IconButton(
icon: Icon(_isObscureNew
? Symbols.visibility
: Symbols.visibility_off),
onPressed: () {
setState(() {
_isObscureNew = !_isObscureNew;
});
},
tooltip: _isObscureNew ? l10n.s_show_pin : l10n.s_hide_pin,
),
),
),
textInputAction: TextInputAction.next,
onChanged: (value) {
setState(() {
_newIsWrong = false;
});
},
onFieldSubmitted: (_) {
if (_newPinController.text.length < minPinLength) {
_newPinFocus.requestFocus();
} else {
_confirmPinFocus.requestFocus();
}
},
).init(),
AppTextFormField(
key: confirmPin,
initialValue: _confirmPin,
focusNode: _confirmPinFocus,
maxLength: maxPinLength,
inputFormatters: [limitBytesLength(maxPinLength)],
buildCounter: buildByteCounterFor(_confirmPin),
@ -206,19 +231,22 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
border: const OutlineInputBorder(),
labelText: l10n.s_confirm_pin,
prefixIcon: const Icon(Symbols.pin),
suffixIcon: IconButton(
icon: Icon(_isObscureConfirm
? Symbols.visibility
: Symbols.visibility_off),
onPressed: () {
setState(() {
_isObscureConfirm = !_isObscureConfirm;
});
},
tooltip:
_isObscureConfirm ? l10n.s_show_pin : l10n.s_hide_pin,
suffixIcon: ExcludeFocusTraversal(
excluding: !confirmPinEnabled,
child: IconButton(
icon: Icon(_isObscureConfirm
? Symbols.visibility
: Symbols.visibility_off),
onPressed: () {
setState(() {
_isObscureConfirm = !_isObscureConfirm;
});
},
tooltip:
_isObscureConfirm ? l10n.s_show_pin : l10n.s_hide_pin,
),
),
enabled: !_isBlocked && currentPinLenOk && newPinLenOk,
enabled: confirmPinEnabled,
errorText:
_newPinController.text.length == _confirmPin.length &&
_newPinController.text != _confirmPin
@ -226,6 +254,7 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
: null,
helperText: '', // Prevents resizing when errorText shown
),
textInputAction: TextInputAction.done,
onChanged: (value) {
setState(() {
_confirmPin = value;
@ -234,6 +263,8 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
onFieldSubmitted: (_) {
if (isValid) {
_submit();
} else {
_confirmPinFocus.requestFocus();
}
},
).init(),

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2022-2023 Yubico.
* Copyright (C) 2022-2024 Yubico.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -41,12 +41,20 @@ class RenameFingerprintDialog extends ConsumerStatefulWidget {
class _RenameAccountDialogState extends ConsumerState<RenameFingerprintDialog> {
late String _label;
late FocusNode _labelFocus;
_RenameAccountDialogState();
@override
void initState() {
super.initState();
_label = widget.fingerprint.name ?? '';
_labelFocus = FocusNode();
}
@override
void dispose() {
_labelFocus.dispose();
super.dispose();
}
_submit() async {
@ -93,7 +101,9 @@ class _RenameAccountDialogState extends ConsumerState<RenameFingerprintDialog> {
Text(l10n.q_rename_target(widget.fingerprint.label)),
Text(l10n.p_will_change_label_fp),
AppTextFormField(
autofocus: true,
initialValue: _label,
focusNode: _labelFocus,
maxLength: 15,
inputFormatters: [limitBytesLength(15)],
buildCounter: buildByteCounterFor(_label),
@ -110,6 +120,8 @@ class _RenameAccountDialogState extends ConsumerState<RenameFingerprintDialog> {
onFieldSubmitted: (_) {
if (_label.isNotEmpty) {
_submit();
} else {
_labelFocus.requestFocus();
}
},
).init(),

View File

@ -29,7 +29,6 @@ import '../../app/views/app_page.dart';
import '../../core/models.dart';
import '../../core/state.dart';
import '../../management/models.dart';
import '../../widgets/choice_filter_chip.dart';
import '../../widgets/product_image.dart';
import 'key_actions.dart';
import 'manage_label_dialog.dart';
@ -72,7 +71,7 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
homeBuildActions(context, widget.deviceData, ref),
builder: (context, expanded) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
padding: const EdgeInsets.symmetric(horizontal: 18.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -92,16 +91,14 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
runSpacing: 8,
children: Capability.values
.where((c) => enabledCapabilities & c.value != 0)
.map((c) => CapabilityBadge(c))
.map((c) => CapabilityBadge(c, noTooltip: true))
.toList(),
),
if (serial != null) ...[
const SizedBox(height: 32.0),
_DeviceColor(
deviceData: widget.deviceData,
initialCustomization: keyCustomization ??
KeyCustomization(serial: serial))
]
if (widget.deviceData.info.fipsCapable != 0)
Padding(
padding: const EdgeInsets.only(top: 38),
child: _FipsLegend(),
),
],
),
),
@ -131,6 +128,62 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
}
}
class _FipsLegend extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Opacity(
opacity: 0.6,
child: Wrap(
runSpacing: 0,
spacing: 16,
children: [
RichText(
text: TextSpan(
children: [
const WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Padding(
padding: EdgeInsets.only(right: 4),
child: Icon(
Symbols.shield,
size: 12,
fill: 0.0,
),
),
),
TextSpan(
text: l10n.l_fips_capable,
style: Theme.of(context).textTheme.bodySmall),
],
),
),
RichText(
text: TextSpan(
children: [
const WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Padding(
padding: EdgeInsets.only(right: 4),
child: Icon(
Symbols.shield,
size: 12,
fill: 1.0,
),
),
),
TextSpan(
text: l10n.l_fips_approved,
style: Theme.of(context).textTheme.bodySmall),
],
),
),
],
),
);
}
}
class _DeviceContent extends ConsumerWidget {
final YubiKeyData deviceData;
final KeyCustomization? initialCustomization;
@ -147,6 +200,9 @@ class _DeviceContent extends ConsumerWidget {
final label = initialCustomization?.name;
String displayName = label != null ? '$label ($name)' : name;
final defaultColor = ref.watch(defaultColorProvider);
final customColor = initialCustomization?.color;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -158,22 +214,114 @@ class _DeviceContent extends ConsumerWidget {
style: Theme.of(context).textTheme.titleLarge,
),
),
if (serial != null)
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: IconButton(
icon: const Icon(Symbols.edit),
onPressed: () async {
await ref.read(withContextProvider)((context) async {
await _showManageLabelDialog(
initialCustomization ??
KeyCustomization(serial: serial),
context,
);
});
},
),
if (serial != null) ...[
const SizedBox(width: 8),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IconButton(
icon: const Icon(Symbols.edit),
tooltip: l10n.s_set_label,
color: Theme.of(context).colorScheme.onSurfaceVariant,
onPressed: () async {
await ref.read(withContextProvider)((context) async {
await _showManageLabelDialog(
initialCustomization ??
KeyCustomization(serial: serial),
context,
);
});
},
),
Column(
children: [
PopupMenuButton(
popUpAnimationStyle:
AnimationStyle(duration: Duration.zero),
menuPadding: EdgeInsets.zero,
tooltip: l10n.s_set_color,
itemBuilder: (context) {
return [
PopupMenuItem(
child: Center(
child: Wrap(
runSpacing: 8,
spacing: 16,
children: [
...[
Colors.teal,
Colors.cyan,
Colors.blueAccent,
Colors.deepPurple,
Colors.red,
Colors.orange,
Colors.yellow,
// add nice color to devices with dynamic color
if (isAndroid &&
ref.read(androidSdkVersionProvider) >=
31)
Colors.lightGreen
].map((e) => _ColorButton(
color: e,
isSelected:
customColor?.value == e.value,
onPressed: () {
_updateColor(e, ref, serial);
Navigator.of(context).pop();
},
)),
// "use default color" button
RawMaterialButton(
onPressed: () {
_updateColor(null, ref, serial);
Navigator.of(context).pop();
},
constraints: const BoxConstraints(
minWidth: 26.0, minHeight: 26.0),
fillColor: defaultColor,
hoverColor: Colors.black12,
shape: const CircleBorder(),
child: Icon(
customColor == null
? Symbols.circle
: Symbols.clear,
fill: 1,
size: 16,
weight: 700,
opticalSize: 20,
color: defaultColor
.computeLuminance() >
0.7
? Colors.grey // for bright colors
: Colors.white),
),
],
),
),
)
];
},
icon: Icon(
Symbols.palette,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
Container(
height: 3.0,
width: 24.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
color: Theme.of(context)
.colorScheme
.primary
.withOpacity(0.9)),
)
],
),
],
)
]
],
),
const SizedBox(
@ -195,10 +343,42 @@ class _DeviceContent extends ConsumerWidget {
.titleSmall
?.apply(color: Theme.of(context).colorScheme.onSurfaceVariant),
),
if (deviceData.info.pinComplexity)
Padding(
padding: const EdgeInsets.only(top: 12),
child: RichText(
text: TextSpan(children: [
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Padding(
padding: const EdgeInsets.only(right: 4),
child: Icon(
Symbols.check,
size: 16,
color: Theme.of(context).colorScheme.primary,
),
)),
TextSpan(
text: l10n.l_pin_complexity,
style: Theme.of(context).textTheme.titleSmall?.apply(
color: Theme.of(context).colorScheme.onSurfaceVariant),
),
]),
),
),
],
);
}
void _updateColor(Color? color, WidgetRef ref, int serial) async {
final manager = ref.read(keyCustomizationManagerProvider.notifier);
await manager.set(
serial: serial,
name: initialCustomization?.name,
color: color,
);
}
Future<void> _showManageLabelDialog(
KeyCustomization keyCustomization, BuildContext context) async {
await showBlurDialog(
@ -210,97 +390,6 @@ class _DeviceContent extends ConsumerWidget {
}
}
class _DeviceColor extends ConsumerWidget {
final YubiKeyData deviceData;
final KeyCustomization initialCustomization;
const _DeviceColor(
{required this.deviceData, required this.initialCustomization});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!;
final defaultColor = ref.watch(defaultColorProvider);
final customColor = initialCustomization.color;
return ChoiceFilterChip<Color?>(
disableHover: true,
value: customColor,
items: const [null],
selected: customColor != null && customColor.value != defaultColor.value,
itemBuilder: (e) => Wrap(
alignment: WrapAlignment.center,
runSpacing: 8,
spacing: 16,
children: [
...[
Colors.teal,
Colors.cyan,
Colors.blueAccent,
Colors.deepPurple,
Colors.red,
Colors.orange,
Colors.yellow,
// add nice color to devices with dynamic color
if (isAndroid && ref.read(androidSdkVersionProvider) >= 31)
Colors.lightGreen
].map((e) => _ColorButton(
color: e,
isSelected: customColor?.value == e.value,
onPressed: () {
_updateColor(e, ref);
Navigator.of(context).pop();
},
)),
// "use default color" button
RawMaterialButton(
onPressed: () {
_updateColor(null, ref);
Navigator.of(context).pop();
},
constraints: const BoxConstraints(minWidth: 26.0, minHeight: 26.0),
fillColor: defaultColor,
hoverColor: Colors.black12,
shape: const CircleBorder(),
child: Icon(customColor == null ? Symbols.circle : Symbols.clear,
fill: 1,
size: 16,
weight: 700,
opticalSize: 20,
color: defaultColor.computeLuminance() > 0.7
? Colors.grey // for bright colors
: Colors.white),
),
],
),
labelBuilder: (e) => Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
constraints: const BoxConstraints(minWidth: 22.0, minHeight: 22.0),
decoration: BoxDecoration(
color: customColor ?? defaultColor, shape: BoxShape.circle),
),
const SizedBox(
width: 12,
),
Flexible(child: Text(l10n.s_color))
],
),
onChanged: (e) {},
);
}
void _updateColor(Color? color, WidgetRef ref) async {
final manager = ref.read(keyCustomizationManagerProvider.notifier);
await manager.set(
serial: initialCustomization.serial,
name: initialCustomization.name,
color: color,
);
}
}
class _ColorButton extends StatefulWidget {
final Color? color;
final bool isSelected;

View File

@ -34,6 +34,7 @@ Widget homeBuildActions(
BuildContext context, YubiKeyData? deviceData, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!;
final hasFeature = ref.watch(featureProvider);
final interfacesLocked = deviceData?.info.resetBlocked != 0;
final managementAvailability = hasFeature(features.management) &&
switch (deviceData?.info.version) {
Version version => (version.major > 4 || // YK5 and up
@ -56,16 +57,21 @@ Widget homeBuildActions(
title: deviceData.info.version.major > 4
? l10n.s_toggle_applications
: l10n.s_toggle_interfaces,
subtitle: deviceData.info.version.major > 4
? l10n.l_toggle_applications_desc
: l10n.l_toggle_interfaces_desc,
onTap: (context) {
Navigator.of(context).popUntil((route) => route.isFirst);
showBlurDialog(
context: context,
builder: (context) => ManagementScreen(deviceData),
);
},
subtitle: interfacesLocked
? l10n.l_factory_reset_required
: (deviceData.info.version.major > 4
? l10n.l_toggle_applications_desc
: l10n.l_toggle_interfaces_desc),
onTap: interfacesLocked
? null
: (context) {
Navigator.of(context)
.popUntil((route) => route.isFirst);
showBlurDialog(
context: context,
builder: (context) => ManagementScreen(deviceData),
);
},
),
if (getResetCapabilities(hasFeature).any((c) =>
c.value &

View File

@ -30,6 +30,9 @@
"s_delete": "Löschen",
"s_move": null,
"s_quit": "Beenden",
"s_enable": null,
"s_enabled": null,
"s_disabled": null,
"s_status": null,
"s_unlock": "Entsperren",
"s_calculate": "Berechnen",
@ -45,6 +48,8 @@
"s_hide_window": "Fenster verstecken",
"s_expand_navigation": null,
"s_collapse_navigation": null,
"s_expand_sidebar": null,
"s_collapse_sidebar": null,
"q_rename_target": "{label} umbenennen?",
"@q_rename_target": {
"placeholders": {
@ -121,6 +126,12 @@
"s_light_mode": "Heller Modus",
"s_dark_mode": "Dunkler Modus",
"@_layout": {},
"s_list_layout": null,
"s_grid_layout": null,
"s_mixed_layout": null,
"s_select_layout": null,
"@_yubikey_selection": {},
"s_select_to_scan": "Zum Scannen auswählen",
"s_hide_device": "Gerät verstecken",
@ -149,6 +160,8 @@
}
},
"l_firmware_version": null,
"l_fips_capable": null,
"l_fips_approved": null,
"@_yubikey_interactions": {},
"l_insert_yk": "YubiKey anschließen",
@ -301,6 +314,10 @@
"common_pin": {}
}
},
"s_ep_attestation": null,
"s_ep_attestation_enabled": null,
"s_enable_ep_attestation": null,
"p_enable_ep_attestation_desc": null,
"s_pin_required": null,
"p_pin_required_desc": null,
"l_piv_pin_blocked": null,
@ -330,6 +347,7 @@
"l_warning_default_puk": null,
"l_default_pin_used": null,
"l_default_puk_used": null,
"l_pin_complexity": null,
"@_passwords": {},
"s_password": "Passwort",
@ -339,6 +357,7 @@
"s_show_password": null,
"s_hide_password": null,
"l_optional_password_protection": "Optionaler Passwortschutz",
"l_password_protection": null,
"s_new_password": "Neues Passwort",
"s_current_password": "Aktuelles Passwort",
"s_confirm_password": "Passwort bestätigen",
@ -352,6 +371,7 @@
"l_keystore_unavailable": "Passwortspeicher des Betriebssystems nicht verfügbar",
"l_remember_pw_failed": "Konnte Passwort nicht speichern",
"l_unlock_first": "Zuerst mit Passwort entsperren",
"l_set_password_first": null,
"l_enter_oath_pw": "Das OATH-Passwort für Ihren YubiKey eingeben",
"p_enter_current_password_or_reset": "Geben Sie Ihr aktuelles Passwort ein. Wenn Sie Ihr Passwort nicht wissen, müssen Sie den YubiKey zurücksetzen.",
"p_enter_new_password": "Geben Sie Ihr neues Passwort ein. Ein Passwort kann Buchstaben, Ziffern und spezielle Zeichen enthalten.",
@ -410,6 +430,7 @@
"s_pin_account": "Konto anpinnen",
"s_unpin_account": "Konto nicht mehr anpinnen",
"s_no_pinned_accounts": "Keine angepinnten Konten",
"s_pinned": null,
"l_pin_account_desc": null,
"s_rename_account": "Konto umbenennen",
"l_rename_account_desc": null,
@ -635,7 +656,9 @@
"p_subject_desc": null,
"l_rfc4514_invalid": null,
"rfc4514_examples": null,
"s_allow_fingerprint": null,
"p_cert_options_desc": null,
"p_cert_options_bio_desc": null,
"s_overwrite_slot": null,
"p_overwrite_slot_desc": null,
"@p_overwrite_slot_desc": {
@ -778,6 +801,7 @@
"s_reset": "Zurücksetzen",
"s_factory_reset": "Werkseinstellungen",
"l_factory_reset_desc": null,
"l_factory_reset_required": null,
"l_oath_application_reset": "OATH Anwendung zurücksetzen",
"l_fido_app_reset": "FIDO Anwendung zurückgesetzt",
"l_reset_failed": "Fehler beim Zurücksetzen: {message}",
@ -885,6 +909,7 @@
"@_key_customization": {},
"s_set_label": null,
"s_set_color": null,
"s_change_label": null,
"s_color": null,
"p_set_will_add_custom_name": null,

View File

@ -30,6 +30,9 @@
"s_delete": "Delete",
"s_move": "Move",
"s_quit": "Quit",
"s_enable": "Enable",
"s_enabled": "Enabled",
"s_disabled": "Disabled",
"s_status": "Status",
"s_unlock": "Unlock",
"s_calculate": "Calculate",
@ -45,6 +48,8 @@
"s_hide_window": "Hide window",
"s_expand_navigation": "Expand navigation",
"s_collapse_navigation": "Collapse navigation",
"s_expand_sidebar": "Expand sidebar",
"s_collapse_sidebar": "Collapse sidebar",
"q_rename_target": "Rename {label}?",
"@q_rename_target": {
"placeholders": {
@ -121,6 +126,12 @@
"s_light_mode": "Light mode",
"s_dark_mode": "Dark mode",
"@_layout": {},
"s_list_layout": "List layout",
"s_grid_layout": "Grid layout",
"s_mixed_layout": "Mixed layout",
"s_select_layout": "Select layout",
"@_yubikey_selection": {},
"s_select_to_scan": "Select to scan",
"s_hide_device": "Hide device",
@ -149,6 +160,8 @@
}
},
"l_firmware_version": "Firmware version: {version}",
"l_fips_capable": "FIPS capable",
"l_fips_approved": "FIPS approved",
"@_yubikey_interactions": {},
"l_insert_yk": "Insert your YubiKey",
@ -301,6 +314,10 @@
"common_pin": {}
}
},
"s_ep_attestation": "Enterprise Attestation",
"s_ep_attestation_enabled": "Enterprise Attestation enabled",
"s_enable_ep_attestation": "Enable Enterprise Attestation",
"p_enable_ep_attestation_desc": "This will enable Enterprise Attestation, allowing authorized domains to uniquely identify your YubiKey.",
"s_pin_required": "PIN required",
"p_pin_required_desc": "The action you are about to perform requires the PIV PIN to be entered.",
"l_piv_pin_blocked": "Blocked, use PUK to reset",
@ -330,6 +347,7 @@
"l_warning_default_puk": "Warning: Default PUK used",
"l_default_pin_used": "Default PIN used",
"l_default_puk_used": "Default PUK used",
"l_pin_complexity": "PIN complexity enforced",
"@_passwords": {},
"s_password": "Password",
@ -339,6 +357,7 @@
"s_show_password": "Show password",
"s_hide_password": "Hide password",
"l_optional_password_protection": "Optional password protection",
"l_password_protection": "Password protection of accounts",
"s_new_password": "New password",
"s_current_password": "Current password",
"s_confirm_password": "Confirm password",
@ -352,6 +371,7 @@
"l_keystore_unavailable": "OS Keystore unavailable",
"l_remember_pw_failed": "Failed to remember password",
"l_unlock_first": "Unlock with password first",
"l_set_password_first": "Set a password first",
"l_enter_oath_pw": "Enter the OATH password for your YubiKey",
"p_enter_current_password_or_reset": "Enter your current password. If you don't know your password, you'll need to reset the YubiKey.",
"p_enter_new_password": "Enter your new password. A password may contain letters, numbers and special characters.",
@ -410,6 +430,7 @@
"s_pin_account": "Pin account",
"s_unpin_account": "Unpin account",
"s_no_pinned_accounts": "No pinned accounts",
"s_pinned": "Pinned",
"l_pin_account_desc": "Keep your important accounts together",
"s_rename_account": "Rename account",
"l_rename_account_desc": "Edit the issuer/name of the account",
@ -635,7 +656,9 @@
"p_subject_desc": "A distinguished name (DN) formatted in accordance to the RFC 4514 specification.",
"l_rfc4514_invalid": "Invalid RFC 4514 format",
"rfc4514_examples": "Examples:\nCN=Example Name\nCN=jsmith,DC=example,DC=net",
"s_allow_fingerprint": "Allow fingerprint",
"p_cert_options_desc": "Key algorithm to use, output format, and expiration date (certificate only).",
"p_cert_options_bio_desc": "Key algorithm to use, output format, expiration date (certificate only), and if biometrics can be used instead of PIN.",
"s_overwrite_slot": "Overwrite slot",
"p_overwrite_slot_desc": "This will permanently overwrite existing content in slot {slot}.",
"@p_overwrite_slot_desc": {
@ -778,6 +801,7 @@
"s_reset": "Reset",
"s_factory_reset": "Factory reset",
"l_factory_reset_desc": "Restore YubiKey defaults",
"l_factory_reset_required": "Factory reset required",
"l_oath_application_reset": "OATH application reset",
"l_fido_app_reset": "FIDO application reset",
"l_reset_failed": "Error performing reset: {message}",
@ -885,6 +909,7 @@
"@_key_customization": {},
"s_set_label": "Set label",
"s_set_color": "Set color",
"s_change_label": "Change label",
"s_color": "Color",
"p_set_will_add_custom_name": "This will give your YubiKey a custom name.",

View File

@ -30,6 +30,9 @@
"s_delete": "Supprimer",
"s_move": "Déplacer",
"s_quit": "Quitter",
"s_enable": null,
"s_enabled": null,
"s_disabled": null,
"s_status": "État",
"s_unlock": "Déverrouiller",
"s_calculate": "Calculer",
@ -45,6 +48,8 @@
"s_hide_window": "Masquer fenêtre",
"s_expand_navigation": "Développer la navigation",
"s_collapse_navigation": "Réduire la navigation",
"s_expand_sidebar": null,
"s_collapse_sidebar": null,
"q_rename_target": "Renommer {label}\u00a0?",
"@q_rename_target": {
"placeholders": {
@ -121,6 +126,12 @@
"s_light_mode": "Thème clair",
"s_dark_mode": "Thème sombre",
"@_layout": {},
"s_list_layout": null,
"s_grid_layout": null,
"s_mixed_layout": null,
"s_select_layout": null,
"@_yubikey_selection": {},
"s_select_to_scan": "Sélectionner pour scanner",
"s_hide_device": "Masquer appareil",
@ -149,6 +160,8 @@
}
},
"l_firmware_version": "Version du firmware : {version}",
"l_fips_capable": null,
"l_fips_approved": null,
"@_yubikey_interactions": {},
"l_insert_yk": "Insérez votre YubiKey",
@ -301,6 +314,10 @@
"common_pin": {}
}
},
"s_ep_attestation": null,
"s_ep_attestation_enabled": null,
"s_enable_ep_attestation": null,
"p_enable_ep_attestation_desc": null,
"s_pin_required": "PIN requis",
"p_pin_required_desc": "L'action que vous allez effectuer nécessite la saisie du PIN PIV.",
"l_piv_pin_blocked": "Bloqué, utilisez PUK pour réinitialiser",
@ -330,6 +347,7 @@
"l_warning_default_puk": "Attention : PUK par défaut utilisé",
"l_default_pin_used": "Code PIN par défaut utilisé",
"l_default_puk_used": "PUK par défaut utilisé",
"l_pin_complexity": null,
"@_passwords": {},
"s_password": "Mot de passe",
@ -339,6 +357,7 @@
"s_show_password": "Montrer mot de passe",
"s_hide_password": "Masquer mot de passe",
"l_optional_password_protection": "Protection par mot de passe facultative",
"l_password_protection": null,
"s_new_password": "Nouveau mot de passe",
"s_current_password": "Mot de passe actuel",
"s_confirm_password": "Confirmer mot de passe",
@ -352,6 +371,7 @@
"l_keystore_unavailable": "OS Keystore indisponible",
"l_remember_pw_failed": "Mémorisation mot de passe impossible",
"l_unlock_first": "Débloquez d'abord avec mot de passe",
"l_set_password_first": null,
"l_enter_oath_pw": "Saisissez le mot de passe OATH de votre YubiKey",
"p_enter_current_password_or_reset": "Saisissez votre mot de passe actuel. Vous ne connaissez votre mot de passe\u00a0? Réinitialisez la YubiKey.",
"p_enter_new_password": "Saisissez votre nouveau mot de passe. Un mot de passe peut inclure des lettres, chiffres et caractères spéciaux.",
@ -410,6 +430,7 @@
"s_pin_account": "Épingler compte",
"s_unpin_account": "Détacher compte",
"s_no_pinned_accounts": "Aucun compte épinglé",
"s_pinned": null,
"l_pin_account_desc": "Conserver vos comptes importants ensemble",
"s_rename_account": "Renommer compte",
"l_rename_account_desc": "Modifier émetteur/nom du compte",
@ -635,7 +656,9 @@
"p_subject_desc": "DN (nom distinctif) formaté conformément à la spécification RFC 4514.",
"l_rfc4514_invalid": "Format RFC 4514 non valide",
"rfc4514_examples": "Exemples\u00a0:\nCN=exemple de nom\nCN=jsmith,DC=exemple,DC=net",
"s_allow_fingerprint": null,
"p_cert_options_desc": "Algorithme clé à utiliser, format de sortie et date d'expiration (certificat uniquement).",
"p_cert_options_bio_desc": null,
"s_overwrite_slot": "Écraser slot",
"p_overwrite_slot_desc": "Cela écrasera définitivement le contenu du slot {slot}.",
"@p_overwrite_slot_desc": {
@ -778,6 +801,7 @@
"s_reset": "Réinitialiser",
"s_factory_reset": "Réinitialisation usine",
"l_factory_reset_desc": "Restaurer les paramètres par défaut de la YubiKey",
"l_factory_reset_required": null,
"l_oath_application_reset": "Réinitialisation OATH",
"l_fido_app_reset": "Réinitialisation FIDO",
"l_reset_failed": "Erreur de réinitialisation\u00a0: {message}",
@ -885,6 +909,7 @@
"@_key_customization": {},
"s_set_label": "Définir l'étiquette",
"s_set_color": null,
"s_change_label": "Modifier l'étiquette",
"s_color": "Couleur",
"p_set_will_add_custom_name": "Cela donnera un nom personnalisé à votre YubiKey.",

View File

@ -30,6 +30,9 @@
"s_delete": "削除",
"s_move": "移動",
"s_quit": "終了",
"s_enable": null,
"s_enabled": null,
"s_disabled": null,
"s_status": "ステータス",
"s_unlock": "ロック解除",
"s_calculate": "計算",
@ -45,6 +48,8 @@
"s_hide_window": "ウィンドウを非表示",
"s_expand_navigation": "ナビゲーションを展開",
"s_collapse_navigation": "ナビゲーションを閉じる",
"s_expand_sidebar": null,
"s_collapse_sidebar": null,
"q_rename_target": "{label}の名前を変更しますか?",
"@q_rename_target": {
"placeholders": {
@ -121,6 +126,12 @@
"s_light_mode": "ライトモード",
"s_dark_mode": "ダークモード",
"@_layout": {},
"s_list_layout": null,
"s_grid_layout": null,
"s_mixed_layout": null,
"s_select_layout": null,
"@_yubikey_selection": {},
"s_select_to_scan": "選択してスキャン",
"s_hide_device": "デバイスを非表示",
@ -149,6 +160,8 @@
}
},
"l_firmware_version": "ファームウェアバージョン: {version}",
"l_fips_capable": null,
"l_fips_approved": null,
"@_yubikey_interactions": {},
"l_insert_yk": "YubiKeyを挿入してください",
@ -301,6 +314,10 @@
"common_pin": {}
}
},
"s_ep_attestation": null,
"s_ep_attestation_enabled": null,
"s_enable_ep_attestation": null,
"p_enable_ep_attestation_desc": null,
"s_pin_required": "PINが必要",
"p_pin_required_desc": "実行しようとしているアクションでは、PIV PINを入力する必要があります。",
"l_piv_pin_blocked": "ブロックされています。リセットするにはPUKを使用してください",
@ -330,6 +347,7 @@
"l_warning_default_puk": "警告: デフォルトのPUKが使用されています",
"l_default_pin_used": "デフォルトのPINが使用されています",
"l_default_puk_used": "既定のPUKを使用",
"l_pin_complexity": null,
"@_passwords": {},
"s_password": "パスワード",
@ -339,6 +357,7 @@
"s_show_password": "パスワードを表示",
"s_hide_password": "パスワードを非表示",
"l_optional_password_protection": "オプションのパスワード保護",
"l_password_protection": null,
"s_new_password": "新しいパスワード",
"s_current_password": "現在のパスワード",
"s_confirm_password": "パスワードを確認",
@ -352,6 +371,7 @@
"l_keystore_unavailable": "OSのキーストアを利用できません",
"l_remember_pw_failed": "パスワードを記憶できませんでした",
"l_unlock_first": "最初にパスワードでロックを解除",
"l_set_password_first": null,
"l_enter_oath_pw": "YubiKeyのOATHパスワードを入力",
"p_enter_current_password_or_reset": "現在のパスワードを入力してください。パスワードがわからない場合は、YubiKeyをリセットする必要があります。",
"p_enter_new_password": "新しいパスワードを入力してください。パスワードには文字、数字、特殊文字を含めることができます。",
@ -410,6 +430,7 @@
"s_pin_account": "アカウントをピン留めする",
"s_unpin_account": "アカウントのピン留めを解除",
"s_no_pinned_accounts": "ピン留めされたアカウントはありません",
"s_pinned": null,
"l_pin_account_desc": "重要なアカウントをまとめて保持",
"s_rename_account": "アカウント名を変更",
"l_rename_account_desc": "アカウントの発行者/名前を編集",
@ -635,7 +656,9 @@
"p_subject_desc": "RFC 4514仕様に準拠した形式の識別名DN。",
"l_rfc4514_invalid": "無効なRFC 4514形式",
"rfc4514_examples": "例:\nCN=Example Name CN=jsmith,DC=example,\nDC=net",
"s_allow_fingerprint": null,
"p_cert_options_desc": "使用する鍵アルゴリズム、出力形式、および有効期限(証明書のみ)。",
"p_cert_options_bio_desc": null,
"s_overwrite_slot": "スロットを上書き",
"p_overwrite_slot_desc": "これにより、スロット{slot}内の既存コンテンツが完全に上書きされます。",
"@p_overwrite_slot_desc": {
@ -778,6 +801,7 @@
"s_reset": "リセット",
"s_factory_reset": "工場出荷時の状態にリセット",
"l_factory_reset_desc": "YubiKey の既定値を復元",
"l_factory_reset_required": null,
"l_oath_application_reset": "OATHアプリケーションのリセット",
"l_fido_app_reset": "FIDOアプリケーションのリセット",
"l_reset_failed": "リセットの実行エラー:{message}",
@ -885,6 +909,7 @@
"@_key_customization": {},
"s_set_label": "ラベルを設定",
"s_set_color": null,
"s_change_label": "ラベルを変更",
"s_color": "色",
"p_set_will_add_custom_name": "これにより、YubiKey にカスタム名を付けることができます。",

View File

@ -30,6 +30,9 @@
"s_delete": "Usuń",
"s_move": null,
"s_quit": "Wyjdź",
"s_enable": null,
"s_enabled": null,
"s_disabled": null,
"s_status": "Status",
"s_unlock": "Odblokuj",
"s_calculate": "Oblicz",
@ -45,6 +48,8 @@
"s_hide_window": "Ukryj okno",
"s_expand_navigation": null,
"s_collapse_navigation": null,
"s_expand_sidebar": null,
"s_collapse_sidebar": null,
"q_rename_target": "Zmienić nazwę {label}?",
"@q_rename_target": {
"placeholders": {
@ -121,6 +126,12 @@
"s_light_mode": "Jasny",
"s_dark_mode": "Ciemny",
"@_layout": {},
"s_list_layout": null,
"s_grid_layout": null,
"s_mixed_layout": null,
"s_select_layout": null,
"@_yubikey_selection": {},
"s_select_to_scan": "Wybierz, aby skanować",
"s_hide_device": "Ukryj urządzenie",
@ -149,6 +160,8 @@
}
},
"l_firmware_version": null,
"l_fips_capable": null,
"l_fips_approved": null,
"@_yubikey_interactions": {},
"l_insert_yk": "Podłącz klucz YubiKey",
@ -301,6 +314,10 @@
"common_pin": {}
}
},
"s_ep_attestation": null,
"s_ep_attestation_enabled": null,
"s_enable_ep_attestation": null,
"p_enable_ep_attestation_desc": null,
"s_pin_required": "Wymagany PIN",
"p_pin_required_desc": "Czynność, którą zamierzasz wykonać, wymaga wprowadzenia kodu PIN PIV.",
"l_piv_pin_blocked": "Zablokowano, użyj PUK, aby zresetować",
@ -330,6 +347,7 @@
"l_warning_default_puk": null,
"l_default_pin_used": null,
"l_default_puk_used": null,
"l_pin_complexity": null,
"@_passwords": {},
"s_password": "Hasło",
@ -339,6 +357,7 @@
"s_show_password": "Pokaż hasło",
"s_hide_password": "Ukryj hasło",
"l_optional_password_protection": "Opcjonalna ochrona hasłem",
"l_password_protection": null,
"s_new_password": "Nowe hasło",
"s_current_password": "Aktualne hasło",
"s_confirm_password": "Potwierdź hasło",
@ -352,6 +371,7 @@
"l_keystore_unavailable": "Magazyn kluczy systemu operacyjnego jest niedostępny",
"l_remember_pw_failed": "Nie udało się zapamiętać hasła",
"l_unlock_first": "Najpierw odblokuj hasłem",
"l_set_password_first": null,
"l_enter_oath_pw": "Wprowadź hasło OATH dla klucza YubiKey",
"p_enter_current_password_or_reset": "Wprowadź aktualne hasło. Jeśli go nie znasz, musisz zresetować klucz YubiKey.",
"p_enter_new_password": "Wprowadź nowe hasło. Może ono zawierać litery, cyfry i znaki specjalne.",
@ -410,6 +430,7 @@
"s_pin_account": "Przypnij konto",
"s_unpin_account": "Odepnij konto",
"s_no_pinned_accounts": "Brak przypiętych kont",
"s_pinned": null,
"l_pin_account_desc": "Przechowuj ważne konta razem",
"s_rename_account": "Zmień nazwę konta",
"l_rename_account_desc": "Edytuj wydawcę/nazwę konta",
@ -635,7 +656,9 @@
"p_subject_desc": "Nazwa wyróżniająca (DN) sformatowana zgodnie ze specyfikacją RFC 4514.",
"l_rfc4514_invalid": "Nieprawidłowy format RFC 4514",
"rfc4514_examples": "Przykłady:\nCN=Przykładowa Nazwa\nCN=jkowalski,DC=przyklad,DC=pl",
"s_allow_fingerprint": null,
"p_cert_options_desc": "Algorytm klucza do użycia, format wyjściowy i data wygaśnięcia (tylko certyfikat).",
"p_cert_options_bio_desc": null,
"s_overwrite_slot": "Nadpisz slot",
"p_overwrite_slot_desc": "Spowoduje to trwałe nadpisanie istniejącej zawartości w slocie {slot}.",
"@p_overwrite_slot_desc": {
@ -778,6 +801,7 @@
"s_reset": "Zresetuj",
"s_factory_reset": "Ustawienia fabryczne",
"l_factory_reset_desc": null,
"l_factory_reset_required": null,
"l_oath_application_reset": "Reset funkcji OATH",
"l_fido_app_reset": "Reset funkcji FIDO",
"l_reset_failed": "Błąd podczas resetowania: {message}",
@ -885,6 +909,7 @@
"@_key_customization": {},
"s_set_label": null,
"s_set_color": null,
"s_change_label": null,
"s_color": null,
"p_set_will_add_custom_name": null,

View File

@ -15,6 +15,7 @@
*/
import 'dart:developer' as developer;
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -27,6 +28,7 @@ import 'app/state.dart';
import 'core/state.dart';
import 'desktop/init.dart' as desktop;
import 'error_page.dart';
import 'version.dart';
final _log = Logger('main');
@ -43,6 +45,12 @@ void main(List<String> argv) async {
_initializeDebugLogging();
throw UnimplementedError('Platform not supported');
}
_log.info('Running Yubico Authenticator...', {
'app_version': version,
'dart': Platform.version,
'os': Platform.operatingSystem,
'os_version': Platform.operatingSystemVersion,
});
runApp(initializedApp);
} catch (e) {
_log.warning('Platform initialization failed: $e');

View File

@ -26,8 +26,12 @@ mixin _$DeviceConfig {
int? get challengeResponseTimeout => throw _privateConstructorUsedError;
int? get deviceFlags => throw _privateConstructorUsedError;
/// Serializes this DeviceConfig to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of DeviceConfig
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$DeviceConfigCopyWith<DeviceConfig> get copyWith =>
throw _privateConstructorUsedError;
}
@ -55,6 +59,8 @@ class _$DeviceConfigCopyWithImpl<$Res, $Val extends DeviceConfig>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of DeviceConfig
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -107,6 +113,8 @@ class __$$DeviceConfigImplCopyWithImpl<$Res>
_$DeviceConfigImpl _value, $Res Function(_$DeviceConfigImpl) _then)
: super(_value, _then);
/// Create a copy of DeviceConfig
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -183,7 +191,7 @@ class _$DeviceConfigImpl implements _DeviceConfig {
other.deviceFlags == deviceFlags));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
@ -192,7 +200,9 @@ class _$DeviceConfigImpl implements _DeviceConfig {
challengeResponseTimeout,
deviceFlags);
@JsonKey(ignore: true)
/// Create a copy of DeviceConfig
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$DeviceConfigImplCopyWith<_$DeviceConfigImpl> get copyWith =>
@ -224,8 +234,11 @@ abstract class _DeviceConfig implements DeviceConfig {
int? get challengeResponseTimeout;
@override
int? get deviceFlags;
/// Create a copy of DeviceConfig
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$DeviceConfigImplCopyWith<_$DeviceConfigImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -250,8 +263,12 @@ mixin _$DeviceInfo {
int get fipsApproved => throw _privateConstructorUsedError;
int get resetBlocked => throw _privateConstructorUsedError;
/// Serializes this DeviceInfo to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of DeviceInfo
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$DeviceInfoCopyWith<DeviceInfo> get copyWith =>
throw _privateConstructorUsedError;
}
@ -290,6 +307,8 @@ class _$DeviceInfoCopyWithImpl<$Res, $Val extends DeviceInfo>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of DeviceInfo
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -358,6 +377,8 @@ class _$DeviceInfoCopyWithImpl<$Res, $Val extends DeviceInfo>
) as $Val);
}
/// Create a copy of DeviceInfo
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$DeviceConfigCopyWith<$Res> get config {
@ -366,6 +387,8 @@ class _$DeviceInfoCopyWithImpl<$Res, $Val extends DeviceInfo>
});
}
/// Create a copy of DeviceInfo
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$VersionCopyWith<$Res> get version {
@ -411,6 +434,8 @@ class __$$DeviceInfoImplCopyWithImpl<$Res>
_$DeviceInfoImpl _value, $Res Function(_$DeviceInfoImpl) _then)
: super(_value, _then);
/// Create a copy of DeviceInfo
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -565,7 +590,7 @@ class _$DeviceInfoImpl extends _DeviceInfo {
other.resetBlocked == resetBlocked));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
@ -582,7 +607,9 @@ class _$DeviceInfoImpl extends _DeviceInfo {
fipsApproved,
resetBlocked);
@JsonKey(ignore: true)
/// Create a copy of DeviceInfo
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$DeviceInfoImplCopyWith<_$DeviceInfoImpl> get copyWith =>
@ -639,8 +666,11 @@ abstract class _DeviceInfo extends DeviceInfo {
int get fipsApproved;
@override
int get resetBlocked;
/// Create a copy of DeviceInfo
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$DeviceInfoImplCopyWith<_$DeviceInfoImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -258,3 +258,5 @@ class CredentialData with _$CredentialData {
},
);
}
enum OathLayout { list, grid, mixed }

View File

@ -29,8 +29,12 @@ mixin _$OathCredential {
int get period => throw _privateConstructorUsedError;
bool get touchRequired => throw _privateConstructorUsedError;
/// Serializes this OathCredential to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of OathCredential
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$OathCredentialCopyWith<OathCredential> get copyWith =>
throw _privateConstructorUsedError;
}
@ -61,6 +65,8 @@ class _$OathCredentialCopyWithImpl<$Res, $Val extends OathCredential>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of OathCredential
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -131,6 +137,8 @@ class __$$OathCredentialImplCopyWithImpl<$Res>
_$OathCredentialImpl _value, $Res Function(_$OathCredentialImpl) _then)
: super(_value, _then);
/// Create a copy of OathCredential
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -222,12 +230,14 @@ class _$OathCredentialImpl implements _OathCredential {
other.touchRequired == touchRequired));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType, deviceId, id, issuer, name, oathType, period, touchRequired);
@JsonKey(ignore: true)
/// Create a copy of OathCredential
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$OathCredentialImplCopyWith<_$OathCredentialImpl> get copyWith =>
@ -270,8 +280,11 @@ abstract class _OathCredential implements OathCredential {
int get period;
@override
bool get touchRequired;
/// Create a copy of OathCredential
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$OathCredentialImplCopyWith<_$OathCredentialImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -286,8 +299,12 @@ mixin _$OathCode {
int get validFrom => throw _privateConstructorUsedError;
int get validTo => throw _privateConstructorUsedError;
/// Serializes this OathCode to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of OathCode
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$OathCodeCopyWith<OathCode> get copyWith =>
throw _privateConstructorUsedError;
}
@ -310,6 +327,8 @@ class _$OathCodeCopyWithImpl<$Res, $Val extends OathCode>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of OathCode
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -353,6 +372,8 @@ class __$$OathCodeImplCopyWithImpl<$Res>
_$OathCodeImpl _value, $Res Function(_$OathCodeImpl) _then)
: super(_value, _then);
/// Create a copy of OathCode
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -408,11 +429,13 @@ class _$OathCodeImpl implements _OathCode {
(identical(other.validTo, validTo) || other.validTo == validTo));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, value, validFrom, validTo);
@JsonKey(ignore: true)
/// Create a copy of OathCode
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$OathCodeImplCopyWith<_$OathCodeImpl> get copyWith =>
@ -440,8 +463,11 @@ abstract class _OathCode implements OathCode {
int get validFrom;
@override
int get validTo;
/// Create a copy of OathCode
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$OathCodeImplCopyWith<_$OathCodeImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -455,8 +481,12 @@ mixin _$OathPair {
OathCredential get credential => throw _privateConstructorUsedError;
OathCode? get code => throw _privateConstructorUsedError;
/// Serializes this OathPair to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of OathPair
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$OathPairCopyWith<OathPair> get copyWith =>
throw _privateConstructorUsedError;
}
@ -482,6 +512,8 @@ class _$OathPairCopyWithImpl<$Res, $Val extends OathPair>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of OathPair
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -500,6 +532,8 @@ class _$OathPairCopyWithImpl<$Res, $Val extends OathPair>
) as $Val);
}
/// Create a copy of OathPair
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$OathCredentialCopyWith<$Res> get credential {
@ -508,6 +542,8 @@ class _$OathPairCopyWithImpl<$Res, $Val extends OathPair>
});
}
/// Create a copy of OathPair
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$OathCodeCopyWith<$Res>? get code {
@ -545,6 +581,8 @@ class __$$OathPairImplCopyWithImpl<$Res>
_$OathPairImpl _value, $Res Function(_$OathPairImpl) _then)
: super(_value, _then);
/// Create a copy of OathPair
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -592,11 +630,13 @@ class _$OathPairImpl implements _OathPair {
(identical(other.code, code) || other.code == code));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, credential, code);
@JsonKey(ignore: true)
/// Create a copy of OathPair
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$OathPairImplCopyWith<_$OathPairImpl> get copyWith =>
@ -621,8 +661,11 @@ abstract class _OathPair implements OathPair {
OathCredential get credential;
@override
OathCode? get code;
/// Create a copy of OathPair
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$OathPairImplCopyWith<_$OathPairImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -640,8 +683,12 @@ mixin _$OathState {
bool get locked => throw _privateConstructorUsedError;
KeystoreState get keystore => throw _privateConstructorUsedError;
/// Serializes this OathState to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of OathState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$OathStateCopyWith<OathState> get copyWith =>
throw _privateConstructorUsedError;
}
@ -672,6 +719,8 @@ class _$OathStateCopyWithImpl<$Res, $Val extends OathState>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of OathState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -710,6 +759,8 @@ class _$OathStateCopyWithImpl<$Res, $Val extends OathState>
) as $Val);
}
/// Create a copy of OathState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$VersionCopyWith<$Res> get version {
@ -747,6 +798,8 @@ class __$$OathStateImplCopyWithImpl<$Res>
_$OathStateImpl _value, $Res Function(_$OathStateImpl) _then)
: super(_value, _then);
/// Create a copy of OathState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -833,12 +886,14 @@ class _$OathStateImpl extends _OathState {
other.keystore == keystore));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType, deviceId, version, hasKey, remembered, locked, keystore);
@JsonKey(ignore: true)
/// Create a copy of OathState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$OathStateImplCopyWith<_$OathStateImpl> get copyWith =>
@ -875,8 +930,11 @@ abstract class _OathState extends OathState {
bool get locked;
@override
KeystoreState get keystore;
/// Create a copy of OathState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$OathStateImplCopyWith<_$OathStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -896,8 +954,12 @@ mixin _$CredentialData {
int get period => throw _privateConstructorUsedError;
int get counter => throw _privateConstructorUsedError;
/// Serializes this CredentialData to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of CredentialData
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$CredentialDataCopyWith<CredentialData> get copyWith =>
throw _privateConstructorUsedError;
}
@ -929,6 +991,8 @@ class _$CredentialDataCopyWithImpl<$Res, $Val extends CredentialData>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of CredentialData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1005,6 +1069,8 @@ class __$$CredentialDataImplCopyWithImpl<$Res>
_$CredentialDataImpl _value, $Res Function(_$CredentialDataImpl) _then)
: super(_value, _then);
/// Create a copy of CredentialData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1115,12 +1181,14 @@ class _$CredentialDataImpl extends _CredentialData {
(identical(other.counter, counter) || other.counter == counter));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, issuer, name, secret, oathType,
hashAlgorithm, digits, period, counter);
@JsonKey(ignore: true)
/// Create a copy of CredentialData
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$CredentialDataImplCopyWith<_$CredentialDataImpl> get copyWith =>
@ -1166,8 +1234,11 @@ abstract class _CredentialData extends CredentialData {
int get period;
@override
int get counter;
/// Create a copy of CredentialData
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$CredentialDataImplCopyWith<_$CredentialDataImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -38,6 +38,53 @@ class AccountsSearchNotifier extends StateNotifier<String> {
}
}
final oathLayoutProvider =
StateNotifierProvider.autoDispose<OathLayoutNotfier, OathLayout>((ref) {
final device = ref.watch(currentDeviceProvider);
List<OathPair> credentials = device != null
? ref.read(filteredCredentialsProvider(
ref.read(credentialListProvider(device.path)) ?? []))
: [];
final favorites = ref.watch(favoritesProvider);
final pinnedCreds =
credentials.where((entry) => favorites.contains(entry.credential.id));
return OathLayoutNotfier('OATH_STATE_LAYOUT', ref.watch(prefProvider),
credentials, pinnedCreds.toList());
});
class OathLayoutNotfier extends StateNotifier<OathLayout> {
final String _key;
final SharedPreferences _prefs;
OathLayoutNotfier(this._key, this._prefs, List<OathPair> credentials,
List<OathPair> pinnedCredentials)
: super(
_fromName(_prefs.getString(_key), credentials, pinnedCredentials));
void setLayout(OathLayout layout) {
state = layout;
_prefs.setString(_key, layout.name);
}
static OathLayout _fromName(String? name, List<OathPair> credentials,
List<OathPair> pinnedCredentials) {
final layout = OathLayout.values.firstWhere(
(element) => element.name == name,
orElse: () => OathLayout.list,
);
// Default to list view if current key does not have
// pinned credentials
if (layout == OathLayout.mixed) {
if (pinnedCredentials.isEmpty) {
return OathLayout.list;
}
if (pinnedCredentials.length == credentials.length) {
return OathLayout.grid;
}
}
return layout;
}
}
final oathStateProvider = AsyncNotifierProvider.autoDispose
.family<OathStateNotifier, OathState, DevicePath>(
() => throw UnimplementedError(),

View File

@ -26,7 +26,6 @@ import '../../app/shortcuts.dart';
import '../../app/state.dart';
import '../../core/state.dart';
import '../../widgets/circle_timer.dart';
import '../../widgets/custom_icons.dart';
import '../features.dart' as features;
import '../keys.dart' as keys;
import '../models.dart';
@ -90,7 +89,7 @@ class AccountHelper {
ActionItem(
key: keys.togglePinAction,
feature: features.accountsPin,
icon: pinned ? pushPinStrokeIcon : const Icon(Symbols.push_pin),
icon: Icon(pinned ? Symbols.keep_off : Symbols.keep),
title: pinned ? l10n.s_unpin_account : l10n.s_pin_account,
subtitle: l10n.l_pin_account_desc,
intent: TogglePinIntent(credential),

View File

@ -18,6 +18,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../widgets/flex_box.dart';
import '../models.dart';
import '../state.dart';
import 'account_view.dart';
@ -32,6 +33,9 @@ class AccountList extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!;
final theme = Theme.of(context);
final labelStyle =
theme.textTheme.bodyMedium?.copyWith(color: theme.colorScheme.primary);
final credentials = ref.watch(filteredCredentialsProvider(accounts));
final favorites = ref.watch(favoritesProvider);
if (credentials.isEmpty) {
@ -45,32 +49,71 @@ class AccountList extends ConsumerWidget {
final creds =
credentials.where((entry) => !favorites.contains(entry.credential.id));
final oathLayout = ref.watch(oathLayoutProvider);
final pinnedLayout =
(oathLayout == OathLayout.grid || oathLayout == OathLayout.mixed)
? FlexLayout.grid
: FlexLayout.list;
final normalLayout =
oathLayout == OathLayout.grid ? FlexLayout.grid : FlexLayout.list;
return FocusTraversalGroup(
policy: WidgetOrderTraversalPolicy(),
child: Column(
children: [
...pinnedCreds.map(
(entry) => AccountView(
entry.credential,
expanded: expanded,
selected: entry.credential == selected,
),
),
if (pinnedCreds.isNotEmpty && creds.isNotEmpty)
child: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (pinnedCreds.isNotEmpty) ...[
Padding(
padding: const EdgeInsets.only(left: 18, bottom: 8),
child: Text(l10n.s_pinned, style: labelStyle),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: FlexBox<OathPair>(
items: pinnedCreds.toList(),
itemBuilder: (value) => AccountView(
value.credential,
expanded: expanded,
selected: value.credential == selected,
large: pinnedLayout == FlexLayout.grid,
),
cellMinWidth: 250,
spacing: pinnedLayout == FlexLayout.grid ? 4.0 : 0.0,
runSpacing: pinnedLayout == FlexLayout.grid ? 4.0 : 0.0,
layout: pinnedLayout,
),
),
],
if (pinnedCreds.isNotEmpty && creds.isNotEmpty) ...[
const SizedBox(height: 24),
Padding(
padding: const EdgeInsets.only(left: 18, bottom: 8),
child: Text(
l10n.s_accounts,
style: labelStyle,
),
),
],
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Divider(
color: Theme.of(context).colorScheme.secondaryContainer,
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: FlexBox<OathPair>(
items: creds.toList(),
itemBuilder: (value) => AccountView(
value.credential,
expanded: expanded,
selected: value.credential == selected,
large: normalLayout == FlexLayout.grid,
),
cellMinWidth: 250,
spacing: normalLayout == FlexLayout.grid ? 4.0 : 0.0,
runSpacing: normalLayout == FlexLayout.grid ? 4.0 : 0.0,
layout: normalLayout,
),
),
...creds.map(
(entry) => AccountView(
entry.credential,
expanded: expanded,
selected: entry.credential == selected,
),
),
],
],
),
),
);
}

View File

@ -30,8 +30,12 @@ class AccountView extends ConsumerStatefulWidget {
final OathCredential credential;
final bool expanded;
final bool selected;
final bool large;
const AccountView(this.credential,
{super.key, required this.expanded, required this.selected});
{super.key,
required this.expanded,
required this.selected,
this.large = false});
@override
ConsumerState<ConsumerStatefulWidget> createState() => _AccountViewState();
@ -116,6 +120,95 @@ class _AccountViewState extends ConsumerState<AccountView> {
? CopyIntent<OathCredential>(credential)
: null,
buildPopupActions: (_) => helper.buildActions(),
itemBuilder: widget.large
? (context) {
return ListTile(
mouseCursor: !(isDesktop && !widget.expanded)
? SystemMouseCursors.click
: null,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16)),
selectedTileColor:
Theme.of(context).colorScheme.secondaryContainer,
selectedColor:
Theme.of(context).colorScheme.onSecondaryContainer,
selected: widget.selected,
tileColor: Theme.of(context).hoverColor,
contentPadding:
const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
title: Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AccountIcon(
issuer: credential.issuer,
defaultWidget: circleAvatar),
const SizedBox(width: 12),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
helper.title,
style: Theme.of(context)
.textTheme
.bodyLarge
?.copyWith(
color: Theme.of(context)
.colorScheme
.onSurface),
overflow: TextOverflow.fade,
maxLines: 1,
softWrap: false,
),
if (subtitle != null)
Text(
subtitle,
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(
color: Theme.of(context)
.colorScheme
.onSurfaceVariant),
overflow: TextOverflow.fade,
maxLines: 1,
softWrap: false,
)
],
),
)
],
),
const SizedBox(height: 8.0),
Focus(
skipTraversal: true,
descendantsAreTraversable: false,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
helper.code != null
? FilledButton.tonalIcon(
icon: helper.buildCodeIcon(),
label: helper.buildCodeLabel(),
style: buttonStyle,
onPressed:
Actions.handler(context, openIntent),
)
: FilledButton.tonal(
style: buttonStyle,
onPressed:
Actions.handler(context, openIntent),
child: helper.buildCodeIcon()),
],
),
),
],
),
);
}
: null,
);
}
}

View File

@ -23,6 +23,7 @@ import '../../app/message.dart';
import '../../app/models.dart';
import '../../app/state.dart';
import '../../app/views/action_list.dart';
import '../../management/models.dart';
import '../features.dart' as features;
import '../icon_provider/icon_pack_dialog.dart';
import '../keys.dart' as keys;
@ -39,6 +40,28 @@ Widget oathBuildActions(
}) {
final l10n = AppLocalizations.of(context)!;
final capacity = oathState.capacity;
final (fipsCapable, fipsApproved) = ref
.watch(currentDeviceDataProvider)
.valueOrNull
?.info
.getFipsStatus(Capability.oath) ??
(false, false);
final String? subtitle;
final bool enabled;
if (used == null) {
subtitle = l10n.l_unlock_first;
enabled = false;
} else if (fipsCapable & !fipsApproved) {
subtitle = l10n.l_set_password_first;
enabled = false;
} else if (capacity != null) {
subtitle = l10n.l_accounts_used(used, capacity);
enabled = capacity > used;
} else {
subtitle = null;
enabled = true;
}
return Column(
children: [
@ -47,14 +70,10 @@ Widget oathBuildActions(
feature: features.actionsAdd,
key: keys.addAccountAction,
title: l10n.s_add_account,
subtitle: used == null
? l10n.l_unlock_first
: (capacity != null
? l10n.l_accounts_used(used, capacity)
: null),
subtitle: subtitle,
actionStyle: ActionStyle.primary,
icon: const Icon(Symbols.person_add_alt),
onTap: used != null && (capacity == null || capacity > used)
onTap: enabled
? (context) async {
Navigator.of(context).popUntil((route) => route.isFirst);
await addOathAccount(context, ref, devicePath, oathState);
@ -82,7 +101,7 @@ Widget oathBuildActions(
feature: features.actionsPassword,
title:
oathState.hasKey ? l10n.s_manage_password : l10n.s_set_password,
subtitle: l10n.l_optional_password_protection,
subtitle: l10n.l_password_protection,
icon: const Icon(Symbols.password),
onTap: (context) {
Navigator.of(context).popUntil((route) => route.isFirst);

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2022-2023 Yubico.
* Copyright (C) 2022-2024 Yubico.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -34,6 +34,7 @@ import '../state.dart';
class ManagePasswordDialog extends ConsumerStatefulWidget {
final DevicePath path;
final OathState state;
const ManagePasswordDialog(this.path, this.state, {super.key});
@override
@ -44,6 +45,8 @@ class ManagePasswordDialog extends ConsumerStatefulWidget {
class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
final _currentPasswordController = TextEditingController();
final _currentPasswordFocus = FocusNode();
final _newPasswordFocus = FocusNode();
final _confirmPasswordFocus = FocusNode();
String _newPassword = '';
String _confirmPassword = '';
bool _currentIsWrong = false;
@ -55,6 +58,8 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
void dispose() {
_currentPasswordController.dispose();
_currentPasswordFocus.dispose();
_newPasswordFocus.dispose();
_confirmPasswordFocus.dispose();
super.dispose();
}
@ -92,6 +97,13 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
_newPassword == _confirmPassword &&
(!widget.state.hasKey || _currentPasswordController.text.isNotEmpty);
final newPasswordEnabled =
!widget.state.hasKey || _currentPasswordController.text.isNotEmpty;
final confirmPasswordEnabled =
(!widget.state.hasKey || _currentPasswordController.text.isNotEmpty) &&
_newPassword.isNotEmpty;
return ResponsiveDialog(
title: Text(
widget.state.hasKey ? l10n.s_manage_password : l10n.s_set_password),
@ -141,6 +153,13 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
_currentIsWrong = false;
});
},
onSubmitted: (_) {
if (_currentPasswordController.text.isEmpty) {
_currentPasswordFocus.requestFocus();
} else {
_newPasswordFocus.requestFocus();
}
},
).init(),
Wrap(
spacing: 4.0,
@ -204,24 +223,27 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
autofocus: !widget.state.hasKey,
obscureText: _isObscureNew,
autofillHints: const [AutofillHints.newPassword],
focusNode: _newPasswordFocus,
decoration: AppInputDecoration(
border: const OutlineInputBorder(),
labelText: l10n.s_new_password,
prefixIcon: const Icon(Symbols.password),
suffixIcon: IconButton(
icon: Icon(_isObscureNew
? Symbols.visibility
: Symbols.visibility_off),
onPressed: () {
setState(() {
_isObscureNew = !_isObscureNew;
});
},
tooltip: _isObscureNew
? l10n.s_show_password
: l10n.s_hide_password),
enabled: !widget.state.hasKey ||
_currentPasswordController.text.isNotEmpty,
suffixIcon: ExcludeFocusTraversal(
excluding: !newPasswordEnabled,
child: IconButton(
icon: Icon(_isObscureNew
? Symbols.visibility
: Symbols.visibility_off),
onPressed: () {
setState(() {
_isObscureNew = !_isObscureNew;
});
},
tooltip: _isObscureNew
? l10n.s_show_password
: l10n.s_hide_password),
),
enabled: newPasswordEnabled,
),
textInputAction: TextInputAction.next,
onChanged: (value) {
@ -230,34 +252,38 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
});
},
onSubmitted: (_) {
if (isValid) {
_submit();
if (_newPassword.isNotEmpty) {
_confirmPasswordFocus.requestFocus();
} else if (_newPassword.isEmpty) {
_newPasswordFocus.requestFocus();
}
},
).init(),
AppTextField(
key: keys.confirmPasswordField,
obscureText: _isObscureConfirm,
focusNode: _confirmPasswordFocus,
autofillHints: const [AutofillHints.newPassword],
decoration: AppInputDecoration(
border: const OutlineInputBorder(),
labelText: l10n.s_confirm_password,
prefixIcon: const Icon(Symbols.password),
suffixIcon: IconButton(
icon: Icon(_isObscureConfirm
? Symbols.visibility
: Symbols.visibility_off),
onPressed: () {
setState(() {
_isObscureConfirm = !_isObscureConfirm;
});
},
tooltip: _isObscureConfirm
? l10n.s_show_password
: l10n.s_hide_password),
enabled: (!widget.state.hasKey ||
_currentPasswordController.text.isNotEmpty) &&
_newPassword.isNotEmpty,
suffixIcon: ExcludeFocusTraversal(
excluding: !confirmPasswordEnabled,
child: IconButton(
icon: Icon(_isObscureConfirm
? Symbols.visibility
: Symbols.visibility_off),
onPressed: () {
setState(() {
_isObscureConfirm = !_isObscureConfirm;
});
},
tooltip: _isObscureConfirm
? l10n.s_show_password
: l10n.s_hide_password),
),
enabled: confirmPasswordEnabled,
errorText: _newPassword.length == _confirmPassword.length &&
_newPassword != _confirmPassword
? l10n.l_password_mismatch
@ -273,6 +299,8 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
onSubmitted: (_) {
if (isValid) {
_submit();
} else {
_confirmPasswordFocus.requestFocus();
}
},
).init(),

View File

@ -53,6 +53,19 @@ import 'key_actions.dart';
import 'unlock_form.dart';
import 'utils.dart';
extension on OathLayout {
IconData get _icon => switch (this) {
OathLayout.list => Symbols.list,
OathLayout.grid => Symbols.grid_view,
OathLayout.mixed => Symbols.vertical_split
};
String getDisplayName(AppLocalizations l10n) => switch (this) {
OathLayout.list => l10n.s_list_layout,
OathLayout.grid => l10n.s_grid_layout,
OathLayout.mixed => l10n.s_mixed_layout
};
}
class OathScreen extends ConsumerWidget {
final DevicePath devicePath;
@ -123,6 +136,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
late FocusNode searchFocus;
late TextEditingController searchController;
OathCredential? _selected;
bool _canRequestFocus = true;
@override
void initState() {
@ -376,60 +390,171 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
}
return KeyEventResult.ignored;
},
child: Builder(builder: (context) {
child: LayoutBuilder(builder: (context, constraints) {
final width = constraints.maxWidth;
final textTheme = Theme.of(context).textTheme;
return Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: AppTextFormField(
key: searchField,
controller: searchController,
focusNode: searchFocus,
// Use the default style, but with a smaller font size:
style: textTheme.titleMedium
?.copyWith(fontSize: textTheme.titleSmall?.fontSize),
decoration: AppInputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(48),
borderSide: BorderSide(
width: 0,
style: searchFocus.hasFocus
? BorderStyle.solid
: BorderStyle.none,
return Consumer(
builder: (context, ref, child) {
final credentials = ref.watch(filteredCredentialsProvider(
ref.watch(credentialListProvider(widget.devicePath)) ??
[]));
final favorites = ref.watch(favoritesProvider);
final pinnedCreds = credentials
.where((entry) => favorites.contains(entry.credential.id));
final availableLayouts = pinnedCreds.isEmpty ||
pinnedCreds.length == credentials.length
? OathLayout.values
.where((element) => element != OathLayout.mixed)
: OathLayout.values;
final oathLayout = ref.watch(oathLayoutProvider);
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10.0, vertical: 8.0),
child: AppTextFormField(
key: searchField,
controller: searchController,
canRequestFocus: _canRequestFocus,
focusNode: searchFocus,
// Use the default style, but with a smaller font size:
style: textTheme.titleMedium
?.copyWith(fontSize: textTheme.titleSmall?.fontSize),
decoration: AppInputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(48),
borderSide: BorderSide(
width: 0,
style: searchFocus.hasFocus
? BorderStyle.solid
: BorderStyle.none,
),
),
contentPadding: const EdgeInsets.all(16),
fillColor: Theme.of(context).hoverColor,
filled: true,
hintText: l10n.s_search_accounts,
isDense: true,
prefixIcon: const Padding(
padding: EdgeInsetsDirectional.only(start: 8.0),
child: Icon(Icons.search_outlined),
),
suffixIcons: [
if (searchController.text.isNotEmpty)
IconButton(
icon: const Icon(Icons.clear),
iconSize: 16,
onPressed: () {
searchController.clear();
ref
.read(accountsSearchProvider.notifier)
.setFilter('');
setState(() {});
},
),
if (searchController.text.isEmpty) ...[
if (width >= 450)
...availableLayouts.map(
(e) => MouseRegion(
onEnter: (event) {
if (!searchFocus.hasFocus) {
setState(() {
_canRequestFocus = false;
});
}
},
onExit: (event) {
setState(() {
_canRequestFocus = true;
});
},
child: IconButton(
tooltip: e.getDisplayName(l10n),
onPressed: () {
ref
.read(oathLayoutProvider.notifier)
.setLayout(e);
},
icon: Icon(
e._icon,
color: e == oathLayout
? Theme.of(context).colorScheme.primary
: null,
),
),
),
),
if (width < 450)
MouseRegion(
onEnter: (event) {
if (!searchFocus.hasFocus) {
setState(() {
_canRequestFocus = false;
});
}
},
onExit: (event) {
setState(() {
_canRequestFocus = true;
});
},
child: PopupMenuButton(
constraints: const BoxConstraints.tightFor(),
tooltip: 'Select layout',
popUpAnimationStyle:
AnimationStyle(duration: Duration.zero),
icon: Icon(
oathLayout._icon,
color: Theme.of(context).colorScheme.primary,
),
itemBuilder: (context) => [
...availableLayouts.map(
(e) => PopupMenuItem(
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Tooltip(
message: e.getDisplayName(l10n),
child: Icon(
e._icon,
color: e == oathLayout
? Theme.of(context)
.colorScheme
.primary
: null,
),
),
],
),
onTap: () {
ref
.read(oathLayoutProvider.notifier)
.setLayout(e);
},
),
)
],
),
)
]
],
),
),
contentPadding: const EdgeInsets.all(16),
fillColor: Theme.of(context).hoverColor,
filled: true,
hintText: l10n.s_search_accounts,
isDense: true,
prefixIcon: const Padding(
padding: EdgeInsetsDirectional.only(start: 8.0),
child: Icon(Icons.search_outlined),
),
suffixIcon: searchController.text.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
iconSize: 16,
onPressed: () {
searchController.clear();
ref
.read(accountsSearchProvider.notifier)
.setFilter('');
setState(() {});
},
)
: null,
),
onChanged: (value) {
ref.read(accountsSearchProvider.notifier).setFilter(value);
setState(() {});
},
textInputAction: TextInputAction.next,
onFieldSubmitted: (value) {
Focus.of(context).focusInDirection(TraversalDirection.down);
},
).init(),
onChanged: (value) {
ref
.read(accountsSearchProvider.notifier)
.setFilter(value);
setState(() {});
},
textInputAction: TextInputAction.next,
onFieldSubmitted: (value) {
Focus.of(context)
.focusInDirection(TraversalDirection.down);
},
).init(),
);
},
);
}),
),

View File

@ -23,8 +23,12 @@ mixin _$OtpState {
bool get slot1Configured => throw _privateConstructorUsedError;
bool get slot2Configured => throw _privateConstructorUsedError;
/// Serializes this OtpState to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of OtpState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$OtpStateCopyWith<OtpState> get copyWith =>
throw _privateConstructorUsedError;
}
@ -47,6 +51,8 @@ class _$OtpStateCopyWithImpl<$Res, $Val extends OtpState>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of OtpState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -85,6 +91,8 @@ class __$$OtpStateImplCopyWithImpl<$Res>
_$OtpStateImpl _value, $Res Function(_$OtpStateImpl) _then)
: super(_value, _then);
/// Create a copy of OtpState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -134,12 +142,14 @@ class _$OtpStateImpl extends _OtpState {
other.slot2Configured == slot2Configured));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode =>
Object.hash(runtimeType, slot1Configured, slot2Configured);
@JsonKey(ignore: true)
/// Create a copy of OtpState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$OtpStateImplCopyWith<_$OtpStateImpl> get copyWith =>
@ -166,8 +176,11 @@ abstract class _OtpState extends OtpState {
bool get slot1Configured;
@override
bool get slot2Configured;
/// Create a copy of OtpState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$OtpStateImplCopyWith<_$OtpStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -177,7 +190,9 @@ mixin _$OtpSlot {
SlotId get slot => throw _privateConstructorUsedError;
bool get isConfigured => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of OtpSlot
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$OtpSlotCopyWith<OtpSlot> get copyWith => throw _privateConstructorUsedError;
}
@ -199,6 +214,8 @@ class _$OtpSlotCopyWithImpl<$Res, $Val extends OtpSlot>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of OtpSlot
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -236,6 +253,8 @@ class __$$OtpSlotImplCopyWithImpl<$Res>
_$OtpSlotImpl _value, $Res Function(_$OtpSlotImpl) _then)
: super(_value, _then);
/// Create a copy of OtpSlot
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -283,7 +302,9 @@ class _$OtpSlotImpl implements _OtpSlot {
@override
int get hashCode => Object.hash(runtimeType, slot, isConfigured);
@JsonKey(ignore: true)
/// Create a copy of OtpSlot
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$OtpSlotImplCopyWith<_$OtpSlotImpl> get copyWith =>
@ -299,8 +320,11 @@ abstract class _OtpSlot implements OtpSlot {
SlotId get slot;
@override
bool get isConfigured;
/// Create a copy of OtpSlot
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$OtpSlotImplCopyWith<_$OtpSlotImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -316,8 +340,12 @@ mixin _$SlotConfigurationOptions {
bool? get requireTouch => throw _privateConstructorUsedError;
bool? get appendCr => throw _privateConstructorUsedError;
/// Serializes this SlotConfigurationOptions to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of SlotConfigurationOptions
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$SlotConfigurationOptionsCopyWith<SlotConfigurationOptions> get copyWith =>
throw _privateConstructorUsedError;
}
@ -342,6 +370,8 @@ class _$SlotConfigurationOptionsCopyWithImpl<$Res,
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of SlotConfigurationOptions
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -388,6 +418,8 @@ class __$$SlotConfigurationOptionsImplCopyWithImpl<$Res>
$Res Function(_$SlotConfigurationOptionsImpl) _then)
: super(_value, _then);
/// Create a copy of SlotConfigurationOptions
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -446,11 +478,13 @@ class _$SlotConfigurationOptionsImpl implements _SlotConfigurationOptions {
other.appendCr == appendCr));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, digits8, requireTouch, appendCr);
@JsonKey(ignore: true)
/// Create a copy of SlotConfigurationOptions
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SlotConfigurationOptionsImplCopyWith<_$SlotConfigurationOptionsImpl>
@ -480,8 +514,11 @@ abstract class _SlotConfigurationOptions implements SlotConfigurationOptions {
bool? get requireTouch;
@override
bool? get appendCr;
/// Create a copy of SlotConfigurationOptions
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SlotConfigurationOptionsImplCopyWith<_$SlotConfigurationOptionsImpl>
get copyWith => throw _privateConstructorUsedError;
}
@ -570,8 +607,13 @@ mixin _$SlotConfiguration {
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
/// Serializes this SlotConfiguration to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of SlotConfiguration
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$SlotConfigurationCopyWith<SlotConfiguration> get copyWith =>
throw _privateConstructorUsedError;
}
@ -597,6 +639,8 @@ class _$SlotConfigurationCopyWithImpl<$Res, $Val extends SlotConfiguration>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of SlotConfiguration
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -610,6 +654,8 @@ class _$SlotConfigurationCopyWithImpl<$Res, $Val extends SlotConfiguration>
) as $Val);
}
/// Create a copy of SlotConfiguration
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SlotConfigurationOptionsCopyWith<$Res>? get options {
@ -646,6 +692,8 @@ class __$$SlotConfigurationHotpImplCopyWithImpl<$Res>
$Res Function(_$SlotConfigurationHotpImpl) _then)
: super(_value, _then);
/// Create a copy of SlotConfiguration
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -699,11 +747,13 @@ class _$SlotConfigurationHotpImpl extends _SlotConfigurationHotp {
(identical(other.options, options) || other.options == options));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, key, options);
@JsonKey(ignore: true)
/// Create a copy of SlotConfiguration
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SlotConfigurationHotpImplCopyWith<_$SlotConfigurationHotpImpl>
@ -818,8 +868,11 @@ abstract class _SlotConfigurationHotp extends SlotConfiguration {
String get key;
@override
SlotConfigurationOptions? get options;
/// Create a copy of SlotConfiguration
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SlotConfigurationHotpImplCopyWith<_$SlotConfigurationHotpImpl>
get copyWith => throw _privateConstructorUsedError;
}
@ -849,6 +902,8 @@ class __$$SlotConfigurationHmacSha1ImplCopyWithImpl<$Res>
$Res Function(_$SlotConfigurationHmacSha1Impl) _then)
: super(_value, _then);
/// Create a copy of SlotConfiguration
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -902,11 +957,13 @@ class _$SlotConfigurationHmacSha1Impl extends _SlotConfigurationHmacSha1 {
(identical(other.options, options) || other.options == options));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, key, options);
@JsonKey(ignore: true)
/// Create a copy of SlotConfiguration
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SlotConfigurationHmacSha1ImplCopyWith<_$SlotConfigurationHmacSha1Impl>
@ -1022,8 +1079,11 @@ abstract class _SlotConfigurationHmacSha1 extends SlotConfiguration {
String get key;
@override
SlotConfigurationOptions? get options;
/// Create a copy of SlotConfiguration
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SlotConfigurationHmacSha1ImplCopyWith<_$SlotConfigurationHmacSha1Impl>
get copyWith => throw _privateConstructorUsedError;
}
@ -1056,6 +1116,8 @@ class __$$SlotConfigurationStaticPasswordImplCopyWithImpl<$Res>
$Res Function(_$SlotConfigurationStaticPasswordImpl) _then)
: super(_value, _then);
/// Create a copy of SlotConfiguration
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1124,12 +1186,14 @@ class _$SlotConfigurationStaticPasswordImpl
(identical(other.options, options) || other.options == options));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode =>
Object.hash(runtimeType, password, keyboardLayout, options);
@JsonKey(ignore: true)
/// Create a copy of SlotConfiguration
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SlotConfigurationStaticPasswordImplCopyWith<
@ -1248,8 +1312,11 @@ abstract class _SlotConfigurationStaticPassword extends SlotConfiguration {
String get keyboardLayout;
@override
SlotConfigurationOptions? get options;
/// Create a copy of SlotConfiguration
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SlotConfigurationStaticPasswordImplCopyWith<
_$SlotConfigurationStaticPasswordImpl>
get copyWith => throw _privateConstructorUsedError;
@ -1284,6 +1351,8 @@ class __$$SlotConfigurationYubiOtpImplCopyWithImpl<$Res>
$Res Function(_$SlotConfigurationYubiOtpImpl) _then)
: super(_value, _then);
/// Create a copy of SlotConfiguration
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1359,12 +1428,14 @@ class _$SlotConfigurationYubiOtpImpl extends _SlotConfigurationYubiOtp {
(identical(other.options, options) || other.options == options));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode =>
Object.hash(runtimeType, publicId, privateId, key, options);
@JsonKey(ignore: true)
/// Create a copy of SlotConfiguration
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SlotConfigurationYubiOtpImplCopyWith<_$SlotConfigurationYubiOtpImpl>
@ -1484,8 +1555,11 @@ abstract class _SlotConfigurationYubiOtp extends SlotConfiguration {
String get key;
@override
SlotConfigurationOptions? get options;
/// Create a copy of SlotConfiguration
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SlotConfigurationYubiOtpImplCopyWith<_$SlotConfigurationYubiOtpImpl>
get copyWith => throw _privateConstructorUsedError;
}

View File

@ -180,13 +180,17 @@ class _OtpScreenState extends ConsumerState<OtpScreen> {
return null;
}),
},
child: Column(children: [
...otpState.slots.map((e) => _SlotListItem(
e,
expanded: expanded,
selected: e == selected,
))
]),
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 10.0),
child: Column(children: [
...otpState.slots.map((e) => _SlotListItem(
e,
expanded: expanded,
selected: e == selected,
))
]),
),
);
},
),

View File

@ -270,6 +270,7 @@ class PivState with _$PivState {
required bool derivedKey,
required bool storedKey,
required int pinAttempts,
required bool supportsBio,
String? chuid,
String? ccc,
PivStateMetadata? metadata,

View File

@ -24,8 +24,12 @@ mixin _$PinMetadata {
int get totalAttempts => throw _privateConstructorUsedError;
int get attemptsRemaining => throw _privateConstructorUsedError;
/// Serializes this PinMetadata to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of PinMetadata
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$PinMetadataCopyWith<PinMetadata> get copyWith =>
throw _privateConstructorUsedError;
}
@ -49,6 +53,8 @@ class _$PinMetadataCopyWithImpl<$Res, $Val extends PinMetadata>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of PinMetadata
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -92,6 +98,8 @@ class __$$PinMetadataImplCopyWithImpl<$Res>
_$PinMetadataImpl _value, $Res Function(_$PinMetadataImpl) _then)
: super(_value, _then);
/// Create a copy of PinMetadata
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -150,12 +158,14 @@ class _$PinMetadataImpl implements _PinMetadata {
other.attemptsRemaining == attemptsRemaining));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode =>
Object.hash(runtimeType, defaultValue, totalAttempts, attemptsRemaining);
@JsonKey(ignore: true)
/// Create a copy of PinMetadata
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$PinMetadataImplCopyWith<_$PinMetadataImpl> get copyWith =>
@ -182,8 +192,11 @@ abstract class _PinMetadata implements PinMetadata {
int get totalAttempts;
@override
int get attemptsRemaining;
/// Create a copy of PinMetadata
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$PinMetadataImplCopyWith<_$PinMetadataImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -247,6 +260,9 @@ class _$PinVerificationStatusCopyWithImpl<$Res,
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of PinVerificationStatus
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
@ -263,6 +279,9 @@ class __$$PinSuccessImplCopyWithImpl<$Res>
__$$PinSuccessImplCopyWithImpl(
_$PinSuccessImpl _value, $Res Function(_$PinSuccessImpl) _then)
: super(_value, _then);
/// Create a copy of PinVerificationStatus
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
@ -370,6 +389,8 @@ class __$$PinFailureImplCopyWithImpl<$Res>
_$PinFailureImpl _value, $Res Function(_$PinFailureImpl) _then)
: super(_value, _then);
/// Create a copy of PinVerificationStatus
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -383,6 +404,8 @@ class __$$PinFailureImplCopyWithImpl<$Res>
));
}
/// Create a copy of PinVerificationStatus
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$PivPinFailureReasonCopyWith<$Res> get reason {
@ -416,7 +439,9 @@ class _$PinFailureImpl implements PinFailure {
@override
int get hashCode => Object.hash(runtimeType, reason);
@JsonKey(ignore: true)
/// Create a copy of PinVerificationStatus
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$PinFailureImplCopyWith<_$PinFailureImpl> get copyWith =>
@ -489,7 +514,10 @@ abstract class PinFailure implements PinVerificationStatus {
factory PinFailure(final PivPinFailureReason reason) = _$PinFailureImpl;
PivPinFailureReason get reason;
@JsonKey(ignore: true)
/// Create a copy of PinVerificationStatus
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$PinFailureImplCopyWith<_$PinFailureImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -552,6 +580,9 @@ class _$PivPinFailureReasonCopyWithImpl<$Res, $Val extends PivPinFailureReason>
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of PivPinFailureReason
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
@ -571,6 +602,8 @@ class __$$PivInvalidPinImplCopyWithImpl<$Res>
_$PivInvalidPinImpl _value, $Res Function(_$PivInvalidPinImpl) _then)
: super(_value, _then);
/// Create a copy of PivPinFailureReason
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -610,7 +643,9 @@ class _$PivInvalidPinImpl implements PivInvalidPin {
@override
int get hashCode => Object.hash(runtimeType, attemptsRemaining);
@JsonKey(ignore: true)
/// Create a copy of PivPinFailureReason
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$PivInvalidPinImplCopyWith<_$PivInvalidPinImpl> get copyWith =>
@ -683,7 +718,10 @@ abstract class PivInvalidPin implements PivPinFailureReason {
factory PivInvalidPin(final int attemptsRemaining) = _$PivInvalidPinImpl;
int get attemptsRemaining;
@JsonKey(ignore: true)
/// Create a copy of PivPinFailureReason
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$PivInvalidPinImplCopyWith<_$PivInvalidPinImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -702,6 +740,9 @@ class __$$PivWeakPinImplCopyWithImpl<$Res>
__$$PivWeakPinImplCopyWithImpl(
_$PivWeakPinImpl _value, $Res Function(_$PivWeakPinImpl) _then)
: super(_value, _then);
/// Create a copy of PivPinFailureReason
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
@ -801,8 +842,12 @@ mixin _$ManagementKeyMetadata {
bool get defaultValue => throw _privateConstructorUsedError;
TouchPolicy get touchPolicy => throw _privateConstructorUsedError;
/// Serializes this ManagementKeyMetadata to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of ManagementKeyMetadata
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$ManagementKeyMetadataCopyWith<ManagementKeyMetadata> get copyWith =>
throw _privateConstructorUsedError;
}
@ -828,6 +873,8 @@ class _$ManagementKeyMetadataCopyWithImpl<$Res,
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ManagementKeyMetadata
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -874,6 +921,8 @@ class __$$ManagementKeyMetadataImplCopyWithImpl<$Res>
$Res Function(_$ManagementKeyMetadataImpl) _then)
: super(_value, _then);
/// Create a copy of ManagementKeyMetadata
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -931,12 +980,14 @@ class _$ManagementKeyMetadataImpl implements _ManagementKeyMetadata {
other.touchPolicy == touchPolicy));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode =>
Object.hash(runtimeType, keyType, defaultValue, touchPolicy);
@JsonKey(ignore: true)
/// Create a copy of ManagementKeyMetadata
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ManagementKeyMetadataImplCopyWith<_$ManagementKeyMetadataImpl>
@ -966,8 +1017,11 @@ abstract class _ManagementKeyMetadata implements ManagementKeyMetadata {
bool get defaultValue;
@override
TouchPolicy get touchPolicy;
/// Create a copy of ManagementKeyMetadata
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ManagementKeyMetadataImplCopyWith<_$ManagementKeyMetadataImpl>
get copyWith => throw _privateConstructorUsedError;
}
@ -984,8 +1038,12 @@ mixin _$SlotMetadata {
bool get generated => throw _privateConstructorUsedError;
String get publicKey => throw _privateConstructorUsedError;
/// Serializes this SlotMetadata to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of SlotMetadata
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$SlotMetadataCopyWith<SlotMetadata> get copyWith =>
throw _privateConstructorUsedError;
}
@ -1014,6 +1072,8 @@ class _$SlotMetadataCopyWithImpl<$Res, $Val extends SlotMetadata>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of SlotMetadata
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1072,6 +1132,8 @@ class __$$SlotMetadataImplCopyWithImpl<$Res>
_$SlotMetadataImpl _value, $Res Function(_$SlotMetadataImpl) _then)
: super(_value, _then);
/// Create a copy of SlotMetadata
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1147,12 +1209,14 @@ class _$SlotMetadataImpl implements _SlotMetadata {
other.publicKey == publicKey));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType, keyType, pinPolicy, touchPolicy, generated, publicKey);
@JsonKey(ignore: true)
/// Create a copy of SlotMetadata
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SlotMetadataImplCopyWith<_$SlotMetadataImpl> get copyWith =>
@ -1187,8 +1251,11 @@ abstract class _SlotMetadata implements SlotMetadata {
bool get generated;
@override
String get publicKey;
/// Create a copy of SlotMetadata
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SlotMetadataImplCopyWith<_$SlotMetadataImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -1204,8 +1271,12 @@ mixin _$PivStateMetadata {
PinMetadata get pinMetadata => throw _privateConstructorUsedError;
PinMetadata get pukMetadata => throw _privateConstructorUsedError;
/// Serializes this PivStateMetadata to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of PivStateMetadata
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$PivStateMetadataCopyWith<PivStateMetadata> get copyWith =>
throw _privateConstructorUsedError;
}
@ -1236,6 +1307,8 @@ class _$PivStateMetadataCopyWithImpl<$Res, $Val extends PivStateMetadata>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of PivStateMetadata
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1259,6 +1332,8 @@ class _$PivStateMetadataCopyWithImpl<$Res, $Val extends PivStateMetadata>
) as $Val);
}
/// Create a copy of PivStateMetadata
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$ManagementKeyMetadataCopyWith<$Res> get managementKeyMetadata {
@ -1268,6 +1343,8 @@ class _$PivStateMetadataCopyWithImpl<$Res, $Val extends PivStateMetadata>
});
}
/// Create a copy of PivStateMetadata
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$PinMetadataCopyWith<$Res> get pinMetadata {
@ -1276,6 +1353,8 @@ class _$PivStateMetadataCopyWithImpl<$Res, $Val extends PivStateMetadata>
});
}
/// Create a copy of PivStateMetadata
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$PinMetadataCopyWith<$Res> get pukMetadata {
@ -1314,6 +1393,8 @@ class __$$PivStateMetadataImplCopyWithImpl<$Res>
$Res Function(_$PivStateMetadataImpl) _then)
: super(_value, _then);
/// Create a copy of PivStateMetadata
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1374,12 +1455,14 @@ class _$PivStateMetadataImpl implements _PivStateMetadata {
other.pukMetadata == pukMetadata));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode =>
Object.hash(runtimeType, managementKeyMetadata, pinMetadata, pukMetadata);
@JsonKey(ignore: true)
/// Create a copy of PivStateMetadata
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$PivStateMetadataImplCopyWith<_$PivStateMetadataImpl> get copyWith =>
@ -1409,8 +1492,11 @@ abstract class _PivStateMetadata implements PivStateMetadata {
PinMetadata get pinMetadata;
@override
PinMetadata get pukMetadata;
/// Create a copy of PivStateMetadata
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$PivStateMetadataImplCopyWith<_$PivStateMetadataImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -1426,12 +1512,17 @@ mixin _$PivState {
bool get derivedKey => throw _privateConstructorUsedError;
bool get storedKey => throw _privateConstructorUsedError;
int get pinAttempts => throw _privateConstructorUsedError;
bool get supportsBio => throw _privateConstructorUsedError;
String? get chuid => throw _privateConstructorUsedError;
String? get ccc => throw _privateConstructorUsedError;
PivStateMetadata? get metadata => throw _privateConstructorUsedError;
/// Serializes this PivState to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of PivState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$PivStateCopyWith<PivState> get copyWith =>
throw _privateConstructorUsedError;
}
@ -1447,6 +1538,7 @@ abstract class $PivStateCopyWith<$Res> {
bool derivedKey,
bool storedKey,
int pinAttempts,
bool supportsBio,
String? chuid,
String? ccc,
PivStateMetadata? metadata});
@ -1465,6 +1557,8 @@ class _$PivStateCopyWithImpl<$Res, $Val extends PivState>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of PivState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1473,6 +1567,7 @@ class _$PivStateCopyWithImpl<$Res, $Val extends PivState>
Object? derivedKey = null,
Object? storedKey = null,
Object? pinAttempts = null,
Object? supportsBio = null,
Object? chuid = freezed,
Object? ccc = freezed,
Object? metadata = freezed,
@ -1498,6 +1593,10 @@ class _$PivStateCopyWithImpl<$Res, $Val extends PivState>
? _value.pinAttempts
: pinAttempts // ignore: cast_nullable_to_non_nullable
as int,
supportsBio: null == supportsBio
? _value.supportsBio
: supportsBio // ignore: cast_nullable_to_non_nullable
as bool,
chuid: freezed == chuid
? _value.chuid
: chuid // ignore: cast_nullable_to_non_nullable
@ -1513,6 +1612,8 @@ class _$PivStateCopyWithImpl<$Res, $Val extends PivState>
) as $Val);
}
/// Create a copy of PivState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$VersionCopyWith<$Res> get version {
@ -1521,6 +1622,8 @@ class _$PivStateCopyWithImpl<$Res, $Val extends PivState>
});
}
/// Create a copy of PivState
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$PivStateMetadataCopyWith<$Res>? get metadata {
@ -1548,6 +1651,7 @@ abstract class _$$PivStateImplCopyWith<$Res>
bool derivedKey,
bool storedKey,
int pinAttempts,
bool supportsBio,
String? chuid,
String? ccc,
PivStateMetadata? metadata});
@ -1566,6 +1670,8 @@ class __$$PivStateImplCopyWithImpl<$Res>
_$PivStateImpl _value, $Res Function(_$PivStateImpl) _then)
: super(_value, _then);
/// Create a copy of PivState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1574,6 +1680,7 @@ class __$$PivStateImplCopyWithImpl<$Res>
Object? derivedKey = null,
Object? storedKey = null,
Object? pinAttempts = null,
Object? supportsBio = null,
Object? chuid = freezed,
Object? ccc = freezed,
Object? metadata = freezed,
@ -1599,6 +1706,10 @@ class __$$PivStateImplCopyWithImpl<$Res>
? _value.pinAttempts
: pinAttempts // ignore: cast_nullable_to_non_nullable
as int,
supportsBio: null == supportsBio
? _value.supportsBio
: supportsBio // ignore: cast_nullable_to_non_nullable
as bool,
chuid: freezed == chuid
? _value.chuid
: chuid // ignore: cast_nullable_to_non_nullable
@ -1624,6 +1735,7 @@ class _$PivStateImpl extends _PivState {
required this.derivedKey,
required this.storedKey,
required this.pinAttempts,
required this.supportsBio,
this.chuid,
this.ccc,
this.metadata})
@ -1643,6 +1755,8 @@ class _$PivStateImpl extends _PivState {
@override
final int pinAttempts;
@override
final bool supportsBio;
@override
final String? chuid;
@override
final String? ccc;
@ -1651,7 +1765,7 @@ class _$PivStateImpl extends _PivState {
@override
String toString() {
return 'PivState(version: $version, authenticated: $authenticated, derivedKey: $derivedKey, storedKey: $storedKey, pinAttempts: $pinAttempts, chuid: $chuid, ccc: $ccc, metadata: $metadata)';
return 'PivState(version: $version, authenticated: $authenticated, derivedKey: $derivedKey, storedKey: $storedKey, pinAttempts: $pinAttempts, supportsBio: $supportsBio, chuid: $chuid, ccc: $ccc, metadata: $metadata)';
}
@override
@ -1668,18 +1782,22 @@ class _$PivStateImpl extends _PivState {
other.storedKey == storedKey) &&
(identical(other.pinAttempts, pinAttempts) ||
other.pinAttempts == pinAttempts) &&
(identical(other.supportsBio, supportsBio) ||
other.supportsBio == supportsBio) &&
(identical(other.chuid, chuid) || other.chuid == chuid) &&
(identical(other.ccc, ccc) || other.ccc == ccc) &&
(identical(other.metadata, metadata) ||
other.metadata == metadata));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, version, authenticated,
derivedKey, storedKey, pinAttempts, chuid, ccc, metadata);
derivedKey, storedKey, pinAttempts, supportsBio, chuid, ccc, metadata);
@JsonKey(ignore: true)
/// Create a copy of PivState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$PivStateImplCopyWith<_$PivStateImpl> get copyWith =>
@ -1700,6 +1818,7 @@ abstract class _PivState extends PivState {
required final bool derivedKey,
required final bool storedKey,
required final int pinAttempts,
required final bool supportsBio,
final String? chuid,
final String? ccc,
final PivStateMetadata? metadata}) = _$PivStateImpl;
@ -1719,13 +1838,18 @@ abstract class _PivState extends PivState {
@override
int get pinAttempts;
@override
bool get supportsBio;
@override
String? get chuid;
@override
String? get ccc;
@override
PivStateMetadata? get metadata;
/// Create a copy of PivState
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$PivStateImplCopyWith<_$PivStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -1744,8 +1868,12 @@ mixin _$CertInfo {
String get notValidAfter => throw _privateConstructorUsedError;
String get fingerprint => throw _privateConstructorUsedError;
/// Serializes this CertInfo to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of CertInfo
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$CertInfoCopyWith<CertInfo> get copyWith =>
throw _privateConstructorUsedError;
}
@ -1775,6 +1903,8 @@ class _$CertInfoCopyWithImpl<$Res, $Val extends CertInfo>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of CertInfo
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1845,6 +1975,8 @@ class __$$CertInfoImplCopyWithImpl<$Res>
_$CertInfoImpl _value, $Res Function(_$CertInfoImpl) _then)
: super(_value, _then);
/// Create a copy of CertInfo
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -1941,12 +2073,14 @@ class _$CertInfoImpl implements _CertInfo {
other.fingerprint == fingerprint));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, keyType, subject, issuer, serial,
notValidBefore, notValidAfter, fingerprint);
@JsonKey(ignore: true)
/// Create a copy of CertInfo
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$CertInfoImplCopyWith<_$CertInfoImpl> get copyWith =>
@ -1987,8 +2121,11 @@ abstract class _CertInfo implements CertInfo {
String get notValidAfter;
@override
String get fingerprint;
/// Create a copy of CertInfo
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$CertInfoImplCopyWith<_$CertInfoImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -2003,8 +2140,12 @@ mixin _$PivSlot {
SlotMetadata? get metadata => throw _privateConstructorUsedError;
CertInfo? get certInfo => throw _privateConstructorUsedError;
/// Serializes this PivSlot to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of PivSlot
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$PivSlotCopyWith<PivSlot> get copyWith => throw _privateConstructorUsedError;
}
@ -2029,6 +2170,8 @@ class _$PivSlotCopyWithImpl<$Res, $Val extends PivSlot>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of PivSlot
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -2052,6 +2195,8 @@ class _$PivSlotCopyWithImpl<$Res, $Val extends PivSlot>
) as $Val);
}
/// Create a copy of PivSlot
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SlotMetadataCopyWith<$Res>? get metadata {
@ -2064,6 +2209,8 @@ class _$PivSlotCopyWithImpl<$Res, $Val extends PivSlot>
});
}
/// Create a copy of PivSlot
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$CertInfoCopyWith<$Res>? get certInfo {
@ -2100,6 +2247,8 @@ class __$$PivSlotImplCopyWithImpl<$Res>
_$PivSlotImpl _value, $Res Function(_$PivSlotImpl) _then)
: super(_value, _then);
/// Create a copy of PivSlot
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -2156,11 +2305,13 @@ class _$PivSlotImpl implements _PivSlot {
other.certInfo == certInfo));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, slot, metadata, certInfo);
@JsonKey(ignore: true)
/// Create a copy of PivSlot
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$PivSlotImplCopyWith<_$PivSlotImpl> get copyWith =>
@ -2188,8 +2339,11 @@ abstract class _PivSlot implements PivSlot {
SlotMetadata? get metadata;
@override
CertInfo? get certInfo;
/// Create a copy of PivSlot
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$PivSlotImplCopyWith<_$PivSlotImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -2251,6 +2405,8 @@ mixin _$PivExamineResult {
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
/// Serializes this PivExamineResult to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
}
@ -2270,6 +2426,9 @@ class _$PivExamineResultCopyWithImpl<$Res, $Val extends PivExamineResult>
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of PivExamineResult
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
@ -2291,6 +2450,8 @@ class __$$ExamineResultImplCopyWithImpl<$Res>
_$ExamineResultImpl _value, $Res Function(_$ExamineResultImpl) _then)
: super(_value, _then);
/// Create a copy of PivExamineResult
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -2314,6 +2475,8 @@ class __$$ExamineResultImplCopyWithImpl<$Res>
));
}
/// Create a copy of PivExamineResult
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$CertInfoCopyWith<$Res>? get certInfo {
@ -2367,11 +2530,13 @@ class _$ExamineResultImpl implements _ExamineResult {
other.certInfo == certInfo));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, password, keyType, certInfo);
@JsonKey(ignore: true)
/// Create a copy of PivExamineResult
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ExamineResultImplCopyWith<_$ExamineResultImpl> get copyWith =>
@ -2463,7 +2628,10 @@ abstract class _ExamineResult implements PivExamineResult {
bool get password;
KeyType? get keyType;
CertInfo? get certInfo;
@JsonKey(ignore: true)
/// Create a copy of PivExamineResult
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ExamineResultImplCopyWith<_$ExamineResultImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -2482,6 +2650,9 @@ class __$$InvalidPasswordImplCopyWithImpl<$Res>
__$$InvalidPasswordImplCopyWithImpl(
_$InvalidPasswordImpl _value, $Res Function(_$InvalidPasswordImpl) _then)
: super(_value, _then);
/// Create a copy of PivExamineResult
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
@ -2507,7 +2678,7 @@ class _$InvalidPasswordImpl implements _InvalidPassword {
(other.runtimeType == runtimeType && other is _$InvalidPasswordImpl);
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => runtimeType.hashCode;
@ -2661,6 +2832,9 @@ class _$PivGenerateParametersCopyWithImpl<$Res,
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of PivGenerateParameters
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
@ -2677,6 +2851,9 @@ class __$$GeneratePublicKeyImplCopyWithImpl<$Res>
__$$GeneratePublicKeyImplCopyWithImpl(_$GeneratePublicKeyImpl _value,
$Res Function(_$GeneratePublicKeyImpl) _then)
: super(_value, _then);
/// Create a copy of PivGenerateParameters
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
@ -2792,6 +2969,8 @@ class __$$GenerateCertificateImplCopyWithImpl<$Res>
$Res Function(_$GenerateCertificateImpl) _then)
: super(_value, _then);
/// Create a copy of PivGenerateParameters
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -2848,7 +3027,9 @@ class _$GenerateCertificateImpl implements _GenerateCertificate {
@override
int get hashCode => Object.hash(runtimeType, subject, validFrom, validTo);
@JsonKey(ignore: true)
/// Create a copy of PivGenerateParameters
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$GenerateCertificateImplCopyWith<_$GenerateCertificateImpl> get copyWith =>
@ -2937,7 +3118,10 @@ abstract class _GenerateCertificate implements PivGenerateParameters {
String get subject;
DateTime get validFrom;
DateTime get validTo;
@JsonKey(ignore: true)
/// Create a copy of PivGenerateParameters
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$GenerateCertificateImplCopyWith<_$GenerateCertificateImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -2959,6 +3143,8 @@ class __$$GenerateCsrImplCopyWithImpl<$Res>
_$GenerateCsrImpl _value, $Res Function(_$GenerateCsrImpl) _then)
: super(_value, _then);
/// Create a copy of PivGenerateParameters
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -2997,7 +3183,9 @@ class _$GenerateCsrImpl implements _GenerateCsr {
@override
int get hashCode => Object.hash(runtimeType, subject);
@JsonKey(ignore: true)
/// Create a copy of PivGenerateParameters
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$GenerateCsrImplCopyWith<_$GenerateCsrImpl> get copyWith =>
@ -3080,7 +3268,10 @@ abstract class _GenerateCsr implements PivGenerateParameters {
factory _GenerateCsr({required final String subject}) = _$GenerateCsrImpl;
String get subject;
@JsonKey(ignore: true)
/// Create a copy of PivGenerateParameters
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$GenerateCsrImplCopyWith<_$GenerateCsrImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -3095,8 +3286,12 @@ mixin _$PivGenerateResult {
String get publicKey => throw _privateConstructorUsedError;
String? get result => throw _privateConstructorUsedError;
/// Serializes this PivGenerateResult to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of PivGenerateResult
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$PivGenerateResultCopyWith<PivGenerateResult> get copyWith =>
throw _privateConstructorUsedError;
}
@ -3120,6 +3315,8 @@ class _$PivGenerateResultCopyWithImpl<$Res, $Val extends PivGenerateResult>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of PivGenerateResult
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -3163,6 +3360,8 @@ class __$$PivGenerateResultImplCopyWithImpl<$Res>
$Res Function(_$PivGenerateResultImpl) _then)
: super(_value, _then);
/// Create a copy of PivGenerateResult
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -3220,11 +3419,13 @@ class _$PivGenerateResultImpl implements _PivGenerateResult {
(identical(other.result, result) || other.result == result));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, generateType, publicKey, result);
@JsonKey(ignore: true)
/// Create a copy of PivGenerateResult
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$PivGenerateResultImplCopyWith<_$PivGenerateResultImpl> get copyWith =>
@ -3254,8 +3455,11 @@ abstract class _PivGenerateResult implements PivGenerateResult {
String get publicKey;
@override
String? get result;
/// Create a copy of PivGenerateResult
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$PivGenerateResultImplCopyWith<_$PivGenerateResultImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@ -3270,8 +3474,12 @@ mixin _$PivImportResult {
String? get publicKey => throw _privateConstructorUsedError;
String? get certificate => throw _privateConstructorUsedError;
/// Serializes this PivImportResult to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of PivImportResult
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$PivImportResultCopyWith<PivImportResult> get copyWith =>
throw _privateConstructorUsedError;
}
@ -3297,6 +3505,8 @@ class _$PivImportResultCopyWithImpl<$Res, $Val extends PivImportResult>
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of PivImportResult
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -3320,6 +3530,8 @@ class _$PivImportResultCopyWithImpl<$Res, $Val extends PivImportResult>
) as $Val);
}
/// Create a copy of PivImportResult
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$SlotMetadataCopyWith<$Res>? get metadata {
@ -3355,6 +3567,8 @@ class __$$PivImportResultImplCopyWithImpl<$Res>
_$PivImportResultImpl _value, $Res Function(_$PivImportResultImpl) _then)
: super(_value, _then);
/// Create a copy of PivImportResult
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
@ -3415,12 +3629,14 @@ class _$PivImportResultImpl implements _PivImportResult {
other.certificate == certificate));
}
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode =>
Object.hash(runtimeType, metadata, publicKey, certificate);
@JsonKey(ignore: true)
/// Create a copy of PivImportResult
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$PivImportResultImplCopyWith<_$PivImportResultImpl> get copyWith =>
@ -3450,8 +3666,11 @@ abstract class _PivImportResult implements PivImportResult {
String? get publicKey;
@override
String? get certificate;
/// Create a copy of PivImportResult
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
_$$PivImportResultImplCopyWith<_$PivImportResultImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -114,6 +114,7 @@ _$PivStateImpl _$$PivStateImplFromJson(Map<String, dynamic> json) =>
derivedKey: json['derived_key'] as bool,
storedKey: json['stored_key'] as bool,
pinAttempts: (json['pin_attempts'] as num).toInt(),
supportsBio: json['supports_bio'] as bool,
chuid: json['chuid'] as String?,
ccc: json['ccc'] as String?,
metadata: json['metadata'] == null
@ -128,6 +129,7 @@ Map<String, dynamic> _$$PivStateImplToJson(_$PivStateImpl instance) =>
'derived_key': instance.derivedKey,
'stored_key': instance.storedKey,
'pin_attempts': instance.pinAttempts,
'supports_bio': instance.supportsBio,
'chuid': instance.chuid,
'ccc': instance.ccc,
'metadata': instance.metadata,

View File

@ -310,7 +310,16 @@ class PivActions extends ConsumerWidget {
}
List<ActionItem> buildSlotActions(
PivState pivState, PivSlot slot, AppLocalizations l10n) {
PivState pivState, PivSlot slot, bool fipsUnready, AppLocalizations l10n) {
if (fipsUnready) {
// TODO: Decide on final look and move strings to .arb file.
return [
ActionItem(
icon: const Icon(Symbols.add),
title: 'Provision slot',
subtitle: 'Change from default PIN/PUK/Management key first'),
];
}
final hasCert = slot.certInfo != null;
final hasKey = slot.metadata != null;
final canDeleteOrMoveKey = hasKey && pivState.version.isAtLeast(5, 7);

View File

@ -36,8 +36,9 @@ class GenerateKeyDialog extends ConsumerStatefulWidget {
final DevicePath devicePath;
final PivState pivState;
final PivSlot pivSlot;
const GenerateKeyDialog(this.devicePath, this.pivState, this.pivSlot,
{super.key});
final bool showMatch;
GenerateKeyDialog(this.devicePath, this.pivState, this.pivSlot, {super.key})
: showMatch = pivSlot.slot != SlotId.cardAuth && pivState.supportsBio;
@override
ConsumerState<ConsumerStatefulWidget> createState() =>
@ -53,6 +54,7 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
late DateTime _validTo;
late DateTime _validToDefault;
late DateTime _validToMax;
late bool _allowMatch;
bool _generating = false;
@override
@ -64,6 +66,8 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
_validToDefault = DateTime.utc(now.year + 1, now.month, now.day);
_validTo = _validToDefault;
_validToMax = DateTime.utc(now.year + 10, now.month, now.day);
_allowMatch = widget.showMatch;
}
@override
@ -117,6 +121,7 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
final result = await pivNotifier.generate(
widget.pivSlot.slot,
_keyType,
pinPolicy: getPinPolicy(widget.pivSlot.slot, _allowMatch),
parameters: switch (_generateType) {
GenerateType.publicKey =>
PivGenerateParameters.publicKey(),
@ -183,7 +188,9 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
l10n.s_options,
style: textTheme.bodyLarge,
),
Text(l10n.p_cert_options_desc),
Text(widget.showMatch
? l10n.p_cert_options_bio_desc
: l10n.p_cert_options_desc),
Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 4.0,
@ -238,6 +245,16 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
}
},
),
if (widget.showMatch)
FilterChip(
label: Text(l10n.s_allow_fingerprint),
selected: _allowMatch,
onSelected: (value) {
setState(() {
_allowMatch = value;
});
},
),
]),
Padding(
padding: const EdgeInsets.symmetric(vertical: 4),

View File

@ -39,9 +39,10 @@ class ImportFileDialog extends ConsumerStatefulWidget {
final PivState pivState;
final PivSlot pivSlot;
final File file;
const ImportFileDialog(
this.devicePath, this.pivState, this.pivSlot, this.file,
{super.key});
final bool showMatch;
ImportFileDialog(this.devicePath, this.pivState, this.pivSlot, this.file,
{super.key})
: showMatch = pivSlot.slot != SlotId.cardAuth && pivState.supportsBio;
@override
ConsumerState<ConsumerStatefulWidget> createState() =>
@ -50,6 +51,7 @@ class ImportFileDialog extends ConsumerStatefulWidget {
class _ImportFileDialogState extends ConsumerState<ImportFileDialog> {
late String _data;
late bool _allowMatch;
PivExamineResult? _state;
String _password = '';
bool _passwordIsWrong = false;
@ -59,6 +61,8 @@ class _ImportFileDialogState extends ConsumerState<ImportFileDialog> {
@override
void initState() {
super.initState();
_allowMatch = widget.showMatch;
_init();
}
@ -214,9 +218,13 @@ class _ImportFileDialogState extends ConsumerState<ImportFileDialog> {
));
await ref
.read(pivSlotsProvider(widget.devicePath).notifier)
.import(widget.pivSlot.slot, _data,
password:
_password.isNotEmpty ? _password : null);
.import(
widget.pivSlot.slot,
_data,
password: _password.isNotEmpty ? _password : null,
pinPolicy: getPinPolicy(
widget.pivSlot.slot, _allowMatch),
);
await withContext(
(context) async {
Navigator.of(context).pop(true);
@ -284,6 +292,16 @@ class _ImportFileDialogState extends ConsumerState<ImportFileDialog> {
),
],
),
if (!unsupportedKey && widget.showMatch)
FilterChip(
label: Text(l10n.s_allow_fingerprint),
selected: _allowMatch,
onSelected: (value) {
setState(() {
_allowMatch = value;
});
},
),
],
if (certInfo != null) ...[
Text(

View File

@ -25,6 +25,7 @@ import '../../app/message.dart';
import '../../app/models.dart';
import '../../app/state.dart';
import '../../core/models.dart';
import '../../management/models.dart';
import '../../widgets/app_input_decoration.dart';
import '../../widgets/app_text_field.dart';
import '../../widgets/app_text_form_field.dart';
@ -176,6 +177,17 @@ class _ManageKeyDialogState extends ConsumerState<ManageKeyDialog> {
? currentKeyOrPin.length >= 4
: currentKeyOrPin.length == currentType.keyLength * 2;
final newLenOk = _keyController.text.length == hexLength;
final (fipsCapable, fipsApproved) = ref
.watch(currentDeviceDataProvider)
.valueOrNull
?.info
.getFipsStatus(Capability.piv) ??
(false, false);
final fipsUnready = fipsCapable && !fipsApproved;
final managementKeyTypes = ManagementKeyType.values.toList();
if (fipsCapable) {
managementKeyTypes.remove(ManagementKeyType.tdes);
}
return ResponsiveDialog(
title: Text(l10n.l_change_management_key),
@ -334,7 +346,7 @@ class _ManageKeyDialogState extends ConsumerState<ManageKeyDialog> {
children: [
if (widget.pivState.metadata != null)
ChoiceFilterChip<ManagementKeyType>(
items: ManagementKeyType.values,
items: managementKeyTypes,
value: _keyType,
selected: _keyType != currentType,
itemBuilder: (value) => Text(value.getDisplayName(l10n)),
@ -344,16 +356,17 @@ class _ManageKeyDialogState extends ConsumerState<ManageKeyDialog> {
});
},
),
FilterChip(
key: keys.pinLockManagementKeyChip,
label: Text(l10n.s_protect_key),
selected: _storeKey,
onSelected: (value) {
setState(() {
_storeKey = value;
});
},
),
if (!fipsUnready)
FilterChip(
key: keys.pinLockManagementKeyChip,
label: Text(l10n.s_protect_key),
selected: _storeKey,
onSelected: (value) {
setState(() {
_storeKey = value;
});
},
),
]),
]
.map((e) => Padding(

View File

@ -24,6 +24,7 @@ import 'package:material_symbols_icons/symbols.dart';
import '../../app/message.dart';
import '../../app/models.dart';
import '../../app/shortcuts.dart';
import '../../app/state.dart';
import '../../app/views/action_list.dart';
import '../../app/views/app_failure_page.dart';
import '../../app/views/app_list_item.dart';
@ -57,6 +58,14 @@ class _PivScreenState extends ConsumerState<PivScreen> {
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final hasFeature = ref.watch(featureProvider);
final (fipsCapable, fipsApproved) = ref
.watch(currentDeviceDataProvider)
.valueOrNull
?.info
.getFipsStatus(Capability.piv) ??
(false, false);
final fipsUnready = fipsCapable && !fipsApproved;
return ref.watch(pivStateProvider(widget.devicePath)).when(
loading: () => MessagePage(
title: l10n.s_certificates,
@ -168,8 +177,8 @@ class _PivScreenState extends ConsumerState<PivScreen> {
ActionListSection.fromMenuActions(
context,
l10n.s_actions,
actions:
buildSlotActions(pivState, selected, l10n),
actions: buildSlotActions(
pivState, selected, fipsUnready, l10n),
),
],
)
@ -200,25 +209,30 @@ class _PivScreenState extends ConsumerState<PivScreen> {
return null;
}),
},
child: Column(
children: [
...normalSlots.map(
(e) => _CertificateListItem(
pivState,
e,
expanded: expanded,
selected: e == selected,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Column(
children: [
...normalSlots.map(
(e) => _CertificateListItem(
pivState,
e,
expanded: expanded,
selected: e == selected,
fipsUnready: fipsUnready,
),
),
),
...shownRetiredSlots.map(
(e) => _CertificateListItem(
pivState,
e,
expanded: expanded,
selected: e == selected,
),
)
],
...shownRetiredSlots.map(
(e) => _CertificateListItem(
pivState,
e,
expanded: expanded,
selected: e == selected,
fipsUnready: fipsUnready,
),
)
],
),
),
);
},
@ -235,9 +249,12 @@ class _CertificateListItem extends ConsumerWidget {
final PivSlot pivSlot;
final bool expanded;
final bool selected;
final bool fipsUnready;
const _CertificateListItem(this.pivState, this.pivSlot,
{required this.expanded, required this.selected});
{required this.expanded,
required this.selected,
required this.fipsUnready});
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -272,8 +289,8 @@ class _CertificateListItem extends ConsumerWidget {
),
tapIntent: isDesktop && !expanded ? null : OpenIntent(pivSlot),
doubleTapIntent: isDesktop && !expanded ? OpenIntent(pivSlot) : null,
buildPopupActions: hasFeature(features.slots)
? (context) => buildSlotActions(pivState, pivSlot, l10n)
buildPopupActions: hasFeature(features.slots) && !fipsUnready
? (context) => buildSlotActions(pivState, pivSlot, fipsUnready, l10n)
: null,
);
}

View File

@ -22,6 +22,7 @@ import '../../app/shortcuts.dart';
import '../../app/state.dart';
import '../../app/views/action_list.dart';
import '../../app/views/fs_dialog.dart';
import '../../management/models.dart';
import '../models.dart';
import '../state.dart';
import 'actions.dart';
@ -34,12 +35,13 @@ class SlotDialog extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// TODO: Solve this in a cleaner way
final node = ref.watch(currentDeviceDataProvider).valueOrNull?.node;
if (node == null) {
var keyData = ref.watch(currentDeviceDataProvider).valueOrNull;
if (keyData == null) {
// The rest of this method assumes there is a device, and will throw an exception if not.
// This will never be shown, as the dialog will be immediately closed
return const SizedBox();
}
final devicePath = keyData.node.path;
final l10n = AppLocalizations.of(context)!;
final textTheme = Theme.of(context).textTheme;
@ -48,8 +50,11 @@ class SlotDialog extends ConsumerWidget {
color: Theme.of(context).colorScheme.onSurfaceVariant,
);
final pivState = ref.watch(pivStateProvider(node.path)).valueOrNull;
final slotData = ref.watch(pivSlotsProvider(node.path).select((value) =>
final (fipsCapable, fipsApproved) =
keyData.info.getFipsStatus(Capability.piv);
final pivState = ref.watch(pivStateProvider(devicePath)).valueOrNull;
final slotData = ref.watch(pivSlotsProvider(devicePath).select((value) =>
value.whenOrNull(
data: (data) =>
data.firstWhere((element) => element.slot == pivSlot))));
@ -61,7 +66,7 @@ class SlotDialog extends ConsumerWidget {
final certInfo = slotData.certInfo;
final metadata = slotData.metadata;
return PivActions(
devicePath: node.path,
devicePath: devicePath,
pivState: pivState,
builder: (context) => ItemShortcuts(
item: slotData,
@ -113,7 +118,8 @@ class SlotDialog extends ConsumerWidget {
ActionListSection.fromMenuActions(
context,
l10n.s_actions,
actions: buildSlotActions(pivState, slotData, l10n),
actions: buildSlotActions(
pivState, slotData, fipsCapable && !fipsApproved, l10n),
),
],
),

View File

@ -29,3 +29,13 @@ List<KeyType> getSupportedKeyTypes(Version version, bool isFips) => [
KeyType.eccp256,
KeyType.eccp384,
];
PinPolicy getPinPolicy(SlotId slot, bool match) {
if (match) {
if (slot == SlotId.signature) {
return PinPolicy.matchAlways;
}
return PinPolicy.matchOnce;
}
return PinPolicy.dfault;
}

View File

@ -1,68 +0,0 @@
/*
* Copyright (C) 2022 Yubico.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
final Widget pushPinStrokeIcon = Builder(builder: (context) {
return CustomPaint(
painter: _StrikethroughPainter(IconTheme.of(context).color ?? Colors.black),
child: ClipPath(
clipper: _StrikethroughClipper(), child: const Icon(Symbols.push_pin)),
);
});
class _StrikethroughClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path()
..moveTo(0, 2)
..lineTo(0, size.height)
..lineTo(size.width - 2, size.height)
..lineTo(0, 2)
..moveTo(2, 0)
..lineTo(size.width, size.height - 2)
..lineTo(size.width, 0)
..lineTo(2, 0)
..close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
class _StrikethroughPainter extends CustomPainter {
final Color color;
_StrikethroughPainter(this.color);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..strokeWidth = 1.3;
canvas.drawLine(Offset(size.width * 0.15, size.height * 0.15),
Offset(size.width * 0.8, size.height * 0.8), paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}

111
lib/widgets/flex_box.dart Normal file
View File

@ -0,0 +1,111 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:material_symbols_icons/symbols.dart';
enum FlexLayout {
list,
grid;
IconData get icon => switch (this) {
FlexLayout.list => Symbols.list,
FlexLayout.grid => Symbols.grid_view
};
String getDisplayName(AppLocalizations l10n) => switch (this) {
FlexLayout.list => l10n.s_list_layout,
FlexLayout.grid => l10n.s_grid_layout
};
}
class FlexBox<T> extends StatelessWidget {
final List<T> items;
final Widget Function(T value) itemBuilder;
final FlexLayout layout;
final double cellMinWidth;
final double spacing;
final double runSpacing;
const FlexBox({
super.key,
required this.items,
required this.itemBuilder,
required this.cellMinWidth,
this.layout = FlexLayout.list,
this.spacing = 0.0,
this.runSpacing = 0.0,
});
int _getItemsPerRow(double width) {
// Calculate the maximum number of cells that can fit in one row
int cellsPerRow = (width / (cellMinWidth + spacing)).floor();
// Ensure there's at least one cell per row
if (cellsPerRow < 1) {
cellsPerRow = 1;
}
// Calculate the total width needed for the calculated number of cells and spacing
double totalWidthNeeded =
cellsPerRow * cellMinWidth + (cellsPerRow - 1) * spacing;
// Adjust the number of cells per row if the calculated total width exceeds the available width
if (totalWidthNeeded > width) {
cellsPerRow = cellsPerRow - 1 > 0 ? cellsPerRow - 1 : 1;
}
return cellsPerRow;
}
List<List<T>> getChunks(int itemsPerChunk) {
List<List<T>> chunks = [];
final numChunks = (items.length / itemsPerChunk).ceil();
for (int i = 0; i < numChunks; i++) {
final index = i * itemsPerChunk;
int endIndex = index + itemsPerChunk;
if (endIndex > items.length) {
endIndex = items.length;
}
chunks.add(items.sublist(index, endIndex));
}
return chunks;
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final width = constraints.maxWidth;
final itemsPerRow =
layout == FlexLayout.grid ? _getItemsPerRow(width) : 1;
final chunks = getChunks(itemsPerRow);
return Column(
children: [
for (final c in chunks) ...[
if (chunks.indexOf(c) > 0) SizedBox(height: runSpacing),
Row(
children: [
for (final entry in c) ...[
Flexible(
child: itemBuilder(entry),
),
if (itemsPerRow != 1 && c.indexOf(entry) != c.length - 1)
SizedBox(width: spacing),
],
if (c.length < itemsPerRow) ...[
// Prevents resizing when an item is removed
SizedBox(width: 8 * (itemsPerRow - c.length).toDouble()),
Spacer(
flex: itemsPerRow - c.length,
)
]
],
),
]
],
);
},
);
}
}

View File

@ -102,7 +102,7 @@ class _ResponsiveDialogState extends State<ResponsiveDialog> {
...widget.actions
],
),
onPopInvoked: (didPop) {
onPopInvokedWithResult: (didPop, _) {
if (didPop) {
widget.onCancel?.call();
}

View File

@ -17,6 +17,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class Toast extends StatefulWidget {
final String message;
@ -149,5 +150,7 @@ void Function() showToast(
Overlay.of(context).insert(entry!);
});
SemanticsService.announce(message, TextDirection.ltr);
return close;
}

View File

@ -63,4 +63,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7
COCOAPODS: 1.14.3
COCOAPODS: 1.15.2

View File

@ -1,7 +1,7 @@
import Cocoa
import FlutterMacOS
@NSApplicationMain
@main
class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
// Keep app running if window closes

12
macos_assemble.patch Normal file
View File

@ -0,0 +1,12 @@
diff --git a/packages/flutter_tools/bin/macos_assemble.sh b/packages/flutter_tools/bin/macos_assemble.sh
index 40c6a5051f..a7f05d9113 100755
--- a/packages/flutter_tools/bin/macos_assemble.sh
+++ b/packages/flutter_tools/bin/macos_assemble.sh
@@ -222,6 +222,7 @@ EmbedFrameworks() {
# Iterate through all .frameworks in native assets directory.
for native_asset in "${native_assets_path}"*.framework; do
+ [ -e "$native_asset" ] || continue # Skip when there are no matches.
# Codesign the framework inside the app bundle.
RunCommand codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${xcode_frameworks_dir}/$(basename "$native_asset")"
done

View File

@ -5,18 +5,23 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
url: "https://pub.dev"
source: hosted
version: "67.0.0"
version: "72.0.0"
_macros:
dependency: transitive
description: dart
source: sdk
version: "0.3.2"
analyzer:
dependency: "direct dev"
description:
name: analyzer
sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
url: "https://pub.dev"
source: hosted
version: "6.4.1"
version: "6.7.0"
analyzer_plugin:
dependency: "direct dev"
description:
@ -101,18 +106,18 @@ packages:
dependency: "direct dev"
description:
name: build_runner
sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7"
sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04
url: "https://pub.dev"
source: hosted
version: "2.4.11"
version: "2.4.12"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe
sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
url: "https://pub.dev"
source: hosted
version: "7.3.1"
version: "7.3.2"
built_collection:
dependency: transitive
description:
@ -197,18 +202,18 @@ packages:
dependency: transitive
description:
name: cross_file
sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32"
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
url: "https://pub.dev"
source: hosted
version: "0.3.4+1"
version: "0.3.4+2"
crypto:
dependency: "direct main"
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
sha256: "1dceb0cf05cb63a7852c11560060e53ec2f182079a16ced6f4395c5b0875baf8"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.0.4"
custom_lint:
dependency: "direct dev"
description:
@ -261,10 +266,10 @@ packages:
dependency: transitive
description:
name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.3"
file:
dependency: transitive
description:
@ -277,10 +282,10 @@ packages:
dependency: "direct main"
description:
name: file_picker
sha256: "824f5b9f389bfc4dddac3dea76cd70c51092d9dff0b2ece7ef4f53db8547d258"
sha256: "1375f8685ca6f0412effecc2db834757e9d0e3e055468053e563794b0755cdcd"
url: "https://pub.dev"
source: hosted
version: "8.0.6"
version: "8.1.1"
fixnum:
dependency: transitive
description:
@ -316,10 +321,10 @@ packages:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: c6b0b4c05c458e1c01ad9bcc14041dd7b1f6783d487be4386f793f47a8a4d03e
sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de"
url: "https://pub.dev"
source: hosted
version: "2.0.20"
version: "2.0.21"
flutter_riverpod:
dependency: "direct main"
description:
@ -342,18 +347,18 @@ packages:
dependency: "direct dev"
description:
name: freezed
sha256: a434911f643466d78462625df76fd9eb13e57348ff43fe1f77bbe909522c67a1
sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e"
url: "https://pub.dev"
source: hosted
version: "2.5.2"
version: "2.5.7"
freezed_annotation:
dependency: "direct main"
description:
name: freezed_annotation
sha256: f54946fdb1fa7b01f780841937b1a80783a20b393485f3f6cdf336fd6f4705f2
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2
url: "https://pub.dev"
source: hosted
version: "2.4.2"
version: "2.4.4"
frontend_server_client:
dependency: transitive
description:
@ -379,10 +384,10 @@ packages:
dependency: transitive
description:
name: graphs
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.3.2"
hotreloader:
dependency: transitive
description:
@ -395,10 +400,10 @@ packages:
dependency: transitive
description:
name: http
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "1.2.2"
http_multi_server:
dependency: transitive
description:
@ -464,18 +469,18 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
url: "https://pub.dev"
source: hosted
version: "10.0.4"
version: "10.0.5"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.0.5"
leak_tracker_testing:
dependency: transitive
description:
@ -515,6 +520,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
macros:
dependency: transitive
description:
name: macros
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
url: "https://pub.dev"
source: hosted
version: "0.1.2-main.4"
matcher:
dependency: transitive
description:
@ -527,18 +540,18 @@ packages:
dependency: transitive
description:
name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.8.0"
version: "0.11.1"
material_symbols_icons:
dependency: "direct main"
description:
name: material_symbols_icons
sha256: a2c78726048c755f0f90fd2b7c8799cd94338e2e9b7ab6498ae56503262c14bc
sha256: "8f4abdb6bc714526ccf66e825b7391d7ca65239484ad92be71980fe73a57521c"
url: "https://pub.dev"
source: hosted
version: "4.2762.0"
version: "4.2780.0"
menu_base:
dependency: transitive
description:
@ -551,10 +564,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
url: "https://pub.dev"
source: hosted
version: "1.12.0"
version: "1.15.0"
mime:
dependency: transitive
description:
@ -591,18 +604,18 @@ packages:
dependency: "direct main"
description:
name: path_provider
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.4"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: bca87b0165ffd7cdb9cad8edd22d18d2201e886d9a9f19b4fb3452ea7df3a72a
sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7"
url: "https://pub.dev"
source: hosted
version: "2.2.6"
version: "2.2.10"
path_provider_foundation:
dependency: transitive
description:
@ -631,10 +644,10 @@ packages:
dependency: transitive
description:
name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.2.1"
version: "2.3.0"
petitparser:
dependency: transitive
description:
@ -647,10 +660,10 @@ packages:
dependency: transitive
description:
name: platform
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
url: "https://pub.dev"
source: hosted
version: "3.1.4"
version: "3.1.5"
plugin_platform_interface:
dependency: transitive
description:
@ -726,58 +739,58 @@ packages:
dependency: "direct main"
description:
name: shared_preferences
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
sha256: c272f9cabca5a81adc9b0894381e9c1def363e980f960fa903c604c471b22f68
url: "https://pub.dev"
source: hosted
version: "2.2.3"
version: "2.3.1"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577"
sha256: a7e8467e9181cef109f601e3f65765685786c1a738a83d7fbbde377589c0d974
url: "https://pub.dev"
source: hosted
version: "2.2.3"
version: "2.3.1"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7"
sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.5.2"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a"
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.4.2"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.4.1"
shelf:
dependency: transitive
description:
@ -899,10 +912,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
url: "https://pub.dev"
source: hosted
version: "0.7.0"
version: "0.7.2"
test_res:
dependency: "direct dev"
description:
@ -946,26 +959,26 @@ packages:
dependency: transitive
description:
name: url_launcher_android
sha256: ceb2625f0c24ade6ef6778d1de0b2e44f2db71fded235eb52295247feba8c5cf
sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79
url: "https://pub.dev"
source: hosted
version: "6.3.3"
version: "6.3.9"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89"
sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e
url: "https://pub.dev"
source: hosted
version: "6.3.0"
version: "6.3.1"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af
url: "https://pub.dev"
source: hosted
version: "3.1.1"
version: "3.2.0"
url_launcher_macos:
dependency: transitive
description:
@ -986,26 +999,26 @@ packages:
dependency: transitive
description:
name: url_launcher_web
sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a"
sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.3.3"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
version: "3.1.2"
uuid:
dependency: transitive
description:
name: uuid
sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8"
sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90"
url: "https://pub.dev"
source: hosted
version: "4.4.0"
version: "4.4.2"
vector_graphics:
dependency: "direct main"
description:
@ -1042,10 +1055,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
url: "https://pub.dev"
source: hosted
version: "14.2.1"
version: "14.2.4"
watcher:
dependency: transitive
description:
@ -1058,26 +1071,26 @@ packages:
dependency: transitive
description:
name: web
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "1.0.0"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "24301d8c293ce6fe327ffe6f59d8fd8834735f0ec36e4fd383ec7ff8a64aa078"
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
url: "https://pub.dev"
source: hosted
version: "0.1.5"
version: "0.1.6"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: a2d56211ee4d35d9b344d9d4ce60f362e4f5d1aafb988302906bd732bc731276
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "3.0.1"
webdriver:
dependency: transitive
description:
@ -1090,10 +1103,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4
sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a"
url: "https://pub.dev"
source: hosted
version: "5.5.1"
version: "5.5.4"
window_manager:
dependency: "direct main"
description:
@ -1128,5 +1141,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.4.3 <4.0.0"
dart: ">=3.5.0-259.0.dev <4.0.0"
flutter: ">=3.22.0"

View File

@ -70,7 +70,7 @@ dependencies:
io: ^1.0.4
base32: ^2.1.3
convert: ^3.1.1
material_symbols_icons: ^4.2719.3
material_symbols_icons: ^4.2741.0
dev_dependencies:
integration_test: