mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-23 10:11:52 +03:00
Merge branch 'main' into fix/android-nfc
This commit is contained in:
commit
902eb6718d
8
.github/workflows/macos.yml
vendored
8
.github/workflows/macos.yml
vendored
@ -48,10 +48,18 @@ jobs:
|
|||||||
- name: Check generated files
|
- name: Check generated files
|
||||||
run: git diff --exit-code
|
run: git diff --exit-code
|
||||||
|
|
||||||
|
- name: Create dmg
|
||||||
|
run: |
|
||||||
|
brew install create-dmg
|
||||||
|
mkdir source_folder
|
||||||
|
cp -R build/macos/Build/Products/Release/"Yubico Authenticator.app" source_folder
|
||||||
|
sh create-dmg.sh
|
||||||
|
|
||||||
- name: Rename and archive app bundle
|
- name: Rename and archive app bundle
|
||||||
run: |
|
run: |
|
||||||
export REF=$(echo ${GITHUB_REF} | cut -d '/' -f 3)
|
export REF=$(echo ${GITHUB_REF} | cut -d '/' -f 3)
|
||||||
mkdir deploy
|
mkdir deploy
|
||||||
|
mv yubioath-desktop.dmg deploy
|
||||||
tar -czf deploy/yubioath-desktop-${REF}.app.tar.gz -C build/macos/Build/Products/Release "Yubico Authenticator.app"
|
tar -czf deploy/yubioath-desktop-${REF}.app.tar.gz -C build/macos/Build/Products/Release "Yubico Authenticator.app"
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
|
2
.github/workflows/windows.yml
vendored
2
.github/workflows/windows.yml
vendored
@ -58,6 +58,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
$env:PATH += ";$env:WIX\bin"
|
$env:PATH += ";$env:WIX\bin"
|
||||||
$env:SRCDIR = "build\windows\runner\Release\"
|
$env:SRCDIR = "build\windows\runner\Release\"
|
||||||
|
cp resources\win\license.rtf .\
|
||||||
heat dir .\build\windows\runner\Release\ -out fragment.wxs -gg -scom -srd -sfrag -dr INSTALLDIR -cg ApplicationFiles -var env.SRCDIR
|
heat dir .\build\windows\runner\Release\ -out fragment.wxs -gg -scom -srd -sfrag -dr INSTALLDIR -cg ApplicationFiles -var env.SRCDIR
|
||||||
candle .\fragment.wxs .\resources\win\yubioath-desktop.wxs -ext WixUtilExtension -arch x64
|
candle .\fragment.wxs .\resources\win\yubioath-desktop.wxs -ext WixUtilExtension -arch x64
|
||||||
light fragment.wixobj yubioath-desktop.wixobj -ext WixUIExtension -ext WixUtilExtension -o yubioath-desktop.msi
|
light fragment.wixobj yubioath-desktop.wixobj -ext WixUIExtension -ext WixUtilExtension -o yubioath-desktop.msi
|
||||||
@ -70,6 +71,7 @@ jobs:
|
|||||||
mkdir $dest
|
mkdir $dest
|
||||||
mv build\windows\runner\Release\* $dest\
|
mv build\windows\runner\Release\* $dest\
|
||||||
mv yubioath-desktop.msi deploy
|
mv yubioath-desktop.msi deploy
|
||||||
|
mv resources deploy
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v1
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Autogenerated from Pigeon (v3.0.3), do not edit directly.
|
// Autogenerated from Pigeon (v3.1.6), do not edit directly.
|
||||||
// See also: https://pub.dev/packages/pigeon
|
// See also: https://pub.dev/packages/pigeon
|
||||||
|
|
||||||
package com.yubico.authenticator.api;
|
package com.yubico.authenticator.api;
|
||||||
|
10
create-dmg.sh
Normal file
10
create-dmg.sh
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
create-dmg \
|
||||||
|
--volname "Yubico Authenticator" \
|
||||||
|
--background "resources/icons/yubico-msi-background.png" \
|
||||||
|
--window-pos 200 120 \
|
||||||
|
--window-size 800 400 \
|
||||||
|
--icon-size 100 \
|
||||||
|
--icon "Yubico Authenticator.app" 200 190 \
|
||||||
|
--app-drop-link 600 185 \
|
||||||
|
"yubioath-desktop.dmg" \
|
||||||
|
"source_folder/"
|
@ -280,7 +280,7 @@ class UsbDeviceNode(AbstractDeviceNode):
|
|||||||
super().__init__(device, info)
|
super().__init__(device, info)
|
||||||
|
|
||||||
def _supports_connection(self, conn_type):
|
def _supports_connection(self, conn_type):
|
||||||
return self._device.supports_connection(conn_type)
|
return self._device.pid.supports_connection(conn_type)
|
||||||
|
|
||||||
def _create_connection(self, conn_type):
|
def _create_connection(self, conn_type):
|
||||||
connection = self._device.open_connection(conn_type)
|
connection = self._device.open_connection(conn_type)
|
||||||
@ -327,7 +327,9 @@ class ReaderDeviceNode(AbstractDeviceNode):
|
|||||||
with self._device.open_connection(SmartCardConnection) as conn:
|
with self._device.open_connection(SmartCardConnection) as conn:
|
||||||
return dict(self._read_data(conn), present=True)
|
return dict(self._read_data(conn), present=True)
|
||||||
except NoCardException:
|
except NoCardException:
|
||||||
return dict(present=False)
|
return dict(present=False, status="no-card")
|
||||||
|
except ValueError:
|
||||||
|
return dict(present=False, status="unknown-device")
|
||||||
|
|
||||||
@child
|
@child
|
||||||
def ccid(self):
|
def ccid(self):
|
||||||
|
@ -171,7 +171,7 @@ class Ctap2Node(RpcNode):
|
|||||||
self.ctap = Ctap2(connection)
|
self.ctap = Ctap2(connection)
|
||||||
if target != _ctap_id(self.ctap):
|
if target != _ctap_id(self.ctap):
|
||||||
raise ValueError("Re-inserted YubiKey does not match initial device")
|
raise ValueError("Re-inserted YubiKey does not match initial device")
|
||||||
self.ctap.reset(event)
|
self.ctap.reset(event=event)
|
||||||
self._info = self.ctap.get_info()
|
self._info = self.ctap.get_info()
|
||||||
self._auth_blocked = False
|
self._auth_blocked = False
|
||||||
self._token = None
|
self._token = None
|
||||||
@ -343,7 +343,7 @@ class FingerprintsNode(RpcNode):
|
|||||||
template_id = None
|
template_id = None
|
||||||
while template_id is None:
|
while template_id is None:
|
||||||
try:
|
try:
|
||||||
template_id = enroller.capture(event)
|
template_id = enroller.capture(event=event)
|
||||||
signal("capture", dict(remaining=enroller.remaining))
|
signal("capture", dict(remaining=enroller.remaining))
|
||||||
except CaptureError as e:
|
except CaptureError as e:
|
||||||
signal("capture-error", dict(code=e.code))
|
signal("capture-error", dict(code=e.code))
|
||||||
|
@ -32,7 +32,7 @@ from yubikit.core.smartcard import SmartCardConnection
|
|||||||
from yubikit.core.otp import OtpConnection
|
from yubikit.core.otp import OtpConnection
|
||||||
from yubikit.core.fido import FidoConnection
|
from yubikit.core.fido import FidoConnection
|
||||||
from yubikit.management import ManagementSession, DeviceConfig, Mode
|
from yubikit.management import ManagementSession, DeviceConfig, Mode
|
||||||
from ykman.device import connect_to_device
|
from ykman.device import list_all_devices
|
||||||
from dataclasses import asdict
|
from dataclasses import asdict
|
||||||
from time import sleep
|
from time import sleep
|
||||||
import logging
|
import logging
|
||||||
@ -78,12 +78,10 @@ class ManagementNode(RpcNode):
|
|||||||
logger.debug("Waiting for device to re-appear...")
|
logger.debug("Waiting for device to re-appear...")
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
sleep(0.2) # Always sleep initially
|
sleep(0.2) # Always sleep initially
|
||||||
try:
|
for dev, info in list_all_devices(connection_types):
|
||||||
conn = connect_to_device(serial, connection_types)[0]
|
if info.serial == serial:
|
||||||
conn.close()
|
return
|
||||||
break
|
logger.debug("Not found, sleep...")
|
||||||
except ValueError:
|
|
||||||
logger.debug("Not found, sleep...")
|
|
||||||
else:
|
else:
|
||||||
logger.warning("Timed out waiting for device")
|
logger.warning("Timed out waiting for device")
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import subprocess
|
|||||||
import tempfile
|
import tempfile
|
||||||
from mss.exception import ScreenShotError
|
from mss.exception import ScreenShotError
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
import numpy.core.multiarray # noqa
|
||||||
|
|
||||||
|
|
||||||
def _capture_screen():
|
def _capture_screen():
|
||||||
@ -41,6 +42,6 @@ def scan_qr(image_data=None):
|
|||||||
img = _capture_screen()
|
img = _capture_screen()
|
||||||
|
|
||||||
result = zxingcpp.read_barcode(img)
|
result = zxingcpp.read_barcode(img)
|
||||||
if result.valid:
|
if result and result.valid:
|
||||||
return result.text
|
return result.text
|
||||||
return None
|
return None
|
||||||
|
350
helper/poetry.lock
generated
350
helper/poetry.lock
generated
@ -30,7 +30,7 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cffi"
|
name = "cffi"
|
||||||
version = "1.15.0"
|
version = "1.15.1"
|
||||||
description = "Foreign Function Interface for Python calling C code."
|
description = "Foreign Function Interface for Python calling C code."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@ -60,7 +60,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cryptography"
|
name = "cryptography"
|
||||||
version = "37.0.2"
|
version = "37.0.4"
|
||||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@ -101,7 +101,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "importlib-metadata"
|
name = "importlib-metadata"
|
||||||
version = "4.11.4"
|
version = "4.12.0"
|
||||||
description = "Read metadata from Python packages"
|
description = "Read metadata from Python packages"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@ -113,7 +113,7 @@ zipp = ">=0.5"
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
|
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
|
||||||
perf = ["ipython"]
|
perf = ["ipython"]
|
||||||
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"]
|
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
@ -174,7 +174,7 @@ python-versions = ">=3.5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "numpy"
|
name = "numpy"
|
||||||
version = "1.22.4"
|
version = "1.23.0"
|
||||||
description = "NumPy is the fundamental package for array computing with Python."
|
description = "NumPy is the fundamental package for array computing with Python."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@ -204,14 +204,14 @@ future = "*"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pillow"
|
name = "pillow"
|
||||||
version = "9.1.1"
|
version = "9.2.0"
|
||||||
description = "Python Imaging Library (Fork)"
|
description = "Python Imaging Library (Fork)"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinx-rtd-theme (>=1.0)", "sphinxext-opengraph"]
|
docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"]
|
||||||
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
|
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -370,7 +370,7 @@ pywin32 = {version = ">=223", markers = "sys_platform == \"win32\""}
|
|||||||
type = "git"
|
type = "git"
|
||||||
url = "https://github.com/Yubico/yubikey-manager.git"
|
url = "https://github.com/Yubico/yubikey-manager.git"
|
||||||
reference = "next"
|
reference = "next"
|
||||||
resolved_reference = "7e0c9e586e507f0dd021a9b7beacc621bfee7baa"
|
resolved_reference = "32612d177db0d8dd768679ce26c4e509d10f2a97"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zipp"
|
name = "zipp"
|
||||||
@ -386,7 +386,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zxing-cpp"
|
name = "zxing-cpp"
|
||||||
version = "1.3.0"
|
version = "1.4.0"
|
||||||
description = "Python bindings for the zxing-cpp barcode library"
|
description = "Python bindings for the zxing-cpp barcode library"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@ -414,56 +414,70 @@ attrs = [
|
|||||||
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
|
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
|
||||||
]
|
]
|
||||||
cffi = [
|
cffi = [
|
||||||
{file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"},
|
{file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
|
||||||
{file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"},
|
{file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
|
||||||
{file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"},
|
{file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"},
|
||||||
{file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"},
|
{file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"},
|
||||||
{file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"},
|
{file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"},
|
||||||
{file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"},
|
{file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"},
|
||||||
{file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"},
|
{file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"},
|
||||||
{file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"},
|
{file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"},
|
||||||
{file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"},
|
{file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"},
|
||||||
{file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"},
|
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"},
|
||||||
{file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"},
|
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"},
|
||||||
{file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"},
|
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"},
|
||||||
{file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"},
|
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"},
|
||||||
{file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"},
|
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"},
|
||||||
{file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"},
|
{file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"},
|
||||||
{file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"},
|
{file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"},
|
||||||
{file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"},
|
{file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"},
|
||||||
{file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"},
|
{file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"},
|
||||||
{file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"},
|
{file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"},
|
||||||
{file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"},
|
{file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"},
|
||||||
{file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"},
|
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"},
|
||||||
{file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"},
|
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"},
|
||||||
{file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"},
|
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"},
|
||||||
{file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"},
|
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"},
|
||||||
{file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"},
|
{file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"},
|
||||||
{file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"},
|
{file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"},
|
||||||
{file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"},
|
{file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"},
|
||||||
{file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"},
|
{file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"},
|
||||||
{file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"},
|
{file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"},
|
||||||
{file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"},
|
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"},
|
||||||
{file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"},
|
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"},
|
||||||
{file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"},
|
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"},
|
||||||
{file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"},
|
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"},
|
||||||
{file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"},
|
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"},
|
||||||
{file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"},
|
{file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"},
|
||||||
{file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"},
|
{file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"},
|
||||||
{file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"},
|
{file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"},
|
||||||
{file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"},
|
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"},
|
||||||
{file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"},
|
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"},
|
||||||
{file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"},
|
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"},
|
||||||
{file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"},
|
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"},
|
||||||
{file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"},
|
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"},
|
||||||
{file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"},
|
{file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"},
|
||||||
{file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"},
|
{file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"},
|
||||||
{file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"},
|
{file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"},
|
||||||
{file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"},
|
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"},
|
||||||
{file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"},
|
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"},
|
||||||
{file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"},
|
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"},
|
||||||
{file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"},
|
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"},
|
||||||
{file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"},
|
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"},
|
||||||
|
{file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"},
|
||||||
|
{file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"},
|
||||||
|
{file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"},
|
||||||
|
{file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"},
|
||||||
|
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"},
|
||||||
|
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"},
|
||||||
|
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"},
|
||||||
|
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"},
|
||||||
|
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"},
|
||||||
|
{file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"},
|
||||||
|
{file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"},
|
||||||
|
{file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"},
|
||||||
|
{file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"},
|
||||||
|
{file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
|
||||||
]
|
]
|
||||||
click = [
|
click = [
|
||||||
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
||||||
@ -474,28 +488,28 @@ colorama = [
|
|||||||
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
|
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
|
||||||
]
|
]
|
||||||
cryptography = [
|
cryptography = [
|
||||||
{file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:ef15c2df7656763b4ff20a9bc4381d8352e6640cfeb95c2972c38ef508e75181"},
|
{file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"},
|
||||||
{file = "cryptography-37.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3c81599befb4d4f3d7648ed3217e00d21a9341a9a688ecdd615ff72ffbed7336"},
|
{file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"},
|
||||||
{file = "cryptography-37.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2bd1096476aaac820426239ab534b636c77d71af66c547b9ddcd76eb9c79e004"},
|
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"},
|
||||||
{file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:31fe38d14d2e5f787e0aecef831457da6cec68e0bb09a35835b0b44ae8b988fe"},
|
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5"},
|
||||||
{file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:093cb351031656d3ee2f4fa1be579a8c69c754cf874206be1d4cf3b542042804"},
|
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b"},
|
||||||
{file = "cryptography-37.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59b281eab51e1b6b6afa525af2bd93c16d49358404f814fe2c2410058623928c"},
|
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8"},
|
||||||
{file = "cryptography-37.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:0cc20f655157d4cfc7bada909dc5cc228211b075ba8407c46467f63597c78178"},
|
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280"},
|
||||||
{file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f8ec91983e638a9bcd75b39f1396e5c0dc2330cbd9ce4accefe68717e6779e0a"},
|
{file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3"},
|
||||||
{file = "cryptography-37.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:46f4c544f6557a2fefa7ac8ac7d1b17bf9b647bd20b16decc8fbcab7117fbc15"},
|
{file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59"},
|
||||||
{file = "cryptography-37.0.2-cp36-abi3-win32.whl", hash = "sha256:731c8abd27693323b348518ed0e0705713a36d79fdbd969ad968fbef0979a7e0"},
|
{file = "cryptography-37.0.4-cp36-abi3-win32.whl", hash = "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157"},
|
||||||
{file = "cryptography-37.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:471e0d70201c069f74c837983189949aa0d24bb2d751b57e26e3761f2f782b8d"},
|
{file = "cryptography-37.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327"},
|
||||||
{file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a68254dd88021f24a68b613d8c51d5c5e74d735878b9e32cc0adf19d1f10aaf9"},
|
{file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b"},
|
||||||
{file = "cryptography-37.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:a7d5137e556cc0ea418dca6186deabe9129cee318618eb1ffecbd35bee55ddc1"},
|
{file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"},
|
||||||
{file = "cryptography-37.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aeaba7b5e756ea52c8861c133c596afe93dd716cbcacae23b80bc238202dc023"},
|
{file = "cryptography-37.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67"},
|
||||||
{file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95e590dd70642eb2079d280420a888190aa040ad20f19ec8c6e097e38aa29e06"},
|
{file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d"},
|
||||||
{file = "cryptography-37.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1b9362d34363f2c71b7853f6251219298124aa4cc2075ae2932e64c91a3e2717"},
|
{file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282"},
|
||||||
{file = "cryptography-37.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e53258e69874a306fcecb88b7534d61820db8a98655662a3dd2ec7f1afd9132f"},
|
{file = "cryptography-37.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa"},
|
||||||
{file = "cryptography-37.0.2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:1f3bfbd611db5cb58ca82f3deb35e83af34bb8cf06043fa61500157d50a70982"},
|
{file = "cryptography-37.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441"},
|
||||||
{file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:419c57d7b63f5ec38b1199a9521d77d7d1754eb97827bbb773162073ccd8c8d4"},
|
{file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596"},
|
||||||
{file = "cryptography-37.0.2-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:dc26bb134452081859aa21d4990474ddb7e863aa39e60d1592800a8865a702de"},
|
{file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a"},
|
||||||
{file = "cryptography-37.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b8398b3d0efc420e777c40c16764d6870bcef2eb383df9c6dbb9ffe12c64452"},
|
{file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"},
|
||||||
{file = "cryptography-37.0.2.tar.gz", hash = "sha256:f224ad253cc9cea7568f49077007d2263efa57396a2f2f78114066fd54b5c68e"},
|
{file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"},
|
||||||
]
|
]
|
||||||
fido2 = [
|
fido2 = [
|
||||||
{file = "fido2-1.0.0-py3-none-any.whl", hash = "sha256:dce13d739b8e0df30505b33f5fd2868fad20f3b309acacce72e5f2d1b0c58761"},
|
{file = "fido2-1.0.0-py3-none-any.whl", hash = "sha256:dce13d739b8e0df30505b33f5fd2868fad20f3b309acacce72e5f2d1b0c58761"},
|
||||||
@ -505,8 +519,8 @@ future = [
|
|||||||
{file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
|
{file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
|
||||||
]
|
]
|
||||||
importlib-metadata = [
|
importlib-metadata = [
|
||||||
{file = "importlib_metadata-4.11.4-py3-none-any.whl", hash = "sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec"},
|
{file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"},
|
||||||
{file = "importlib_metadata-4.11.4.tar.gz", hash = "sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700"},
|
{file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"},
|
||||||
]
|
]
|
||||||
iniconfig = [
|
iniconfig = [
|
||||||
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
|
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
|
||||||
@ -529,28 +543,28 @@ mss = [
|
|||||||
{file = "mss-6.1.0.tar.gz", hash = "sha256:aebd069f3e05667fe9c7b9fa4b1771fe42a4710ce1058ce0236936ce06fa5394"},
|
{file = "mss-6.1.0.tar.gz", hash = "sha256:aebd069f3e05667fe9c7b9fa4b1771fe42a4710ce1058ce0236936ce06fa5394"},
|
||||||
]
|
]
|
||||||
numpy = [
|
numpy = [
|
||||||
{file = "numpy-1.22.4-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:ba9ead61dfb5d971d77b6c131a9dbee62294a932bf6a356e48c75ae684e635b3"},
|
{file = "numpy-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58bfd40eb478f54ff7a5710dd61c8097e169bc36cc68333d00a9bcd8def53b38"},
|
||||||
{file = "numpy-1.22.4-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:1ce7ab2053e36c0a71e7a13a7475bd3b1f54750b4b433adc96313e127b870887"},
|
{file = "numpy-1.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:196cd074c3f97c4121601790955f915187736f9cf458d3ee1f1b46aff2b1ade0"},
|
||||||
{file = "numpy-1.22.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7228ad13744f63575b3a972d7ee4fd61815b2879998e70930d4ccf9ec721dce0"},
|
{file = "numpy-1.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1d88ef79e0a7fa631bb2c3dda1ea46b32b1fe614e10fedd611d3d5398447f2f"},
|
||||||
{file = "numpy-1.22.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43a8ca7391b626b4c4fe20aefe79fec683279e31e7c79716863b4b25021e0e74"},
|
{file = "numpy-1.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d54b3b828d618a19779a84c3ad952e96e2c2311b16384e973e671aa5be1f6187"},
|
||||||
{file = "numpy-1.22.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a911e317e8c826ea632205e63ed8507e0dc877dcdc49744584dfc363df9ca08c"},
|
{file = "numpy-1.23.0-cp310-cp310-win32.whl", hash = "sha256:2b2da66582f3a69c8ce25ed7921dcd8010d05e59ac8d89d126a299be60421171"},
|
||||||
{file = "numpy-1.22.4-cp310-cp310-win32.whl", hash = "sha256:9ce7df0abeabe7fbd8ccbf343dc0db72f68549856b863ae3dd580255d009648e"},
|
{file = "numpy-1.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:97a76604d9b0e79f59baeca16593c711fddb44936e40310f78bfef79ee9a835f"},
|
||||||
{file = "numpy-1.22.4-cp310-cp310-win_amd64.whl", hash = "sha256:3e1ffa4748168e1cc8d3cde93f006fe92b5421396221a02f2274aab6ac83b077"},
|
{file = "numpy-1.23.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d8cc87bed09de55477dba9da370c1679bd534df9baa171dd01accbb09687dac3"},
|
||||||
{file = "numpy-1.22.4-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:59d55e634968b8f77d3fd674a3cf0b96e85147cd6556ec64ade018f27e9479e1"},
|
{file = "numpy-1.23.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f0f18804df7370571fb65db9b98bf1378172bd4e962482b857e612d1fec0f53e"},
|
||||||
{file = "numpy-1.22.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c1d937820db6e43bec43e8d016b9b3165dcb42892ea9f106c70fb13d430ffe72"},
|
{file = "numpy-1.23.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac86f407873b952679f5f9e6c0612687e51547af0e14ddea1eedfcb22466babd"},
|
||||||
{file = "numpy-1.22.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4c5d5eb2ec8da0b4f50c9a843393971f31f1d60be87e0fb0917a49133d257d6"},
|
{file = "numpy-1.23.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae8adff4172692ce56233db04b7ce5792186f179c415c37d539c25de7298d25d"},
|
||||||
{file = "numpy-1.22.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64f56fc53a2d18b1924abd15745e30d82a5782b2cab3429aceecc6875bd5add0"},
|
{file = "numpy-1.23.0-cp38-cp38-win32.whl", hash = "sha256:fe8b9683eb26d2c4d5db32cd29b38fdcf8381324ab48313b5b69088e0e355379"},
|
||||||
{file = "numpy-1.22.4-cp38-cp38-win32.whl", hash = "sha256:fb7a980c81dd932381f8228a426df8aeb70d59bbcda2af075b627bbc50207cba"},
|
{file = "numpy-1.23.0-cp38-cp38-win_amd64.whl", hash = "sha256:5043bcd71fcc458dfb8a0fc5509bbc979da0131b9d08e3d5f50fb0bbb36f169a"},
|
||||||
{file = "numpy-1.22.4-cp38-cp38-win_amd64.whl", hash = "sha256:e96d7f3096a36c8754207ab89d4b3282ba7b49ea140e4973591852c77d09eb76"},
|
{file = "numpy-1.23.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1c29b44905af288b3919803aceb6ec7fec77406d8b08aaa2e8b9e63d0fe2f160"},
|
||||||
{file = "numpy-1.22.4-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:4c6036521f11a731ce0648f10c18ae66d7143865f19f7299943c985cdc95afb5"},
|
{file = "numpy-1.23.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:98e8e0d8d69ff4d3fa63e6c61e8cfe2d03c29b16b58dbef1f9baa175bbed7860"},
|
||||||
{file = "numpy-1.22.4-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:b89bf9b94b3d624e7bb480344e91f68c1c6c75f026ed6755955117de00917a7c"},
|
{file = "numpy-1.23.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a506cacf2be3a74ead5467aee97b81fca00c9c4c8b3ba16dbab488cd99ba10"},
|
||||||
{file = "numpy-1.22.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2d487e06ecbf1dc2f18e7efce82ded4f705f4bd0cd02677ffccfb39e5c284c7e"},
|
{file = "numpy-1.23.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:092f5e6025813e64ad6d1b52b519165d08c730d099c114a9247c9bb635a2a450"},
|
||||||
{file = "numpy-1.22.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3eb268dbd5cfaffd9448113539e44e2dd1c5ca9ce25576f7c04a5453edc26fa"},
|
{file = "numpy-1.23.0-cp39-cp39-win32.whl", hash = "sha256:d6ca8dabe696c2785d0c8c9b0d8a9b6e5fdbe4f922bde70d57fa1a2848134f95"},
|
||||||
{file = "numpy-1.22.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37431a77ceb9307c28382c9773da9f306435135fae6b80b62a11c53cfedd8802"},
|
{file = "numpy-1.23.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc431493df245f3c627c0c05c2bd134535e7929dbe2e602b80e42bf52ff760bc"},
|
||||||
{file = "numpy-1.22.4-cp39-cp39-win32.whl", hash = "sha256:cc7f00008eb7d3f2489fca6f334ec19ca63e31371be28fd5dad955b16ec285bd"},
|
{file = "numpy-1.23.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f9c3fc2adf67762c9fe1849c859942d23f8d3e0bee7b5ed3d4a9c3eeb50a2f07"},
|
||||||
{file = "numpy-1.22.4-cp39-cp39-win_amd64.whl", hash = "sha256:f0725df166cf4785c0bc4cbfb320203182b1ecd30fee6e541c8752a92df6aa32"},
|
{file = "numpy-1.23.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0d2094e8f4d760500394d77b383a1b06d3663e8892cdf5df3c592f55f3bff66"},
|
||||||
{file = "numpy-1.22.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0791fbd1e43bf74b3502133207e378901272f3c156c4df4954cad833b1380207"},
|
{file = "numpy-1.23.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:94b170b4fa0168cd6be4becf37cb5b127bd12a795123984385b8cd4aca9857e5"},
|
||||||
{file = "numpy-1.22.4.zip", hash = "sha256:425b390e4619f58d8526b3dcf656dde069133ae5c240229821f01b5f44ea07af"},
|
{file = "numpy-1.23.0.tar.gz", hash = "sha256:bd3fa4fe2e38533d5336e1272fc4e765cabbbde144309ccee8675509d5cd7b05"},
|
||||||
]
|
]
|
||||||
packaging = [
|
packaging = [
|
||||||
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
|
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
|
||||||
@ -560,44 +574,64 @@ pefile = [
|
|||||||
{file = "pefile-2022.5.30.tar.gz", hash = "sha256:a5488a3dd1fd021ce33f969780b88fe0f7eebb76eb20996d7318f307612a045b"},
|
{file = "pefile-2022.5.30.tar.gz", hash = "sha256:a5488a3dd1fd021ce33f969780b88fe0f7eebb76eb20996d7318f307612a045b"},
|
||||||
]
|
]
|
||||||
pillow = [
|
pillow = [
|
||||||
{file = "Pillow-9.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:42dfefbef90eb67c10c45a73a9bc1599d4dac920f7dfcbf4ec6b80cb620757fe"},
|
{file = "Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb"},
|
||||||
{file = "Pillow-9.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffde4c6fabb52891d81606411cbfaf77756e3b561b566efd270b3ed3791fde4e"},
|
{file = "Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f"},
|
||||||
{file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c857532c719fb30fafabd2371ce9b7031812ff3889d75273827633bca0c4602"},
|
{file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5"},
|
||||||
{file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59789a7d06c742e9d13b883d5e3569188c16acb02eeed2510fd3bfdbc1bd1530"},
|
{file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c"},
|
||||||
{file = "Pillow-9.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d45dbe4b21a9679c3e8b3f7f4f42a45a7d3ddff8a4a16109dff0e1da30a35b2"},
|
{file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1"},
|
||||||
{file = "Pillow-9.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e9ed59d1b6ee837f4515b9584f3d26cf0388b742a11ecdae0d9237a94505d03a"},
|
{file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58"},
|
||||||
{file = "Pillow-9.1.1-cp310-cp310-win32.whl", hash = "sha256:b3fe2ff1e1715d4475d7e2c3e8dabd7c025f4410f79513b4ff2de3d51ce0fa9c"},
|
{file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544"},
|
||||||
{file = "Pillow-9.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5b650dbbc0969a4e226d98a0b440c2f07a850896aed9266b6fedc0f7e7834108"},
|
{file = "Pillow-9.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e"},
|
||||||
{file = "Pillow-9.1.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:0b4d5ad2cd3a1f0d1df882d926b37dbb2ab6c823ae21d041b46910c8f8cd844b"},
|
{file = "Pillow-9.2.0-cp310-cp310-win32.whl", hash = "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28"},
|
||||||
{file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9370d6744d379f2de5d7fa95cdbd3a4d92f0b0ef29609b4b1687f16bc197063d"},
|
{file = "Pillow-9.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d"},
|
||||||
{file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b761727ed7d593e49671d1827044b942dd2f4caae6e51bab144d4accf8244a84"},
|
{file = "Pillow-9.2.0-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:408673ed75594933714482501fe97e055a42996087eeca7e5d06e33218d05aa8"},
|
||||||
{file = "Pillow-9.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a66fe50386162df2da701b3722781cbe90ce043e7d53c1fd6bd801bca6b48d4"},
|
{file = "Pillow-9.2.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:727dd1389bc5cb9827cbd1f9d40d2c2a1a0c9b32dd2261db522d22a604a6eec9"},
|
||||||
{file = "Pillow-9.1.1-cp37-cp37m-win32.whl", hash = "sha256:2b291cab8a888658d72b575a03e340509b6b050b62db1f5539dd5cd18fd50578"},
|
{file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004"},
|
||||||
{file = "Pillow-9.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1d4331aeb12f6b3791911a6da82de72257a99ad99726ed6b63f481c0184b6fb9"},
|
{file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0"},
|
||||||
{file = "Pillow-9.1.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8844217cdf66eabe39567118f229e275f0727e9195635a15e0e4b9227458daaf"},
|
{file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4"},
|
||||||
{file = "Pillow-9.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b6617221ff08fbd3b7a811950b5c3f9367f6e941b86259843eab77c8e3d2b56b"},
|
{file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c"},
|
||||||
{file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20d514c989fa28e73a5adbddd7a171afa5824710d0ab06d4e1234195d2a2e546"},
|
{file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a"},
|
||||||
{file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:088df396b047477dd1bbc7de6e22f58400dae2f21310d9e2ec2933b2ef7dfa4f"},
|
{file = "Pillow-9.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1"},
|
||||||
{file = "Pillow-9.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53c27bd452e0f1bc4bfed07ceb235663a1df7c74df08e37fd6b03eb89454946a"},
|
{file = "Pillow-9.2.0-cp311-cp311-win32.whl", hash = "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf"},
|
||||||
{file = "Pillow-9.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3f6c1716c473ebd1649663bf3b42702d0d53e27af8b64642be0dd3598c761fb1"},
|
{file = "Pillow-9.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c"},
|
||||||
{file = "Pillow-9.1.1-cp38-cp38-win32.whl", hash = "sha256:c67db410508b9de9c4694c57ed754b65a460e4812126e87f5052ecf23a011a54"},
|
{file = "Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069"},
|
||||||
{file = "Pillow-9.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:f054b020c4d7e9786ae0404278ea318768eb123403b18453e28e47cdb7a0a4bf"},
|
{file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f"},
|
||||||
{file = "Pillow-9.1.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:c17770a62a71718a74b7548098a74cd6880be16bcfff5f937f900ead90ca8e92"},
|
{file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8"},
|
||||||
{file = "Pillow-9.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3f6a6034140e9e17e9abc175fc7a266a6e63652028e157750bd98e804a8ed9a"},
|
{file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b"},
|
||||||
{file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f372d0f08eff1475ef426344efe42493f71f377ec52237bf153c5713de987251"},
|
{file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467"},
|
||||||
{file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09e67ef6e430f90caa093528bd758b0616f8165e57ed8d8ce014ae32df6a831d"},
|
{file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59"},
|
||||||
{file = "Pillow-9.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66daa16952d5bf0c9d5389c5e9df562922a59bd16d77e2a276e575d32e38afd1"},
|
{file = "Pillow-9.2.0-cp37-cp37m-win32.whl", hash = "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc"},
|
||||||
{file = "Pillow-9.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d78ca526a559fb84faaaf84da2dd4addef5edb109db8b81677c0bb1aad342601"},
|
{file = "Pillow-9.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d"},
|
||||||
{file = "Pillow-9.1.1-cp39-cp39-win32.whl", hash = "sha256:55e74faf8359ddda43fee01bffbc5bd99d96ea508d8a08c527099e84eb708f45"},
|
{file = "Pillow-9.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14"},
|
||||||
{file = "Pillow-9.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c150dbbb4a94ea4825d1e5f2c5501af7141ea95825fadd7829f9b11c97aaf6c"},
|
{file = "Pillow-9.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3"},
|
||||||
{file = "Pillow-9.1.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:769a7f131a2f43752455cc72f9f7a093c3ff3856bf976c5fb53a59d0ccc704f6"},
|
{file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402"},
|
||||||
{file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:488f3383cf5159907d48d32957ac6f9ea85ccdcc296c14eca1a4e396ecc32098"},
|
{file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f"},
|
||||||
{file = "Pillow-9.1.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b525a356680022b0af53385944026d3486fc8c013638cf9900eb87c866afb4c"},
|
{file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8"},
|
||||||
{file = "Pillow-9.1.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6e760cf01259a1c0a50f3c845f9cad1af30577fd8b670339b1659c6d0e7a41dd"},
|
{file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff"},
|
||||||
{file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4165205a13b16a29e1ac57efeee6be2dfd5b5408122d59ef2145bc3239fa340"},
|
{file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1"},
|
||||||
{file = "Pillow-9.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937a54e5694684f74dcbf6e24cc453bfc5b33940216ddd8f4cd8f0f79167f765"},
|
{file = "Pillow-9.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76"},
|
||||||
{file = "Pillow-9.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:baf3be0b9446a4083cc0c5bb9f9c964034be5374b5bc09757be89f5d2fa247b8"},
|
{file = "Pillow-9.2.0-cp38-cp38-win32.whl", hash = "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f"},
|
||||||
{file = "Pillow-9.1.1.tar.gz", hash = "sha256:7502539939b53d7565f3d11d87c78e7ec900d3c72945d4ee0e2f250d598309a0"},
|
{file = "Pillow-9.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8"},
|
||||||
|
{file = "Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc"},
|
||||||
|
{file = "Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da"},
|
||||||
|
{file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4"},
|
||||||
|
{file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c"},
|
||||||
|
{file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421"},
|
||||||
|
{file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20"},
|
||||||
|
{file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60"},
|
||||||
|
{file = "Pillow-9.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4"},
|
||||||
|
{file = "Pillow-9.2.0-cp39-cp39-win32.whl", hash = "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885"},
|
||||||
|
{file = "Pillow-9.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4"},
|
||||||
|
{file = "Pillow-9.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3"},
|
||||||
|
{file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb"},
|
||||||
|
{file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be"},
|
||||||
|
{file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd"},
|
||||||
|
{file = "Pillow-9.2.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013"},
|
||||||
|
{file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490"},
|
||||||
|
{file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac"},
|
||||||
|
{file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e"},
|
||||||
|
{file = "Pillow-9.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927"},
|
||||||
|
{file = "Pillow-9.2.0.tar.gz", hash = "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04"},
|
||||||
]
|
]
|
||||||
pluggy = [
|
pluggy = [
|
||||||
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||||
@ -677,19 +711,19 @@ zipp = [
|
|||||||
{file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"},
|
{file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"},
|
||||||
]
|
]
|
||||||
zxing-cpp = [
|
zxing-cpp = [
|
||||||
{file = "zxing-cpp-1.3.0.tar.gz", hash = "sha256:5f30545afad01a278fc8c17efae11d82e36f8c2caa87c89096aec5a8d69103b2"},
|
{file = "zxing-cpp-1.4.0.tar.gz", hash = "sha256:3d3ec36954ecbf9b0f633dab4b8cebcf0059d8a27f7a5969c4e41a308111af38"},
|
||||||
{file = "zxing_cpp-1.3.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3a6e183b6c0aae9378f674f9e7714a39482595915cf15198d10b9ba8c33b25f"},
|
{file = "zxing_cpp-1.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25b11d77cf6b9f7405af3ed6bacf4a6e0756ea74dfda7040ff53e7c58f352b05"},
|
||||||
{file = "zxing_cpp-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88eadb723d20655caf81a6ba6ef64d74a266f57cbd782da82736c52a61a73fa5"},
|
{file = "zxing_cpp-1.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1f849205237d4bda462d0a4b745e72494f825e5b6b06581e05b58d34d9869aa"},
|
||||||
{file = "zxing_cpp-1.3.0-cp310-cp310-win32.whl", hash = "sha256:15fb165ada1730ab0d96b67eb2d9827870d9ae534686e27541f3b3add15b96d7"},
|
{file = "zxing_cpp-1.4.0-cp310-cp310-win32.whl", hash = "sha256:76e9777d943af3c51b6406b323b3f28cbf9e40cc65b53cf847fda08295f18e48"},
|
||||||
{file = "zxing_cpp-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:8dbb17a31ee1ac2c946a96e83b170ecefbc87a52b9c35b41809d9afff77d8879"},
|
{file = "zxing_cpp-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:234d672e34e607ffc8e06639e79c8e1bf2ddb7c249134a6836569e92a2f2dd64"},
|
||||||
{file = "zxing_cpp-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:31578db20ba0668e010cb62e4718cb86f47563ec5122e29a0746651ff1e13735"},
|
{file = "zxing_cpp-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1f66c61e43163740c59c58880c3a8c41ebd2109573c0494f255c9c96134e8c"},
|
||||||
{file = "zxing_cpp-1.3.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9253a3b6c8c143f3c22d172922226b10c8cc319d2554c73107fefce7e263daaa"},
|
{file = "zxing_cpp-1.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9418e1bd0775820a4933b60007b7f8a177e4ddd23692c1aaed2348fafc0a8e01"},
|
||||||
{file = "zxing_cpp-1.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:250afd201f08bd1be8fd349766e32ef184a463b616c13102b2f80a4422695957"},
|
{file = "zxing_cpp-1.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bc1e48ddfd6692d183782f091fbf54e5e1d36d0070822b1eab14cfb580b1625"},
|
||||||
{file = "zxing_cpp-1.3.0-cp38-cp38-win32.whl", hash = "sha256:d2891dfba5c53b913867e7b01b8b430d801e15e54f53b3c05b9645dc824dfed3"},
|
{file = "zxing_cpp-1.4.0-cp38-cp38-win32.whl", hash = "sha256:4f340b6907780e8eb0e6473fec43ea145c4dd3275e3c21d6f887c0e28e114f29"},
|
||||||
{file = "zxing_cpp-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:6201e60cbefbc8de90c5f18e6e25c3cb1be19be8f369bf4dad3ab910b954f29d"},
|
{file = "zxing_cpp-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:71772f81c4602133b2dba6a1107339ed965725001ce9a4caaf772598110351a1"},
|
||||||
{file = "zxing_cpp-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:44467984c1a65a332c8656926f30af1752c1ff774c6a030b95572e0a1543b23b"},
|
{file = "zxing_cpp-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:331bec6b0ac8a9b339bc82956c52c022e7b2debfeb9102209483eb7538ed72d4"},
|
||||||
{file = "zxing_cpp-1.3.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0dbb54f8694063376d73be6f7dbddd39f3e7907ab885403d90cff7d518c54f7f"},
|
{file = "zxing_cpp-1.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b0844c6ad3c944452c980a025238ba3fbd3a414fd2c36e2bec1bc5bed03b21e"},
|
||||||
{file = "zxing_cpp-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3cff8a7fe960c2016bc8e217fcf02b9b1ac61b17fc5c0c5158f853088be4ad9"},
|
{file = "zxing_cpp-1.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a770aff618cd00dda3922de2f7085c1f84bbe02f2b6df114d19054ad41c52fb0"},
|
||||||
{file = "zxing_cpp-1.3.0-cp39-cp39-win32.whl", hash = "sha256:f75431cf7cddcb21c267d39a5895831a3c20abfa7676426974652d25b29ae429"},
|
{file = "zxing_cpp-1.4.0-cp39-cp39-win32.whl", hash = "sha256:ebe67de6a4d3c48a5ee52211ecf2003301ab39bd7d7b7dfa72ae80be429cfcf9"},
|
||||||
{file = "zxing_cpp-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:de9dd0a2d01969e9828c5704d709b2559a417fea562bd2f308ebc8d4a9678b5e"},
|
{file = "zxing_cpp-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:d0e8b54b29497ed9238f31ce522ddb0189c0d6c4597787ef2eb823ca9fb42350"},
|
||||||
]
|
]
|
||||||
|
@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
import 'package:yubico_authenticator/android/init.dart' as android;
|
import 'package:yubico_authenticator/android/init.dart' as android;
|
||||||
import 'package:yubico_authenticator/app/views/no_device_screen.dart';
|
import 'package:yubico_authenticator/app/views/device_error_screen.dart';
|
||||||
import 'package:yubico_authenticator/core/state.dart';
|
import 'package:yubico_authenticator/core/state.dart';
|
||||||
import 'package:yubico_authenticator/desktop/init.dart' as desktop;
|
import 'package:yubico_authenticator/desktop/init.dart' as desktop;
|
||||||
import 'package:yubico_authenticator/oath/views/account_list.dart';
|
import 'package:yubico_authenticator/oath/views/account_list.dart';
|
||||||
@ -55,7 +55,7 @@ void main() {
|
|||||||
await tester.pumpWidget(initializedApp);
|
await tester.pumpWidget(initializedApp);
|
||||||
await tester.pump(const Duration(milliseconds: 500));
|
await tester.pump(const Duration(milliseconds: 500));
|
||||||
|
|
||||||
expect(find.byType(NoDeviceScreen), findsNothing,
|
expect(find.byType(DeviceErrorScreen), findsNothing,
|
||||||
reason: 'No YubiKey connected');
|
reason: 'No YubiKey connected');
|
||||||
expect(find.byType(OathScreen), findsOneWidget);
|
expect(find.byType(OathScreen), findsOneWidget);
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Autogenerated from Pigeon (v3.0.3), do not edit directly.
|
// Autogenerated from Pigeon (v3.1.6), do not edit directly.
|
||||||
// See also: https://pub.dev/packages/pigeon
|
// See also: https://pub.dev/packages/pigeon
|
||||||
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
|
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
|
||||||
// @dart = 2.12
|
// @dart = 2.12
|
||||||
|
@ -12,11 +12,11 @@ import '../management/models.dart';
|
|||||||
final _log = Logger('yubikeyDataCommandProvider');
|
final _log = Logger('yubikeyDataCommandProvider');
|
||||||
|
|
||||||
final androidYubikeyProvider =
|
final androidYubikeyProvider =
|
||||||
StateNotifierProvider<_YubikeyProvider, YubiKeyData?>((ref) {
|
StateNotifierProvider<_YubikeyProvider, AsyncValue<YubiKeyData>>((ref) {
|
||||||
return _YubikeyProvider(null, ref);
|
return _YubikeyProvider(const AsyncValue.loading(), ref);
|
||||||
});
|
});
|
||||||
|
|
||||||
class _YubikeyProvider extends StateNotifier<YubiKeyData?> {
|
class _YubikeyProvider extends StateNotifier<AsyncValue<YubiKeyData>> {
|
||||||
final Ref _ref;
|
final Ref _ref;
|
||||||
_YubikeyProvider(super.yubiKeyData, this._ref);
|
_YubikeyProvider(super.yubiKeyData, this._ref);
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ class _YubikeyProvider extends StateNotifier<YubiKeyData?> {
|
|||||||
try {
|
try {
|
||||||
if (input.isEmpty) {
|
if (input.isEmpty) {
|
||||||
_log.debug('Yubikey was detached.');
|
_log.debug('Yubikey was detached.');
|
||||||
state = null;
|
state = const AsyncValue.loading();
|
||||||
|
|
||||||
// reset other providers when YubiKey is removed
|
// reset other providers when YubiKey is removed
|
||||||
_ref.refresh(androidStateProvider);
|
_ref.refresh(androidStateProvider);
|
||||||
@ -49,15 +49,17 @@ class _YubikeyProvider extends StateNotifier<YubiKeyData?> {
|
|||||||
|
|
||||||
// reset oath providers on key change
|
// reset oath providers on key change
|
||||||
var yubiKeyData = YubiKeyData(deviceNode, name, deviceInfo);
|
var yubiKeyData = YubiKeyData(deviceNode, name, deviceInfo);
|
||||||
if (state != yubiKeyData && state != null) {
|
state.whenData((data) {
|
||||||
_ref.refresh(androidStateProvider);
|
if (data != yubiKeyData) {
|
||||||
_ref.refresh(androidCredentialsProvider);
|
_ref.refresh(androidStateProvider);
|
||||||
}
|
_ref.refresh(androidCredentialsProvider);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
state = yubiKeyData;
|
state = AsyncValue.data(yubiKeyData);
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
_log.debug('Invalid data for yubikey: $input. $e');
|
_log.debug('Invalid data for yubikey: $input. $e');
|
||||||
state = null;
|
state = AsyncValue.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,18 +35,17 @@ class _AndroidSubPageNotifier extends CurrentAppNotifier {
|
|||||||
final androidAttachedDevicesProvider =
|
final androidAttachedDevicesProvider =
|
||||||
StateNotifierProvider<AttachedDevicesNotifier, List<DeviceNode>>((ref) {
|
StateNotifierProvider<AttachedDevicesNotifier, List<DeviceNode>>((ref) {
|
||||||
var currentDeviceData = ref.watch(androidDeviceDataProvider);
|
var currentDeviceData = ref.watch(androidDeviceDataProvider);
|
||||||
if (currentDeviceData != null) {
|
List<DeviceNode> devs = currentDeviceData.maybeWhen(
|
||||||
return _AndroidAttachedDevicesNotifier([currentDeviceData.node]);
|
data: (data) => [data.node], orElse: () => []);
|
||||||
}
|
return _AndroidAttachedDevicesNotifier(devs);
|
||||||
return _AndroidAttachedDevicesNotifier([]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
class _AndroidAttachedDevicesNotifier extends AttachedDevicesNotifier {
|
class _AndroidAttachedDevicesNotifier extends AttachedDevicesNotifier {
|
||||||
_AndroidAttachedDevicesNotifier(super.state);
|
_AndroidAttachedDevicesNotifier(super.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
final androidDeviceDataProvider =
|
final androidDeviceDataProvider = Provider<AsyncValue<YubiKeyData>>(
|
||||||
Provider<YubiKeyData?>((ref) => ref.watch(androidYubikeyProvider));
|
(ref) => ref.watch(androidYubikeyProvider));
|
||||||
|
|
||||||
final androidCurrentDeviceProvider =
|
final androidCurrentDeviceProvider =
|
||||||
StateNotifierProvider<CurrentDeviceNotifier, DeviceNode?>((ref) {
|
StateNotifierProvider<CurrentDeviceNotifier, DeviceNode?>((ref) {
|
||||||
|
@ -1,60 +1,67 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
|
import '../widgets/toast.dart';
|
||||||
import 'models.dart';
|
import 'models.dart';
|
||||||
import 'state.dart';
|
|
||||||
|
|
||||||
ScaffoldFeatureController showMessage(
|
void Function() showMessage(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
String message, {
|
String message, {
|
||||||
Duration duration = const Duration(seconds: 1),
|
Duration duration = const Duration(seconds: 2),
|
||||||
}) {
|
}) =>
|
||||||
final width = MediaQuery.of(context).size.width;
|
showToast(context, message, duration: duration);
|
||||||
final narrow = width < 540;
|
|
||||||
return ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
|
||||||
content: Text(message),
|
|
||||||
duration: duration,
|
|
||||||
behavior: narrow ? SnackBarBehavior.fixed : SnackBarBehavior.floating,
|
|
||||||
width: narrow ? null : 400,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> showBottomMenu(
|
Future<void> showBottomMenu(
|
||||||
BuildContext context, List<MenuAction> actions) async {
|
BuildContext context, List<MenuAction> actions) async {
|
||||||
MediaQuery? mediaQuery = context.findAncestorWidgetOfExactType<MediaQuery>();
|
await showBlurDialog(
|
||||||
var width = mediaQuery?.data.size.width ?? 0;
|
|
||||||
await showModalBottomSheet(
|
|
||||||
context: context,
|
context: context,
|
||||||
constraints: width > 540 ? const BoxConstraints(maxWidth: 380) : null,
|
builder: (context) {
|
||||||
builder: (context) => SafeArea(child: _BottomMenu(actions)));
|
return AlertDialog(
|
||||||
|
title: const Text('Options'),
|
||||||
|
contentPadding: const EdgeInsets.only(bottom: 24, top: 4),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: actions
|
||||||
|
.map((a) => ListTile(
|
||||||
|
leading: a.icon,
|
||||||
|
title: Text(a.text),
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
enabled: a.action != null,
|
||||||
|
onTap: a.action == null
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
a.action?.call(context);
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BottomMenu extends ConsumerWidget {
|
Future<T?> showBlurDialog<T>({
|
||||||
final List<MenuAction> actions;
|
required BuildContext context,
|
||||||
const _BottomMenu(this.actions);
|
required Widget Function(BuildContext) builder,
|
||||||
|
RouteSettings? routeSettings,
|
||||||
@override
|
}) =>
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
showGeneralDialog(
|
||||||
// If current device changes, we need to pop back to the main Page.
|
context: context,
|
||||||
ref.listen<DeviceNode?>(currentDeviceProvider, (previous, next) {
|
barrierDismissible: true,
|
||||||
Navigator.of(context).pop();
|
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||||
});
|
barrierColor: Colors.black12,
|
||||||
|
pageBuilder: (ctx, anim1, anim2) => builder(ctx),
|
||||||
return Column(
|
transitionDuration: const Duration(milliseconds: 150),
|
||||||
mainAxisSize: MainAxisSize.min,
|
transitionBuilder: (ctx, anim1, anim2, child) => BackdropFilter(
|
||||||
children: actions
|
filter: ImageFilter.blur(
|
||||||
.map((a) => ListTile(
|
sigmaX: 20 * anim1.value, sigmaY: 20 * anim1.value),
|
||||||
leading: a.icon,
|
child: FadeTransition(
|
||||||
title: Text(a.text),
|
opacity: anim1,
|
||||||
enabled: a.action != null,
|
child: child,
|
||||||
onTap: a.action == null
|
),
|
||||||
? null
|
),
|
||||||
: () {
|
routeSettings: routeSettings,
|
||||||
Navigator.pop(context);
|
|
||||||
a.action?.call(context);
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -175,11 +175,11 @@ abstract class _YubiKeyData implements YubiKeyData {
|
|||||||
_$_YubiKeyData;
|
_$_YubiKeyData;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DeviceNode get node => throw _privateConstructorUsedError;
|
DeviceNode get node;
|
||||||
@override
|
@override
|
||||||
String get name => throw _privateConstructorUsedError;
|
String get name;
|
||||||
@override
|
@override
|
||||||
DeviceInfo get info => throw _privateConstructorUsedError;
|
DeviceInfo get info;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_YubiKeyDataCopyWith<_$_YubiKeyData> get copyWith =>
|
_$$_YubiKeyDataCopyWith<_$_YubiKeyData> get copyWith =>
|
||||||
@ -454,11 +454,11 @@ abstract class UsbYubiKeyNode extends DeviceNode {
|
|||||||
UsbYubiKeyNode._() : super._();
|
UsbYubiKeyNode._() : super._();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DevicePath get path => throw _privateConstructorUsedError;
|
DevicePath get path;
|
||||||
@override
|
@override
|
||||||
String get name => throw _privateConstructorUsedError;
|
String get name;
|
||||||
UsbPid get pid => throw _privateConstructorUsedError;
|
UsbPid get pid;
|
||||||
DeviceInfo? get info => throw _privateConstructorUsedError;
|
DeviceInfo? get info;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$UsbYubiKeyNodeCopyWith<_$UsbYubiKeyNode> get copyWith =>
|
_$$UsbYubiKeyNodeCopyWith<_$UsbYubiKeyNode> get copyWith =>
|
||||||
@ -613,9 +613,9 @@ abstract class NfcReaderNode extends DeviceNode {
|
|||||||
NfcReaderNode._() : super._();
|
NfcReaderNode._() : super._();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DevicePath get path => throw _privateConstructorUsedError;
|
DevicePath get path;
|
||||||
@override
|
@override
|
||||||
String get name => throw _privateConstructorUsedError;
|
String get name;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$NfcReaderNodeCopyWith<_$NfcReaderNode> get copyWith =>
|
_$$NfcReaderNodeCopyWith<_$NfcReaderNode> get copyWith =>
|
||||||
@ -762,11 +762,11 @@ abstract class _MenuAction implements MenuAction {
|
|||||||
final void Function(BuildContext)? action}) = _$_MenuAction;
|
final void Function(BuildContext)? action}) = _$_MenuAction;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get text => throw _privateConstructorUsedError;
|
String get text;
|
||||||
@override
|
@override
|
||||||
Widget get icon => throw _privateConstructorUsedError;
|
Widget get icon;
|
||||||
@override
|
@override
|
||||||
void Function(BuildContext)? get action => throw _privateConstructorUsedError;
|
void Function(BuildContext)? get action;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_MenuActionCopyWith<_$_MenuAction> get copyWith =>
|
_$$_MenuActionCopyWith<_$_MenuAction> get copyWith =>
|
||||||
@ -914,11 +914,11 @@ abstract class _WindowState implements WindowState {
|
|||||||
required final bool active}) = _$_WindowState;
|
required final bool active}) = _$_WindowState;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get focused => throw _privateConstructorUsedError;
|
bool get focused;
|
||||||
@override
|
@override
|
||||||
bool get visible => throw _privateConstructorUsedError;
|
bool get visible;
|
||||||
@override
|
@override
|
||||||
bool get active => throw _privateConstructorUsedError;
|
bool get active;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_WindowStateCopyWith<_$_WindowState> get copyWith =>
|
_$$_WindowStateCopyWith<_$_WindowState> get copyWith =>
|
||||||
|
@ -60,7 +60,7 @@ class AttachedDevicesNotifier extends StateNotifier<List<DeviceNode>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Override with platform implementation
|
// Override with platform implementation
|
||||||
final currentDeviceDataProvider = Provider<YubiKeyData?>(
|
final currentDeviceDataProvider = Provider<AsyncValue<YubiKeyData>>(
|
||||||
(ref) => throw UnimplementedError(),
|
(ref) => throw UnimplementedError(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -77,8 +77,8 @@ abstract class CurrentDeviceNotifier extends StateNotifier<DeviceNode?> {
|
|||||||
final currentAppProvider =
|
final currentAppProvider =
|
||||||
StateNotifierProvider<CurrentAppNotifier, Application>((ref) {
|
StateNotifierProvider<CurrentAppNotifier, Application>((ref) {
|
||||||
final notifier = CurrentAppNotifier(ref.watch(supportedAppsProvider));
|
final notifier = CurrentAppNotifier(ref.watch(supportedAppsProvider));
|
||||||
ref.listen<YubiKeyData?>(currentDeviceDataProvider, (_, data) {
|
ref.listen<AsyncValue<YubiKeyData>>(currentDeviceDataProvider, (_, data) {
|
||||||
notifier._notifyDeviceChanged(data);
|
notifier._notifyDeviceChanged(data.whenOrNull(data: ((data) => data)));
|
||||||
}, fireImmediately: true);
|
}, fireImmediately: true);
|
||||||
return notifier;
|
return notifier;
|
||||||
});
|
});
|
||||||
|
@ -49,7 +49,7 @@ class AppFailurePage extends ConsumerWidget {
|
|||||||
icon: const Icon(Icons.lock_open),
|
icon: const Icon(Icons.lock_open),
|
||||||
style: AppTheme.primaryOutlinedButtonStyle(context),
|
style: AppTheme.primaryOutlinedButtonStyle(context),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final controller = showMessage(
|
final closeMessage = showMessage(
|
||||||
context, 'Elevating permissions...',
|
context, 'Elevating permissions...',
|
||||||
duration: const Duration(seconds: 30));
|
duration: const Duration(seconds: 30));
|
||||||
try {
|
try {
|
||||||
@ -59,7 +59,7 @@ class AppFailurePage extends ConsumerWidget {
|
|||||||
showMessage(context, 'Permission denied');
|
showMessage(context, 'Permission denied');
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
controller.close();
|
closeMessage();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
@ -9,12 +9,14 @@ class AppPage extends ConsumerWidget {
|
|||||||
final Widget? title;
|
final Widget? title;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final List<Widget> actions;
|
final List<Widget> actions;
|
||||||
|
final List<PopupMenuEntry> keyActions;
|
||||||
final bool centered;
|
final bool centered;
|
||||||
AppPage({
|
AppPage({
|
||||||
super.key,
|
super.key,
|
||||||
this.title,
|
this.title,
|
||||||
required this.child,
|
required this.child,
|
||||||
this.actions = const [],
|
this.actions = const [],
|
||||||
|
this.keyActions = const [],
|
||||||
this.centered = false,
|
this.centered = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -82,10 +84,10 @@ class AppPage extends ConsumerWidget {
|
|||||||
title: title,
|
title: title,
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
titleTextStyle: Theme.of(context).textTheme.titleLarge,
|
titleTextStyle: Theme.of(context).textTheme.titleLarge,
|
||||||
actions: const [
|
actions: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(right: 6),
|
padding: const EdgeInsets.only(right: 6),
|
||||||
child: DeviceButton(),
|
child: DeviceButton(actions: keyActions),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -1,75 +1,80 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import '../../widgets/custom_icons.dart';
|
import '../../widgets/custom_icons.dart';
|
||||||
import '../models.dart';
|
import '../models.dart';
|
||||||
|
import '../state.dart';
|
||||||
import 'device_images.dart';
|
import 'device_images.dart';
|
||||||
|
|
||||||
class DeviceAvatar extends StatelessWidget {
|
class DeviceAvatar extends StatelessWidget {
|
||||||
final bool selected;
|
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final Widget? badge;
|
final Widget? badge;
|
||||||
final double? radius;
|
final double? radius;
|
||||||
const DeviceAvatar(
|
const DeviceAvatar({super.key, required this.child, this.badge, this.radius});
|
||||||
{super.key,
|
|
||||||
this.selected = false,
|
|
||||||
required this.child,
|
|
||||||
this.badge,
|
|
||||||
this.radius});
|
|
||||||
|
|
||||||
factory DeviceAvatar.yubiKeyData(YubiKeyData data,
|
factory DeviceAvatar.yubiKeyData(YubiKeyData data, {double? radius}) =>
|
||||||
{bool selected = false, double? radius}) =>
|
|
||||||
DeviceAvatar(
|
DeviceAvatar(
|
||||||
badge: data.node is NfcReaderNode ? nfcIcon : null,
|
badge: data.node is NfcReaderNode ? nfcIcon : null,
|
||||||
selected: selected,
|
|
||||||
radius: radius,
|
radius: radius,
|
||||||
child: getProductImage(data.info, data.name),
|
child: getProductImage(data.info, data.name),
|
||||||
);
|
);
|
||||||
|
|
||||||
factory DeviceAvatar.deviceNode(DeviceNode node,
|
factory DeviceAvatar.deviceNode(DeviceNode node, {double? radius}) =>
|
||||||
{bool selected = false, double? radius}) =>
|
|
||||||
node.map(
|
node.map(
|
||||||
usbYubiKey: (node) {
|
usbYubiKey: (node) {
|
||||||
final info = node.info;
|
final info = node.info;
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
return DeviceAvatar.yubiKeyData(
|
return DeviceAvatar.yubiKeyData(
|
||||||
YubiKeyData(node, node.name, info),
|
YubiKeyData(node, node.name, info),
|
||||||
selected: selected,
|
|
||||||
radius: radius,
|
radius: radius,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return DeviceAvatar(
|
return DeviceAvatar(
|
||||||
selected: selected,
|
|
||||||
radius: radius,
|
radius: radius,
|
||||||
child: const Icon(Icons.device_unknown),
|
child: const Icon(Icons.device_unknown),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
nfcReader: (_) => DeviceAvatar(
|
nfcReader: (_) => DeviceAvatar(
|
||||||
selected: selected,
|
|
||||||
radius: radius,
|
radius: radius,
|
||||||
child: nfcIcon,
|
child: nfcIcon,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
factory DeviceAvatar.currentDevice(WidgetRef ref, {double? radius}) {
|
||||||
|
final deviceNode = ref.watch(currentDeviceProvider);
|
||||||
|
if (deviceNode != null) {
|
||||||
|
return ref.watch(currentDeviceDataProvider).maybeWhen(
|
||||||
|
data: (data) => DeviceAvatar.yubiKeyData(
|
||||||
|
data,
|
||||||
|
radius: radius,
|
||||||
|
),
|
||||||
|
orElse: () => DeviceAvatar.deviceNode(
|
||||||
|
deviceNode,
|
||||||
|
radius: radius,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return DeviceAvatar(
|
||||||
|
radius: radius,
|
||||||
|
child: const Icon(Icons.usb),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final radius = this.radius ?? 24;
|
final radius = this.radius ?? 20;
|
||||||
return Stack(
|
return Stack(
|
||||||
alignment: AlignmentDirectional.bottomEnd,
|
alignment: AlignmentDirectional.bottomEnd,
|
||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: radius,
|
radius: radius,
|
||||||
backgroundColor: selected
|
backgroundColor: Theme.of(context).colorScheme.background,
|
||||||
? Theme.of(context).colorScheme.primary
|
child: IconTheme(
|
||||||
: Colors.transparent,
|
data: IconTheme.of(context).copyWith(
|
||||||
child: CircleAvatar(
|
size: radius,
|
||||||
radius: radius - 1,
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.background,
|
|
||||||
child: IconTheme(
|
|
||||||
data: IconTheme.of(context).copyWith(
|
|
||||||
size: radius,
|
|
||||||
),
|
|
||||||
child: child,
|
|
||||||
),
|
),
|
||||||
|
child: child,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (badge != null)
|
if (badge != null)
|
||||||
|
@ -1,57 +1,127 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import '../message.dart';
|
||||||
import '../state.dart';
|
import '../state.dart';
|
||||||
import 'device_avatar.dart';
|
import 'device_avatar.dart';
|
||||||
import 'device_picker_dialog.dart';
|
import 'device_picker_dialog.dart';
|
||||||
|
import 'device_utils.dart';
|
||||||
|
|
||||||
|
class _CircledDeviceAvatar extends ConsumerWidget {
|
||||||
|
final double radius;
|
||||||
|
const _CircledDeviceAvatar(this.radius);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) => CircleAvatar(
|
||||||
|
radius: radius,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
child: IconTheme(
|
||||||
|
// Force the standard icon theme
|
||||||
|
data: IconTheme.of(context),
|
||||||
|
child: DeviceAvatar.currentDevice(ref, radius: radius - 1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class DeviceButton extends ConsumerWidget {
|
class DeviceButton extends ConsumerWidget {
|
||||||
final double radius;
|
final double radius;
|
||||||
const DeviceButton({super.key, this.radius = 16});
|
final List<PopupMenuEntry> actions;
|
||||||
|
const DeviceButton({super.key, this.actions = const [], this.radius = 16});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final deviceNode = ref.watch(currentDeviceProvider);
|
|
||||||
final deviceData = ref.watch(currentDeviceDataProvider);
|
|
||||||
Widget deviceWidget;
|
|
||||||
if (deviceNode != null) {
|
|
||||||
if (deviceData != null) {
|
|
||||||
deviceWidget = DeviceAvatar.yubiKeyData(
|
|
||||||
deviceData,
|
|
||||||
selected: true,
|
|
||||||
radius: radius,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
deviceWidget = DeviceAvatar.deviceNode(
|
|
||||||
deviceNode,
|
|
||||||
selected: true,
|
|
||||||
radius: radius,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
deviceWidget = DeviceAvatar(
|
|
||||||
radius: radius,
|
|
||||||
selected: true,
|
|
||||||
child: const Icon(Icons.usb),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(right: 8.0),
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
tooltip: 'Select YubiKey or device',
|
tooltip: 'More actions',
|
||||||
icon: OverflowBox(
|
icon: OverflowBox(
|
||||||
maxHeight: 44,
|
maxHeight: 44,
|
||||||
maxWidth: 44,
|
maxWidth: 44,
|
||||||
child: deviceWidget,
|
child: _CircledDeviceAvatar(radius),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDialog(
|
final withContext = ref.read(withContextProvider);
|
||||||
|
|
||||||
|
showMenu(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => const DevicePickerDialog(),
|
position: const RelativeRect.fromLTRB(100, 0, 0, 0),
|
||||||
routeSettings: const RouteSettings(name: 'device_picker'),
|
items: <PopupMenuEntry>[
|
||||||
|
PopupMenuItem(
|
||||||
|
padding: const EdgeInsets.only(left: 11, right: 16),
|
||||||
|
onTap: () {
|
||||||
|
// Wait for menu to close, and use the main context to open
|
||||||
|
Timer.run(() {
|
||||||
|
withContext(
|
||||||
|
(context) async {
|
||||||
|
await showBlurDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const DevicePickerDialog(),
|
||||||
|
routeSettings:
|
||||||
|
const RouteSettings(name: 'device_picker'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: _SlideInWidget(radius: radius),
|
||||||
|
),
|
||||||
|
if (actions.isNotEmpty) const PopupMenuDivider(),
|
||||||
|
...actions,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _SlideInWidget extends ConsumerStatefulWidget {
|
||||||
|
final double radius;
|
||||||
|
const _SlideInWidget({required this.radius});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<_SlideInWidget> createState() => _SlideInWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SlideInWidgetState extends ConsumerState<_SlideInWidget>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late final AnimationController _controller = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
vsync: this,
|
||||||
|
)..forward();
|
||||||
|
late final Animation<Offset> _offsetAnimation = Tween<Offset>(
|
||||||
|
begin: const Offset(0.9, 0.0),
|
||||||
|
end: Offset.zero,
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _controller,
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
));
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final messages = getDeviceMessages(
|
||||||
|
ref.watch(currentDeviceProvider),
|
||||||
|
ref.watch(currentDeviceDataProvider),
|
||||||
|
);
|
||||||
|
return SlideTransition(
|
||||||
|
position: _offsetAnimation,
|
||||||
|
child: ListTile(
|
||||||
|
dense: true,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
minLeadingWidth: 0,
|
||||||
|
horizontalTitleGap: 13,
|
||||||
|
leading: _CircledDeviceAvatar(widget.radius),
|
||||||
|
title: Text(messages.removeAt(0)),
|
||||||
|
subtitle: Text(messages.first),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -12,9 +12,10 @@ import 'device_avatar.dart';
|
|||||||
import 'graphics.dart';
|
import 'graphics.dart';
|
||||||
import 'message_page.dart';
|
import 'message_page.dart';
|
||||||
|
|
||||||
class NoDeviceScreen extends ConsumerWidget {
|
class DeviceErrorScreen extends ConsumerWidget {
|
||||||
final DeviceNode? node;
|
final DeviceNode node;
|
||||||
const NoDeviceScreen(this.node, {super.key});
|
final Object? error;
|
||||||
|
const DeviceErrorScreen(this.node, {this.error, super.key});
|
||||||
|
|
||||||
Widget _buildUsbPid(BuildContext context, WidgetRef ref, UsbPid pid) {
|
Widget _buildUsbPid(BuildContext context, WidgetRef ref, UsbPid pid) {
|
||||||
if (pid.usbInterfaces == UsbInterface.fido.value) {
|
if (pid.usbInterfaces == UsbInterface.fido.value) {
|
||||||
@ -29,7 +30,7 @@ class NoDeviceScreen extends ConsumerWidget {
|
|||||||
label: const Text('Unlock'),
|
label: const Text('Unlock'),
|
||||||
icon: const Icon(Icons.lock_open),
|
icon: const Icon(Icons.lock_open),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final controller = showMessage(
|
final closeMessage = showMessage(
|
||||||
context, 'Elevating permissions...',
|
context, 'Elevating permissions...',
|
||||||
duration: const Duration(seconds: 30));
|
duration: const Duration(seconds: 30));
|
||||||
try {
|
try {
|
||||||
@ -39,7 +40,7 @@ class NoDeviceScreen extends ConsumerWidget {
|
|||||||
showMessage(context, 'Permission denied');
|
showMessage(context, 'Permission denied');
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
controller.close();
|
closeMessage();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -55,12 +56,19 @@ class NoDeviceScreen extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return node?.map(
|
return node.map(
|
||||||
usbYubiKey: (node) => _buildUsbPid(context, ref, node.pid),
|
usbYubiKey: (node) => _buildUsbPid(context, ref, node.pid),
|
||||||
nfcReader: (_) => const MessagePage(
|
nfcReader: (node) {
|
||||||
message: 'Place your YubiKey on the NFC reader',
|
final String message;
|
||||||
),
|
switch (error) {
|
||||||
) ??
|
case 'unknown-device':
|
||||||
const MessagePage(message: 'Insert your YubiKey');
|
message = 'Unrecognized device';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
message = 'Place your YubiKey on the NFC reader';
|
||||||
|
}
|
||||||
|
return MessagePage(message: message);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,28 +2,84 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import '../../core/state.dart';
|
||||||
import '../../management/models.dart';
|
import '../../management/models.dart';
|
||||||
import '../models.dart';
|
import '../models.dart';
|
||||||
import '../state.dart';
|
import '../state.dart';
|
||||||
import 'device_avatar.dart';
|
import 'device_avatar.dart';
|
||||||
|
import 'device_utils.dart';
|
||||||
|
|
||||||
String _getSubtitle(DeviceInfo info) {
|
final _hiddenDevicesProvider =
|
||||||
final serial = info.serial;
|
StateNotifierProvider<_HiddenDevicesNotifier, List<String>>(
|
||||||
var subtitle = '';
|
(ref) => _HiddenDevicesNotifier(ref.watch(prefProvider)));
|
||||||
if (serial != null) {
|
|
||||||
subtitle += 'S/N: $serial ';
|
class _HiddenDevicesNotifier extends StateNotifier<List<String>> {
|
||||||
|
static const String _key = 'DEVICE_PICKER_HIDDEN';
|
||||||
|
final SharedPreferences _prefs;
|
||||||
|
_HiddenDevicesNotifier(this._prefs) : super(_prefs.getStringList(_key) ?? []);
|
||||||
|
|
||||||
|
void showAll() {
|
||||||
|
state = [];
|
||||||
|
_prefs.setStringList(_key, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hideDevice(DevicePath devicePath) {
|
||||||
|
state = [...state, devicePath.key];
|
||||||
|
_prefs.setStringList(_key, state);
|
||||||
}
|
}
|
||||||
subtitle += 'F/W: ${info.version}';
|
|
||||||
return subtitle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DevicePickerDialog extends ConsumerWidget {
|
class DevicePickerDialog extends StatefulWidget {
|
||||||
const DevicePickerDialog({super.key});
|
const DevicePickerDialog({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _DevicePickerDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DevicePickerDialogState extends State<DevicePickerDialog> {
|
||||||
|
late FocusScopeNode _focus;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_focus = FocusScopeNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_focus.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// This keeps the focus in the dialog, even if the underlying page
|
||||||
|
// changes as it does when a new device is selected.
|
||||||
|
return FocusScope(
|
||||||
|
node: _focus,
|
||||||
|
autofocus: true,
|
||||||
|
onFocusChange: (focused) {
|
||||||
|
if (!focused) {
|
||||||
|
_focus.requestFocus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const _DevicePickerContent(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DevicePickerContent extends ConsumerWidget {
|
||||||
|
const _DevicePickerContent();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final devices = ref.watch(attachedDevicesProvider).toList();
|
final hidden = ref.watch(_hiddenDevicesProvider);
|
||||||
|
final devices = ref
|
||||||
|
.watch(attachedDevicesProvider)
|
||||||
|
.where((e) => !hidden.contains(e.path.key))
|
||||||
|
.toList();
|
||||||
final currentNode = ref.watch(currentDeviceProvider);
|
final currentNode = ref.watch(currentDeviceProvider);
|
||||||
|
|
||||||
final Widget hero;
|
final Widget hero;
|
||||||
@ -33,24 +89,27 @@ class DevicePickerDialog extends ConsumerWidget {
|
|||||||
devices.removeWhere((e) => e.path == currentNode.path);
|
devices.removeWhere((e) => e.path == currentNode.path);
|
||||||
hero = _CurrentDeviceRow(
|
hero = _CurrentDeviceRow(
|
||||||
currentNode,
|
currentNode,
|
||||||
data: ref.watch(currentDeviceDataProvider),
|
ref.watch(currentDeviceDataProvider),
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
hero = ListTile(
|
hero = Column(
|
||||||
leading: DeviceAvatar(
|
children: [
|
||||||
selected: true,
|
_HeroAvatar(
|
||||||
child: Icon(Platform.isAndroid ? Icons.no_cell : Icons.usb),
|
child: DeviceAvatar(
|
||||||
),
|
radius: 64,
|
||||||
title: Text(Platform.isAndroid ? 'No YubiKey' : 'USB'),
|
child: Icon(Platform.isAndroid ? Icons.no_cell : Icons.usb),
|
||||||
subtitle: Text(Platform.isAndroid
|
),
|
||||||
? 'Insert or tap a YubiKey'
|
),
|
||||||
: 'Insert a YubiKey'),
|
ListTile(
|
||||||
onTap: () {
|
title:
|
||||||
Navigator.of(context).pop();
|
Center(child: Text(Platform.isAndroid ? 'No YubiKey' : 'USB')),
|
||||||
},
|
subtitle: Center(
|
||||||
|
child: Text(Platform.isAndroid
|
||||||
|
? 'Insert or tap a YubiKey'
|
||||||
|
: 'Insert a YubiKey'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
showUsb = false;
|
showUsb = false;
|
||||||
}
|
}
|
||||||
@ -60,136 +119,194 @@ class DevicePickerDialog extends ConsumerWidget {
|
|||||||
ListTile(
|
ListTile(
|
||||||
leading: const Padding(
|
leading: const Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 4),
|
padding: EdgeInsets.symmetric(horizontal: 4),
|
||||||
child: DeviceAvatar(
|
child: DeviceAvatar(child: Icon(Icons.usb)),
|
||||||
radius: 20,
|
|
||||||
child: Icon(Icons.usb),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
title: const Text('USB'),
|
title: const Text('USB'),
|
||||||
subtitle: const Text('No YubiKey present'),
|
subtitle: const Text('No YubiKey present'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).pop();
|
|
||||||
ref.read(currentDeviceProvider.notifier).setCurrentDevice(null);
|
ref.read(currentDeviceProvider.notifier).setCurrentDevice(null);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
...devices.map(
|
...devices.map(
|
||||||
(e) => _DeviceRow(
|
(e) => e.map(
|
||||||
e,
|
usbYubiKey: (node) => _DeviceRow(node, info: node.info),
|
||||||
info: e.map(
|
nfcReader: (node) => _NfcDeviceRow(node),
|
||||||
usbYubiKey: (node) => node.info,
|
|
||||||
nfcReader: (_) => null,
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
ref.read(currentDeviceProvider.notifier).setCurrentDevice(e);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
return SimpleDialog(
|
return GestureDetector(
|
||||||
children: [
|
onSecondaryTapDown: hidden.isEmpty
|
||||||
hero,
|
? null
|
||||||
if (others.isNotEmpty) const Divider(),
|
: (details) {
|
||||||
...others,
|
showMenu(
|
||||||
],
|
context: context,
|
||||||
|
position: RelativeRect.fromLTRB(
|
||||||
|
details.globalPosition.dx,
|
||||||
|
details.globalPosition.dy,
|
||||||
|
details.globalPosition.dx,
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
items: [
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: () {
|
||||||
|
ref.read(_hiddenDevicesProvider.notifier).showAll();
|
||||||
|
},
|
||||||
|
child: const ListTile(
|
||||||
|
title: Text('Show hidden devices'),
|
||||||
|
dense: true,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: SimpleDialog(
|
||||||
|
children: [
|
||||||
|
hero,
|
||||||
|
if (others.isNotEmpty)
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Divider(),
|
||||||
|
),
|
||||||
|
...others,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HeroAvatar extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
const _HeroAvatar({required this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
gradient: RadialGradient(
|
||||||
|
colors: [
|
||||||
|
theme.colorScheme.background,
|
||||||
|
theme.colorScheme.background.withOpacity(0.4),
|
||||||
|
(theme.dialogTheme.backgroundColor ?? theme.dialogBackgroundColor)
|
||||||
|
.withOpacity(0),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Theme(
|
||||||
|
// Give the avatar a transparent background
|
||||||
|
data: theme.copyWith(
|
||||||
|
colorScheme:
|
||||||
|
theme.colorScheme.copyWith(background: Colors.transparent)),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CurrentDeviceRow extends StatelessWidget {
|
class _CurrentDeviceRow extends StatelessWidget {
|
||||||
final DeviceNode node;
|
final DeviceNode node;
|
||||||
final YubiKeyData? data;
|
final AsyncValue<YubiKeyData> data;
|
||||||
final Function() onTap;
|
|
||||||
|
|
||||||
const _CurrentDeviceRow(
|
const _CurrentDeviceRow(this.node, this.data);
|
||||||
this.node, {
|
|
||||||
this.data,
|
|
||||||
required this.onTap,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return node.when(usbYubiKey: (path, name, pid, info) {
|
final hero = data.maybeWhen(
|
||||||
if (info != null) {
|
data: (data) => DeviceAvatar.yubiKeyData(data, radius: 64),
|
||||||
return ListTile(
|
orElse: () => DeviceAvatar.deviceNode(node, radius: 64),
|
||||||
leading: DeviceAvatar.yubiKeyData(
|
);
|
||||||
data!,
|
final messages = getDeviceMessages(node, data);
|
||||||
selected: true,
|
|
||||||
),
|
return Column(
|
||||||
title: Text(name),
|
children: [
|
||||||
subtitle: Text(_getSubtitle(info)),
|
_HeroAvatar(child: hero),
|
||||||
onTap: onTap,
|
ListTile(
|
||||||
);
|
title: Text(messages.removeAt(0), textAlign: TextAlign.center),
|
||||||
} else {
|
isThreeLine: messages.length > 1,
|
||||||
return ListTile(
|
subtitle: Text(messages.join('\n'), textAlign: TextAlign.center),
|
||||||
leading: DeviceAvatar.deviceNode(
|
)
|
||||||
node,
|
],
|
||||||
selected: true,
|
);
|
||||||
),
|
|
||||||
title: Text(name),
|
|
||||||
subtitle: const Text('Device inaccessible'),
|
|
||||||
onTap: onTap,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, nfcReader: (path, name) {
|
|
||||||
final info = data?.info;
|
|
||||||
if (info != null) {
|
|
||||||
return ListTile(
|
|
||||||
leading: DeviceAvatar.yubiKeyData(
|
|
||||||
data!,
|
|
||||||
selected: true,
|
|
||||||
),
|
|
||||||
title: Text(data!.name),
|
|
||||||
isThreeLine: true,
|
|
||||||
subtitle: Text('$name\n${_getSubtitle(info)}'),
|
|
||||||
onTap: onTap,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return ListTile(
|
|
||||||
leading: DeviceAvatar.deviceNode(
|
|
||||||
node,
|
|
||||||
selected: true,
|
|
||||||
),
|
|
||||||
title: const Text('No YubiKey present'),
|
|
||||||
subtitle: Text(name),
|
|
||||||
onTap: onTap,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DeviceRow extends StatelessWidget {
|
class _DeviceRow extends ConsumerWidget {
|
||||||
final DeviceNode node;
|
final DeviceNode node;
|
||||||
final DeviceInfo? info;
|
final DeviceInfo? info;
|
||||||
final Function() onTap;
|
|
||||||
|
|
||||||
const _DeviceRow(
|
const _DeviceRow(this.node, {this.info});
|
||||||
this.node, {
|
|
||||||
required this.info,
|
|
||||||
required this.onTap,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: Padding(
|
leading: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
child: DeviceAvatar.deviceNode(
|
child: DeviceAvatar.deviceNode(node),
|
||||||
node,
|
|
||||||
radius: 20,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
title: Text(node.name),
|
title: Text(node.name),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
node.when(
|
node.when(
|
||||||
usbYubiKey: (_, __, ___, info) =>
|
usbYubiKey: (_, __, ___, info) =>
|
||||||
info == null ? 'Device inaccessible' : _getSubtitle(info),
|
info == null ? 'Device inaccessible' : getDeviceInfoString(info),
|
||||||
nfcReader: (_, __) => 'Select to scan',
|
nfcReader: (_, __) => 'Select to scan',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: onTap,
|
onTap: () {
|
||||||
|
ref.read(currentDeviceProvider.notifier).setCurrentDevice(node);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NfcDeviceRow extends ConsumerWidget {
|
||||||
|
final DeviceNode node;
|
||||||
|
|
||||||
|
const _NfcDeviceRow(this.node);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final hidden = ref.watch(_hiddenDevicesProvider);
|
||||||
|
return GestureDetector(
|
||||||
|
onSecondaryTapDown: (details) {
|
||||||
|
showMenu(
|
||||||
|
context: context,
|
||||||
|
position: RelativeRect.fromLTRB(
|
||||||
|
details.globalPosition.dx,
|
||||||
|
details.globalPosition.dy,
|
||||||
|
details.globalPosition.dx,
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
items: [
|
||||||
|
PopupMenuItem(
|
||||||
|
enabled: hidden.isNotEmpty,
|
||||||
|
onTap: () {
|
||||||
|
ref.read(_hiddenDevicesProvider.notifier).showAll();
|
||||||
|
},
|
||||||
|
child: ListTile(
|
||||||
|
title: const Text('Show hidden devices'),
|
||||||
|
dense: true,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
enabled: hidden.isNotEmpty,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: () {
|
||||||
|
ref.read(_hiddenDevicesProvider.notifier).hideDevice(node.path);
|
||||||
|
},
|
||||||
|
child: const ListTile(
|
||||||
|
title: Text('Hide device'),
|
||||||
|
dense: true,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: _DeviceRow(node),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
44
lib/app/views/device_utils.dart
Executable file
44
lib/app/views/device_utils.dart
Executable file
@ -0,0 +1,44 @@
|
|||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import '../../management/models.dart';
|
||||||
|
import '../models.dart';
|
||||||
|
|
||||||
|
String getDeviceInfoString(DeviceInfo info) {
|
||||||
|
final serial = info.serial;
|
||||||
|
var subtitle = '';
|
||||||
|
if (serial != null) {
|
||||||
|
subtitle += 'S/N: $serial ';
|
||||||
|
}
|
||||||
|
subtitle += 'F/W: ${info.version}';
|
||||||
|
return subtitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> getDeviceMessages(DeviceNode? node, AsyncValue<YubiKeyData> data) {
|
||||||
|
if (node == null) {
|
||||||
|
return ['Insert a YubiKey', 'USB'];
|
||||||
|
}
|
||||||
|
final messages = data.whenOrNull(
|
||||||
|
data: (data) => [getDeviceInfoString(data.info)],
|
||||||
|
error: (error, _) {
|
||||||
|
switch (error) {
|
||||||
|
case 'unknown-device':
|
||||||
|
return ['Unrecognized device'];
|
||||||
|
case 'device-inaccessible':
|
||||||
|
return ['Device inacessible'];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
) ??
|
||||||
|
['No YubiKey present'];
|
||||||
|
|
||||||
|
final name = data.asData?.value.name;
|
||||||
|
if (name != null) {
|
||||||
|
messages.insert(0, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node is NfcReaderNode) {
|
||||||
|
messages.add(node.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages;
|
||||||
|
}
|
@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import '../../management/views/management_screen.dart';
|
import '../../management/views/management_screen.dart';
|
||||||
import '../../about_page.dart';
|
import '../../about_page.dart';
|
||||||
import '../../settings_page.dart';
|
import '../../settings_page.dart';
|
||||||
|
import '../message.dart';
|
||||||
import '../models.dart';
|
import '../models.dart';
|
||||||
import '../state.dart';
|
import '../state.dart';
|
||||||
|
|
||||||
@ -35,7 +36,8 @@ class MainPageDrawer extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final supportedApps = ref.watch(supportedAppsProvider);
|
final supportedApps = ref.watch(supportedAppsProvider);
|
||||||
final data = ref.watch(currentDeviceDataProvider);
|
final data =
|
||||||
|
ref.watch(currentDeviceDataProvider).whenOrNull(data: (data) => data);
|
||||||
final currentApp = ref.watch(currentAppProvider);
|
final currentApp = ref.watch(currentAppProvider);
|
||||||
|
|
||||||
MediaQuery? mediaQuery =
|
MediaQuery? mediaQuery =
|
||||||
@ -76,7 +78,7 @@ class MainPageDrawer extends ConsumerWidget {
|
|||||||
icon: Icon(Application.management._icon),
|
icon: Icon(Application.management._icon),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (shouldPop) Navigator.of(context).pop();
|
if (shouldPop) Navigator.of(context).pop();
|
||||||
showDialog(
|
showBlurDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => ManagementScreen(data),
|
builder: (context) => ManagementScreen(data),
|
||||||
);
|
);
|
||||||
@ -92,7 +94,7 @@ class MainPageDrawer extends ConsumerWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
final nav = Navigator.of(context);
|
final nav = Navigator.of(context);
|
||||||
if (shouldPop) nav.pop();
|
if (shouldPop) nav.pop();
|
||||||
showDialog(
|
showBlurDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => const SettingsPage(),
|
builder: (context) => const SettingsPage(),
|
||||||
routeSettings: const RouteSettings(name: 'settings'),
|
routeSettings: const RouteSettings(name: 'settings'),
|
||||||
@ -105,7 +107,7 @@ class MainPageDrawer extends ConsumerWidget {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
final nav = Navigator.of(context);
|
final nav = Navigator.of(context);
|
||||||
if (shouldPop) nav.pop();
|
if (shouldPop) nav.pop();
|
||||||
showDialog(
|
showBlurDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => const AboutPage(),
|
builder: (context) => const AboutPage(),
|
||||||
routeSettings: const RouteSettings(name: 'about'),
|
routeSettings: const RouteSettings(name: 'about'),
|
||||||
|
@ -2,12 +2,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'message_page.dart';
|
import 'message_page.dart';
|
||||||
import 'no_device_screen.dart';
|
import 'device_error_screen.dart';
|
||||||
import '../models.dart';
|
import '../models.dart';
|
||||||
import '../state.dart';
|
import '../state.dart';
|
||||||
import '../../fido/views/fido_screen.dart';
|
import '../../fido/views/fido_screen.dart';
|
||||||
import '../../oath/views/oath_screen.dart';
|
import '../../oath/views/oath_screen.dart';
|
||||||
import '../../management/views/management_screen.dart';
|
|
||||||
|
|
||||||
class MainPage extends ConsumerWidget {
|
class MainPage extends ConsumerWidget {
|
||||||
const MainPage({super.key});
|
const MainPage({super.key});
|
||||||
@ -21,7 +20,7 @@ class MainPage extends ConsumerWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
// If the current device changes, we need to pop any open dialogs.
|
// If the current device changes, we need to pop any open dialogs.
|
||||||
ref.listen<YubiKeyData?>(currentDeviceDataProvider, (_, __) {
|
ref.listen<AsyncValue<YubiKeyData>>(currentDeviceDataProvider, (_, __) {
|
||||||
Navigator.of(context).popUntil((route) {
|
Navigator.of(context).popUntil((route) {
|
||||||
return route.isFirst ||
|
return route.isFirst ||
|
||||||
[
|
[
|
||||||
@ -32,31 +31,36 @@ class MainPage extends ConsumerWidget {
|
|||||||
].contains(route.settings.name);
|
].contains(route.settings.name);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
final deviceData = ref.watch(currentDeviceDataProvider);
|
|
||||||
if (deviceData == null) {
|
|
||||||
final node = ref.watch(currentDeviceProvider);
|
|
||||||
return NoDeviceScreen(node);
|
|
||||||
}
|
|
||||||
final app = ref.watch(currentAppProvider);
|
|
||||||
if (app.getAvailability(deviceData) != Availability.enabled) {
|
|
||||||
return const MessagePage(
|
|
||||||
header: 'Application disabled',
|
|
||||||
message: 'Enable the application on your YubiKey to access',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (app) {
|
final deviceNode = ref.watch(currentDeviceProvider);
|
||||||
case Application.oath:
|
if (deviceNode == null) {
|
||||||
return OathScreen(deviceData.node.path);
|
return const MessagePage(message: 'Insert your YubiKey');
|
||||||
case Application.management:
|
} else {
|
||||||
return ManagementScreen(deviceData);
|
return ref.watch(currentDeviceDataProvider).when(
|
||||||
case Application.fido:
|
data: (data) {
|
||||||
return FidoScreen(deviceData);
|
final app = ref.watch(currentAppProvider);
|
||||||
default:
|
if (app.getAvailability(data) != Availability.enabled) {
|
||||||
return const MessagePage(
|
return const MessagePage(
|
||||||
header: 'Not implemented',
|
header: 'Application disabled',
|
||||||
message: 'This section has not yet been implemented',
|
message: 'Enable the application on your YubiKey to access',
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (app) {
|
||||||
|
case Application.oath:
|
||||||
|
return OathScreen(data.node.path);
|
||||||
|
case Application.fido:
|
||||||
|
return FidoScreen(data);
|
||||||
|
default:
|
||||||
|
return const MessagePage(
|
||||||
|
header: 'Not supported',
|
||||||
|
message: 'This application is not supported',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loading: () => DeviceErrorScreen(deviceNode),
|
||||||
|
error: (error, _) => DeviceErrorScreen(deviceNode, error: error),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ class MessagePage extends StatelessWidget {
|
|||||||
final String? header;
|
final String? header;
|
||||||
final String? message;
|
final String? message;
|
||||||
final List<Widget> actions;
|
final List<Widget> actions;
|
||||||
|
final List<PopupMenuEntry> keyActions;
|
||||||
|
|
||||||
const MessagePage({
|
const MessagePage({
|
||||||
super.key,
|
super.key,
|
||||||
@ -16,6 +17,7 @@ class MessagePage extends StatelessWidget {
|
|||||||
this.header,
|
this.header,
|
||||||
this.message,
|
this.message,
|
||||||
this.actions = const [],
|
this.actions = const [],
|
||||||
|
this.keyActions = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -23,6 +25,7 @@ class MessagePage extends StatelessWidget {
|
|||||||
title: title,
|
title: title,
|
||||||
centered: true,
|
centered: true,
|
||||||
actions: actions,
|
actions: actions,
|
||||||
|
keyActions: keyActions,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../message.dart';
|
||||||
|
|
||||||
abstract class UserInteractionController {
|
abstract class UserInteractionController {
|
||||||
void updateContent({String? title, String? description, Widget? icon});
|
void updateContent({String? title, String? description, Widget? icon});
|
||||||
void close();
|
void close();
|
||||||
@ -56,15 +58,28 @@ class _UserInteractionDialogState extends State<_UserInteractionDialog> {
|
|||||||
Widget? icon = widget.controller.icon;
|
Widget? icon = widget.controller.icon;
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
scrollable: true,
|
scrollable: true,
|
||||||
title: Text(widget.controller.title),
|
|
||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
width: 300,
|
height: 160,
|
||||||
|
width: 100,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
if (icon != null) icon,
|
if (icon != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: IconTheme(
|
||||||
|
data: IconTheme.of(context).copyWith(size: 36),
|
||||||
|
child: icon,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
widget.controller.title,
|
||||||
|
style: Theme.of(context).textTheme.headline6,
|
||||||
|
),
|
||||||
Text(
|
Text(
|
||||||
widget.controller.description,
|
widget.controller.description,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -110,7 +125,7 @@ UserInteractionController promptUserInteraction(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
showDialog(
|
showBlurDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
|
@ -88,7 +88,7 @@ enum UsbPid {
|
|||||||
final suffix = UsbInterface.values
|
final suffix = UsbInterface.values
|
||||||
.where((e) => e.value & usbInterfaces != 0)
|
.where((e) => e.value & usbInterfaces != 0)
|
||||||
.map((e) => e.name.toUpperCase())
|
.map((e) => e.name.toUpperCase())
|
||||||
.join(' ');
|
.join('+');
|
||||||
return '$prefix $suffix';
|
return '$prefix $suffix';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,11 +151,11 @@ abstract class _Version extends Version {
|
|||||||
const _Version._() : super._();
|
const _Version._() : super._();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get major => throw _privateConstructorUsedError;
|
int get major;
|
||||||
@override
|
@override
|
||||||
int get minor => throw _privateConstructorUsedError;
|
int get minor;
|
||||||
@override
|
@override
|
||||||
int get patch => throw _privateConstructorUsedError;
|
int get patch;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_VersionCopyWith<_$_Version> get copyWith =>
|
_$$_VersionCopyWith<_$_Version> get copyWith =>
|
||||||
@ -284,9 +284,9 @@ abstract class _Pair<T1, T2> implements Pair<T1, T2> {
|
|||||||
factory _Pair(final T1 first, final T2 second) = _$_Pair<T1, T2>;
|
factory _Pair(final T1 first, final T2 second) = _$_Pair<T1, T2>;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
T1 get first => throw _privateConstructorUsedError;
|
T1 get first;
|
||||||
@override
|
@override
|
||||||
T2 get second => throw _privateConstructorUsedError;
|
T2 get second;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_PairCopyWith<T1, T2, _$_Pair<T1, T2>> get copyWith =>
|
_$$_PairCopyWith<T1, T2, _$_Pair<T1, T2>> get copyWith =>
|
||||||
|
@ -193,7 +193,8 @@ class _DesktopDevicesNotifier extends AttachedDevicesNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final _desktopDeviceDataProvider =
|
final _desktopDeviceDataProvider =
|
||||||
StateNotifierProvider<CurrentDeviceDataNotifier, YubiKeyData?>((ref) {
|
StateNotifierProvider<CurrentDeviceDataNotifier, AsyncValue<YubiKeyData>>(
|
||||||
|
(ref) {
|
||||||
final notifier = CurrentDeviceDataNotifier(
|
final notifier = CurrentDeviceDataNotifier(
|
||||||
ref.watch(rpcProvider),
|
ref.watch(rpcProvider),
|
||||||
ref.watch(currentDeviceProvider),
|
ref.watch(currentDeviceProvider),
|
||||||
@ -207,23 +208,26 @@ final _desktopDeviceDataProvider =
|
|||||||
return notifier;
|
return notifier;
|
||||||
});
|
});
|
||||||
|
|
||||||
final desktopDeviceDataProvider = Provider<YubiKeyData?>(
|
final desktopDeviceDataProvider = Provider<AsyncValue<YubiKeyData>>(
|
||||||
(ref) {
|
(ref) {
|
||||||
return ref.watch(_desktopDeviceDataProvider);
|
return ref.watch(_desktopDeviceDataProvider);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
class CurrentDeviceDataNotifier extends StateNotifier<YubiKeyData?> {
|
class CurrentDeviceDataNotifier extends StateNotifier<AsyncValue<YubiKeyData>> {
|
||||||
final RpcSession _rpc;
|
final RpcSession _rpc;
|
||||||
final DeviceNode? _deviceNode;
|
final DeviceNode? _deviceNode;
|
||||||
Timer? _pollTimer;
|
Timer? _pollTimer;
|
||||||
|
|
||||||
CurrentDeviceDataNotifier(this._rpc, this._deviceNode) : super(null) {
|
CurrentDeviceDataNotifier(this._rpc, this._deviceNode)
|
||||||
|
: super(const AsyncValue.loading()) {
|
||||||
final dev = _deviceNode;
|
final dev = _deviceNode;
|
||||||
if (dev is UsbYubiKeyNode) {
|
if (dev is UsbYubiKeyNode) {
|
||||||
final info = dev.info;
|
final info = dev.info;
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
state = YubiKeyData(dev, dev.name, info);
|
state = AsyncValue.data(YubiKeyData(dev, dev.name, info));
|
||||||
|
} else {
|
||||||
|
state = const AsyncValue.error('device-inaccessible');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -254,10 +258,10 @@ class CurrentDeviceDataNotifier extends StateNotifier<YubiKeyData?> {
|
|||||||
var result = await _rpc.command('get', node.path.segments);
|
var result = await _rpc.command('get', node.path.segments);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
if (result['data']['present']) {
|
if (result['data']['present']) {
|
||||||
state = YubiKeyData(node, result['data']['name'],
|
state = AsyncValue.data(YubiKeyData(node, result['data']['name'],
|
||||||
DeviceInfo.fromJson(result['data']['info']));
|
DeviceInfo.fromJson(result['data']['info'])));
|
||||||
} else {
|
} else {
|
||||||
state = null;
|
state = AsyncValue.error(result['data']['status']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} on RpcError catch (e) {
|
} on RpcError catch (e) {
|
||||||
@ -265,7 +269,7 @@ class CurrentDeviceDataNotifier extends StateNotifier<YubiKeyData?> {
|
|||||||
}
|
}
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_pollTimer = Timer(
|
_pollTimer = Timer(
|
||||||
state == null ? _nfcAttachPollDelay : _nfcDetachPollDelay,
|
state is AsyncData ? _nfcDetachPollDelay : _nfcAttachPollDelay,
|
||||||
_pollReader);
|
_pollReader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -272,7 +272,7 @@ abstract class Success implements RpcResponse {
|
|||||||
factory Success.fromJson(Map<String, dynamic> json) = _$Success.fromJson;
|
factory Success.fromJson(Map<String, dynamic> json) = _$Success.fromJson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> get body => throw _privateConstructorUsedError;
|
Map<String, dynamic> get body;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$SuccessCopyWith<_$Success> get copyWith =>
|
_$$SuccessCopyWith<_$Success> get copyWith =>
|
||||||
@ -446,9 +446,9 @@ abstract class Signal implements RpcResponse {
|
|||||||
|
|
||||||
factory Signal.fromJson(Map<String, dynamic> json) = _$Signal.fromJson;
|
factory Signal.fromJson(Map<String, dynamic> json) = _$Signal.fromJson;
|
||||||
|
|
||||||
String get status => throw _privateConstructorUsedError;
|
String get status;
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> get body => throw _privateConstructorUsedError;
|
Map<String, dynamic> get body;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$SignalCopyWith<_$Signal> get copyWith =>
|
_$$SignalCopyWith<_$Signal> get copyWith =>
|
||||||
@ -633,10 +633,10 @@ abstract class RpcError implements RpcResponse {
|
|||||||
|
|
||||||
factory RpcError.fromJson(Map<String, dynamic> json) = _$RpcError.fromJson;
|
factory RpcError.fromJson(Map<String, dynamic> json) = _$RpcError.fromJson;
|
||||||
|
|
||||||
String get status => throw _privateConstructorUsedError;
|
String get status;
|
||||||
String get message => throw _privateConstructorUsedError;
|
String get message;
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> get body => throw _privateConstructorUsedError;
|
Map<String, dynamic> get body;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$RpcErrorCopyWith<_$RpcError> get copyWith =>
|
_$$RpcErrorCopyWith<_$RpcError> get copyWith =>
|
||||||
@ -780,9 +780,9 @@ abstract class _RpcState implements RpcState {
|
|||||||
factory _RpcState.fromJson(Map<String, dynamic> json) = _$_RpcState.fromJson;
|
factory _RpcState.fromJson(Map<String, dynamic> json) = _$_RpcState.fromJson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get version => throw _privateConstructorUsedError;
|
String get version;
|
||||||
@override
|
@override
|
||||||
bool get isAdmin => throw _privateConstructorUsedError;
|
bool get isAdmin;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_RpcStateCopyWith<_$_RpcState> get copyWith =>
|
_$$_RpcStateCopyWith<_$_RpcState> get copyWith =>
|
||||||
|
@ -2,13 +2,14 @@ import 'dart:async';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:yubico_authenticator/app/logging.dart';
|
|
||||||
import 'package:yubico_authenticator/app/views/user_interaction.dart';
|
|
||||||
|
|
||||||
|
import '../../app/logging.dart';
|
||||||
import '../../app/models.dart';
|
import '../../app/models.dart';
|
||||||
import '../../app/state.dart';
|
import '../../app/state.dart';
|
||||||
|
import '../../app/views/user_interaction.dart';
|
||||||
import '../../core/models.dart';
|
import '../../core/models.dart';
|
||||||
import '../../oath/models.dart';
|
import '../../oath/models.dart';
|
||||||
import '../../oath/state.dart';
|
import '../../oath/state.dart';
|
||||||
@ -254,6 +255,7 @@ class _DesktopCredentialListNotifier extends OathCredentialListNotifier {
|
|||||||
controller = await _withContext(
|
controller = await _withContext(
|
||||||
(context) async => promptUserInteraction(
|
(context) async => promptUserInteraction(
|
||||||
context,
|
context,
|
||||||
|
icon: const Icon(Icons.touch_app),
|
||||||
title: 'Touch Required',
|
title: 'Touch Required',
|
||||||
description: 'Touch the button on your YubiKey now.',
|
description: 'Touch the button on your YubiKey now.',
|
||||||
),
|
),
|
||||||
|
@ -162,9 +162,9 @@ abstract class _FidoState extends FidoState {
|
|||||||
_$_FidoState.fromJson;
|
_$_FidoState.fromJson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> get info => throw _privateConstructorUsedError;
|
Map<String, dynamic> get info;
|
||||||
@override
|
@override
|
||||||
bool get unlocked => throw _privateConstructorUsedError;
|
bool get unlocked;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_FidoStateCopyWith<_$_FidoState> get copyWith =>
|
_$$_FidoStateCopyWith<_$_FidoState> get copyWith =>
|
||||||
@ -471,8 +471,8 @@ abstract class _PinFailure implements PinResult {
|
|||||||
factory _PinFailure(final int retries, final bool authBlocked) =
|
factory _PinFailure(final int retries, final bool authBlocked) =
|
||||||
_$_PinFailure;
|
_$_PinFailure;
|
||||||
|
|
||||||
int get retries => throw _privateConstructorUsedError;
|
int get retries;
|
||||||
bool get authBlocked => throw _privateConstructorUsedError;
|
bool get authBlocked;
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_PinFailureCopyWith<_$_PinFailure> get copyWith =>
|
_$$_PinFailureCopyWith<_$_PinFailure> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
@ -620,9 +620,9 @@ abstract class _Fingerprint extends Fingerprint {
|
|||||||
_$_Fingerprint.fromJson;
|
_$_Fingerprint.fromJson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get templateId => throw _privateConstructorUsedError;
|
String get templateId;
|
||||||
@override
|
@override
|
||||||
String? get name => throw _privateConstructorUsedError;
|
String? get name;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_FingerprintCopyWith<_$_Fingerprint> get copyWith =>
|
_$$_FingerprintCopyWith<_$_Fingerprint> get copyWith =>
|
||||||
@ -828,7 +828,7 @@ class _$_EventCapture implements _EventCapture {
|
|||||||
abstract class _EventCapture implements FingerprintEvent {
|
abstract class _EventCapture implements FingerprintEvent {
|
||||||
factory _EventCapture(final int remaining) = _$_EventCapture;
|
factory _EventCapture(final int remaining) = _$_EventCapture;
|
||||||
|
|
||||||
int get remaining => throw _privateConstructorUsedError;
|
int get remaining;
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_EventCaptureCopyWith<_$_EventCapture> get copyWith =>
|
_$$_EventCaptureCopyWith<_$_EventCapture> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
@ -978,7 +978,7 @@ class _$_EventComplete implements _EventComplete {
|
|||||||
abstract class _EventComplete implements FingerprintEvent {
|
abstract class _EventComplete implements FingerprintEvent {
|
||||||
factory _EventComplete(final Fingerprint fingerprint) = _$_EventComplete;
|
factory _EventComplete(final Fingerprint fingerprint) = _$_EventComplete;
|
||||||
|
|
||||||
Fingerprint get fingerprint => throw _privateConstructorUsedError;
|
Fingerprint get fingerprint;
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_EventCompleteCopyWith<_$_EventComplete> get copyWith =>
|
_$$_EventCompleteCopyWith<_$_EventComplete> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
@ -1118,7 +1118,7 @@ class _$_EventError implements _EventError {
|
|||||||
abstract class _EventError implements FingerprintEvent {
|
abstract class _EventError implements FingerprintEvent {
|
||||||
factory _EventError(final int code) = _$_EventError;
|
factory _EventError(final int code) = _$_EventError;
|
||||||
|
|
||||||
int get code => throw _privateConstructorUsedError;
|
int get code;
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_EventErrorCopyWith<_$_EventError> get copyWith =>
|
_$$_EventErrorCopyWith<_$_EventError> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
@ -1304,13 +1304,13 @@ abstract class _FidoCredential implements FidoCredential {
|
|||||||
_$_FidoCredential.fromJson;
|
_$_FidoCredential.fromJson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get rpId => throw _privateConstructorUsedError;
|
String get rpId;
|
||||||
@override
|
@override
|
||||||
String get credentialId => throw _privateConstructorUsedError;
|
String get credentialId;
|
||||||
@override
|
@override
|
||||||
String get userId => throw _privateConstructorUsedError;
|
String get userId;
|
||||||
@override
|
@override
|
||||||
String get userName => throw _privateConstructorUsedError;
|
String get userName;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_FidoCredentialCopyWith<_$_FidoCredential> get copyWith =>
|
_$$_FidoCredentialCopyWith<_$_FidoCredential> get copyWith =>
|
||||||
|
@ -10,6 +10,7 @@ import 'package:yubico_authenticator/app/logging.dart';
|
|||||||
import '../../app/message.dart';
|
import '../../app/message.dart';
|
||||||
import '../../desktop/models.dart';
|
import '../../desktop/models.dart';
|
||||||
import '../../widgets/responsive_dialog.dart';
|
import '../../widgets/responsive_dialog.dart';
|
||||||
|
import '../../widgets/utf8_utils.dart';
|
||||||
import '../state.dart';
|
import '../state.dart';
|
||||||
import '../../fido/models.dart';
|
import '../../fido/models.dart';
|
||||||
import '../../app/models.dart';
|
import '../../app/models.dart';
|
||||||
@ -179,6 +180,8 @@ class _AddFingerprintDialogState extends ConsumerState<AddFingerprintDialog>
|
|||||||
TextFormField(
|
TextFormField(
|
||||||
focusNode: _nameFocus,
|
focusNode: _nameFocus,
|
||||||
maxLength: 15,
|
maxLength: 15,
|
||||||
|
inputFormatters: [limitBytesLength(15)],
|
||||||
|
buildCounter: buildByteCounterFor(_label),
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
enabled: _fingerprint != null,
|
enabled: _fingerprint != null,
|
||||||
|
@ -6,7 +6,7 @@ import '../../app/models.dart';
|
|||||||
import '../../app/views/app_page.dart';
|
import '../../app/views/app_page.dart';
|
||||||
import '../../app/views/graphics.dart';
|
import '../../app/views/graphics.dart';
|
||||||
import '../../app/views/message_page.dart';
|
import '../../app/views/message_page.dart';
|
||||||
import '../../theme.dart';
|
import '../../widgets/menu_list_tile.dart';
|
||||||
import '../models.dart';
|
import '../models.dart';
|
||||||
import '../state.dart';
|
import '../state.dart';
|
||||||
import 'pin_dialog.dart';
|
import 'pin_dialog.dart';
|
||||||
@ -27,7 +27,7 @@ class FidoLockedPage extends ConsumerWidget {
|
|||||||
graphic: noFingerprints,
|
graphic: noFingerprints,
|
||||||
header: 'No fingerprints',
|
header: 'No fingerprints',
|
||||||
message: 'Set a PIN to register fingerprints',
|
message: 'Set a PIN to register fingerprints',
|
||||||
actions: _buildActions(context),
|
keyActions: _buildActions(context),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return MessagePage(
|
return MessagePage(
|
||||||
@ -36,7 +36,7 @@ class FidoLockedPage extends ConsumerWidget {
|
|||||||
header: state.credMgmt ? 'No discoverable accounts' : 'Ready to use',
|
header: state.credMgmt ? 'No discoverable accounts' : 'Ready to use',
|
||||||
message:
|
message:
|
||||||
'Optionally set a PIN to protect access to your YubiKey\nRegister as a Security Key on websites',
|
'Optionally set a PIN to protect access to your YubiKey\nRegister as a Security Key on websites',
|
||||||
actions: _buildActions(context),
|
keyActions: _buildActions(context),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,13 +47,13 @@ class FidoLockedPage extends ConsumerWidget {
|
|||||||
graphic: manageAccounts,
|
graphic: manageAccounts,
|
||||||
header: 'Ready to use',
|
header: 'Ready to use',
|
||||||
message: 'Register as a Security Key on websites',
|
message: 'Register as a Security Key on websites',
|
||||||
actions: _buildActions(context),
|
keyActions: _buildActions(context),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return AppPage(
|
return AppPage(
|
||||||
title: const Text('WebAuthn'),
|
title: const Text('WebAuthn'),
|
||||||
actions: _buildActions(context),
|
keyActions: _buildActions(context),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_PinEntryForm(state, node),
|
_PinEntryForm(state, node),
|
||||||
@ -62,48 +62,26 @@ class FidoLockedPage extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildActions(BuildContext context) => [
|
List<PopupMenuEntry> _buildActions(BuildContext context) => [
|
||||||
if (!state.hasPin)
|
if (!state.hasPin)
|
||||||
OutlinedButton.icon(
|
buildMenuItem(
|
||||||
style: state.bioEnroll != null
|
title: const Text('Set PIN'),
|
||||||
? AppTheme.primaryOutlinedButtonStyle(context)
|
leading: const Icon(Icons.pin),
|
||||||
: null,
|
action: () {
|
||||||
label: const Text('Set PIN'),
|
showBlurDialog(
|
||||||
icon: const Icon(Icons.pin),
|
|
||||||
onPressed: () {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => FidoPinDialog(node.path, state),
|
builder: (context) => FidoPinDialog(node.path, state),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
OutlinedButton.icon(
|
buildMenuItem(
|
||||||
label: const Text('Options'),
|
title: const Text('Reset FIDO'),
|
||||||
icon: const Icon(Icons.tune),
|
leading: const Icon(Icons.delete),
|
||||||
onPressed: () {
|
action: () {
|
||||||
showBottomMenu(context, [
|
showBlurDialog(
|
||||||
if (state.hasPin)
|
context: context,
|
||||||
MenuAction(
|
builder: (context) => ResetDialog(node),
|
||||||
text: 'Change PIN',
|
);
|
||||||
icon: const Icon(Icons.pin),
|
|
||||||
action: (context) {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => FidoPinDialog(node.path, state),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
MenuAction(
|
|
||||||
text: 'Reset FIDO',
|
|
||||||
icon: const Icon(Icons.delete),
|
|
||||||
action: (context) {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => ResetDialog(node),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import '../../app/message.dart';
|
import '../../app/message.dart';
|
||||||
import '../../desktop/models.dart';
|
import '../../desktop/models.dart';
|
||||||
import '../../widgets/responsive_dialog.dart';
|
import '../../widgets/responsive_dialog.dart';
|
||||||
|
import '../../widgets/utf8_utils.dart';
|
||||||
import '../models.dart';
|
import '../models.dart';
|
||||||
import '../state.dart';
|
import '../state.dart';
|
||||||
import '../../app/models.dart';
|
import '../../app/models.dart';
|
||||||
@ -69,8 +70,9 @@ class _RenameAccountDialogState extends ConsumerState<RenameFingerprintDialog> {
|
|||||||
const Text('This will change the label of the fingerprint.'),
|
const Text('This will change the label of the fingerprint.'),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
initialValue: _label,
|
initialValue: _label,
|
||||||
// TODO: Make this field count UTF-8 bytes instead of characters.
|
|
||||||
maxLength: 15,
|
maxLength: 15,
|
||||||
|
inputFormatters: [limitBytesLength(15)],
|
||||||
|
buildCounter: buildByteCounterFor(_label),
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
labelText: 'Label',
|
labelText: 'Label',
|
||||||
|
@ -6,8 +6,8 @@ import '../../app/models.dart';
|
|||||||
import '../../app/views/app_page.dart';
|
import '../../app/views/app_page.dart';
|
||||||
import '../../app/views/graphics.dart';
|
import '../../app/views/graphics.dart';
|
||||||
import '../../app/views/message_page.dart';
|
import '../../app/views/message_page.dart';
|
||||||
import '../../theme.dart';
|
|
||||||
import '../../widgets/list_title.dart';
|
import '../../widgets/list_title.dart';
|
||||||
|
import '../../widgets/menu_list_tile.dart';
|
||||||
import '../models.dart';
|
import '../models.dart';
|
||||||
import '../state.dart';
|
import '../state.dart';
|
||||||
import 'add_fingerprint_dialog.dart';
|
import 'add_fingerprint_dialog.dart';
|
||||||
@ -57,7 +57,7 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDialog(
|
showBlurDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
DeleteCredentialDialog(node.path, cred),
|
DeleteCredentialDialog(node.path, cred),
|
||||||
@ -96,7 +96,7 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDialog(
|
showBlurDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
RenameFingerprintDialog(node.path, fp),
|
RenameFingerprintDialog(node.path, fp),
|
||||||
@ -105,7 +105,7 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
icon: const Icon(Icons.edit_outlined)),
|
icon: const Icon(Icons.edit_outlined)),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDialog(
|
showBlurDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
DeleteFingerprintDialog(node.path, fp),
|
DeleteFingerprintDialog(node.path, fp),
|
||||||
@ -121,7 +121,7 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
if (children.isNotEmpty) {
|
if (children.isNotEmpty) {
|
||||||
return AppPage(
|
return AppPage(
|
||||||
title: const Text('WebAuthn'),
|
title: const Text('WebAuthn'),
|
||||||
actions: _buildActions(context),
|
keyActions: _buildKeyActions(context),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start, children: children),
|
crossAxisAlignment: CrossAxisAlignment.start, children: children),
|
||||||
);
|
);
|
||||||
@ -133,7 +133,7 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
graphic: noFingerprints,
|
graphic: noFingerprints,
|
||||||
header: 'No fingerprints',
|
header: 'No fingerprints',
|
||||||
message: 'Add one or more (up to five) fingerprints',
|
message: 'Add one or more (up to five) fingerprints',
|
||||||
actions: _buildActions(context, fingerprintPrimary: true),
|
keyActions: _buildKeyActions(context),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +142,7 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
graphic: manageAccounts,
|
graphic: manageAccounts,
|
||||||
header: 'No discoverable accounts',
|
header: 'No discoverable accounts',
|
||||||
message: 'Register as a Security Key on websites',
|
message: 'Register as a Security Key on websites',
|
||||||
actions: _buildActions(context),
|
keyActions: _buildKeyActions(context),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,49 +152,36 @@ class FidoUnlockedPage extends ConsumerWidget {
|
|||||||
child: const CircularProgressIndicator(),
|
child: const CircularProgressIndicator(),
|
||||||
);
|
);
|
||||||
|
|
||||||
List<Widget> _buildActions(BuildContext context,
|
List<PopupMenuEntry> _buildKeyActions(BuildContext context) => [
|
||||||
{bool fingerprintPrimary = false}) =>
|
|
||||||
[
|
|
||||||
if (state.bioEnroll != null)
|
if (state.bioEnroll != null)
|
||||||
OutlinedButton.icon(
|
buildMenuItem(
|
||||||
style: fingerprintPrimary
|
title: const Text('Add fingerprint'),
|
||||||
? AppTheme.primaryOutlinedButtonStyle(context)
|
leading: const Icon(Icons.fingerprint),
|
||||||
: null,
|
action: () {
|
||||||
label: const Text('Add fingerprint'),
|
showBlurDialog(
|
||||||
icon: const Icon(Icons.fingerprint),
|
|
||||||
onPressed: () {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AddFingerprintDialog(node.path),
|
builder: (context) => AddFingerprintDialog(node.path),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
OutlinedButton.icon(
|
buildMenuItem(
|
||||||
label: const Text('Options'),
|
title: const Text('Change PIN'),
|
||||||
icon: const Icon(Icons.tune),
|
leading: const Icon(Icons.pin),
|
||||||
onPressed: () {
|
action: () {
|
||||||
showBottomMenu(context, [
|
showBlurDialog(
|
||||||
MenuAction(
|
context: context,
|
||||||
text: 'Change PIN',
|
builder: (context) => FidoPinDialog(node.path, state),
|
||||||
icon: const Icon(Icons.pin),
|
);
|
||||||
action: (context) {
|
},
|
||||||
showDialog(
|
),
|
||||||
context: context,
|
buildMenuItem(
|
||||||
builder: (context) => FidoPinDialog(node.path, state),
|
title: const Text('Reset FIDO'),
|
||||||
);
|
leading: const Icon(Icons.delete),
|
||||||
},
|
action: () {
|
||||||
),
|
showBlurDialog(
|
||||||
MenuAction(
|
context: context,
|
||||||
text: 'Reset FIDO',
|
builder: (context) => ResetDialog(node),
|
||||||
icon: const Icon(Icons.delete),
|
);
|
||||||
action: (context) {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => ResetDialog(node),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
@ -208,14 +208,13 @@ abstract class _DeviceConfig implements DeviceConfig {
|
|||||||
_$_DeviceConfig.fromJson;
|
_$_DeviceConfig.fromJson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<Transport, int> get enabledCapabilities =>
|
Map<Transport, int> get enabledCapabilities;
|
||||||
throw _privateConstructorUsedError;
|
|
||||||
@override
|
@override
|
||||||
int? get autoEjectTimeout => throw _privateConstructorUsedError;
|
int? get autoEjectTimeout;
|
||||||
@override
|
@override
|
||||||
int? get challengeResponseTimeout => throw _privateConstructorUsedError;
|
int? get challengeResponseTimeout;
|
||||||
@override
|
@override
|
||||||
int? get deviceFlags => throw _privateConstructorUsedError;
|
int? get deviceFlags;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_DeviceConfigCopyWith<_$_DeviceConfig> get copyWith =>
|
_$$_DeviceConfigCopyWith<_$_DeviceConfig> get copyWith =>
|
||||||
@ -514,22 +513,21 @@ abstract class _DeviceInfo implements DeviceInfo {
|
|||||||
_$_DeviceInfo.fromJson;
|
_$_DeviceInfo.fromJson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DeviceConfig get config => throw _privateConstructorUsedError;
|
DeviceConfig get config;
|
||||||
@override
|
@override
|
||||||
int? get serial => throw _privateConstructorUsedError;
|
int? get serial;
|
||||||
@override
|
@override
|
||||||
Version get version => throw _privateConstructorUsedError;
|
Version get version;
|
||||||
@override
|
@override
|
||||||
FormFactor get formFactor => throw _privateConstructorUsedError;
|
FormFactor get formFactor;
|
||||||
@override
|
@override
|
||||||
Map<Transport, int> get supportedCapabilities =>
|
Map<Transport, int> get supportedCapabilities;
|
||||||
throw _privateConstructorUsedError;
|
|
||||||
@override
|
@override
|
||||||
bool get isLocked => throw _privateConstructorUsedError;
|
bool get isLocked;
|
||||||
@override
|
@override
|
||||||
bool get isFips => throw _privateConstructorUsedError;
|
bool get isFips;
|
||||||
@override
|
@override
|
||||||
bool get isSky => throw _privateConstructorUsedError;
|
bool get isSky;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_DeviceInfoCopyWith<_$_DeviceInfo> get copyWith =>
|
_$$_DeviceInfoCopyWith<_$_DeviceInfo> get copyWith =>
|
||||||
|
@ -180,7 +180,7 @@ class _ManagementScreenState extends ConsumerState<ManagementScreen> {
|
|||||||
context,
|
context,
|
||||||
'Reconfiguring YubiKey...',
|
'Reconfiguring YubiKey...',
|
||||||
duration: const Duration(seconds: 8),
|
duration: const Duration(seconds: 8),
|
||||||
).close;
|
);
|
||||||
}
|
}
|
||||||
await ref
|
await ref
|
||||||
.read(managementStateProvider(widget.deviceData.node.path).notifier)
|
.read(managementStateProvider(widget.deviceData.node.path).notifier)
|
||||||
|
@ -125,26 +125,18 @@ class CredentialData with _$CredentialData {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Uri toUri() {
|
Uri toUri() => Uri(
|
||||||
final path = issuer != null ? '$issuer:$name' : name;
|
scheme: 'otpauth',
|
||||||
var uri = 'otpauth://${oathType.name}/$path?secret=$secret';
|
host: oathType.name,
|
||||||
switch (oathType) {
|
path: issuer != null ? '$issuer:$name' : name,
|
||||||
case OathType.hotp:
|
queryParameters: {
|
||||||
uri += '&counter=$counter';
|
'secret': secret,
|
||||||
break;
|
if (oathType == OathType.totp) 'period': period.toString(),
|
||||||
case OathType.totp:
|
if (oathType == OathType.hotp) 'counter': counter.toString(),
|
||||||
uri += '&period=$period';
|
if (issuer != null) 'issuer': issuer!,
|
||||||
break;
|
if (digits != 6) 'digits': digits.toString(),
|
||||||
}
|
if (hashAlgorithm != HashAlgorithm.sha1)
|
||||||
if (issuer != null) {
|
'algorithm': hashAlgorithm.name,
|
||||||
uri += '&issuer=$issuer';
|
},
|
||||||
}
|
);
|
||||||
if (digits != 6) {
|
|
||||||
uri += '&digits=$digits';
|
|
||||||
}
|
|
||||||
if (hashAlgorithm != HashAlgorithm.sha1) {
|
|
||||||
uri += '&algorithm=${hashAlgorithm.name}';
|
|
||||||
}
|
|
||||||
return Uri.parse(uri);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -253,19 +253,19 @@ abstract class _OathCredential implements OathCredential {
|
|||||||
_$_OathCredential.fromJson;
|
_$_OathCredential.fromJson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get deviceId => throw _privateConstructorUsedError;
|
String get deviceId;
|
||||||
@override
|
@override
|
||||||
String get id => throw _privateConstructorUsedError;
|
String get id;
|
||||||
@override
|
@override
|
||||||
String? get issuer => throw _privateConstructorUsedError;
|
String? get issuer;
|
||||||
@override
|
@override
|
||||||
String get name => throw _privateConstructorUsedError;
|
String get name;
|
||||||
@override
|
@override
|
||||||
OathType get oathType => throw _privateConstructorUsedError;
|
OathType get oathType;
|
||||||
@override
|
@override
|
||||||
int get period => throw _privateConstructorUsedError;
|
int get period;
|
||||||
@override
|
@override
|
||||||
bool get touchRequired => throw _privateConstructorUsedError;
|
bool get touchRequired;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_OathCredentialCopyWith<_$_OathCredential> get copyWith =>
|
_$$_OathCredentialCopyWith<_$_OathCredential> get copyWith =>
|
||||||
@ -424,11 +424,11 @@ abstract class _OathCode implements OathCode {
|
|||||||
factory _OathCode.fromJson(Map<String, dynamic> json) = _$_OathCode.fromJson;
|
factory _OathCode.fromJson(Map<String, dynamic> json) = _$_OathCode.fromJson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get value => throw _privateConstructorUsedError;
|
String get value;
|
||||||
@override
|
@override
|
||||||
int get validFrom => throw _privateConstructorUsedError;
|
int get validFrom;
|
||||||
@override
|
@override
|
||||||
int get validTo => throw _privateConstructorUsedError;
|
int get validTo;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_OathCodeCopyWith<_$_OathCode> get copyWith =>
|
_$$_OathCodeCopyWith<_$_OathCode> get copyWith =>
|
||||||
@ -599,9 +599,9 @@ abstract class _OathPair implements OathPair {
|
|||||||
factory _OathPair.fromJson(Map<String, dynamic> json) = _$_OathPair.fromJson;
|
factory _OathPair.fromJson(Map<String, dynamic> json) = _$_OathPair.fromJson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
OathCredential get credential => throw _privateConstructorUsedError;
|
OathCredential get credential;
|
||||||
@override
|
@override
|
||||||
OathCode? get code => throw _privateConstructorUsedError;
|
OathCode? get code;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_OathPairCopyWith<_$_OathPair> get copyWith =>
|
_$$_OathPairCopyWith<_$_OathPair> get copyWith =>
|
||||||
@ -838,17 +838,17 @@ abstract class _OathState implements OathState {
|
|||||||
_$_OathState.fromJson;
|
_$_OathState.fromJson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get deviceId => throw _privateConstructorUsedError;
|
String get deviceId;
|
||||||
@override
|
@override
|
||||||
Version get version => throw _privateConstructorUsedError;
|
Version get version;
|
||||||
@override
|
@override
|
||||||
bool get hasKey => throw _privateConstructorUsedError;
|
bool get hasKey;
|
||||||
@override
|
@override
|
||||||
bool get remembered => throw _privateConstructorUsedError;
|
bool get remembered;
|
||||||
@override
|
@override
|
||||||
bool get locked => throw _privateConstructorUsedError;
|
bool get locked;
|
||||||
@override
|
@override
|
||||||
KeystoreState get keystore => throw _privateConstructorUsedError;
|
KeystoreState get keystore;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_OathStateCopyWith<_$_OathState> get copyWith =>
|
_$$_OathStateCopyWith<_$_OathState> get copyWith =>
|
||||||
@ -1126,21 +1126,21 @@ abstract class _CredentialData extends CredentialData {
|
|||||||
_$_CredentialData.fromJson;
|
_$_CredentialData.fromJson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? get issuer => throw _privateConstructorUsedError;
|
String? get issuer;
|
||||||
@override
|
@override
|
||||||
String get name => throw _privateConstructorUsedError;
|
String get name;
|
||||||
@override
|
@override
|
||||||
String get secret => throw _privateConstructorUsedError;
|
String get secret;
|
||||||
@override
|
@override
|
||||||
OathType get oathType => throw _privateConstructorUsedError;
|
OathType get oathType;
|
||||||
@override
|
@override
|
||||||
HashAlgorithm get hashAlgorithm => throw _privateConstructorUsedError;
|
HashAlgorithm get hashAlgorithm;
|
||||||
@override
|
@override
|
||||||
int get digits => throw _privateConstructorUsedError;
|
int get digits;
|
||||||
@override
|
@override
|
||||||
int get period => throw _privateConstructorUsedError;
|
int get period;
|
||||||
@override
|
@override
|
||||||
int get counter => throw _privateConstructorUsedError;
|
int get counter;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_CredentialDataCopyWith<_$_CredentialData> get copyWith =>
|
_$$_CredentialDataCopyWith<_$_CredentialData> get copyWith =>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
@ -59,15 +60,39 @@ abstract class OathCredentialListNotifier
|
|||||||
Future<void> deleteAccount(OathCredential credential);
|
Future<void> deleteAccount(OathCredential credential);
|
||||||
}
|
}
|
||||||
|
|
||||||
final credentialsProvider = Provider.autoDispose<List<OathCredential>?>((ref) {
|
final credentialsProvider = StateNotifierProvider.autoDispose<
|
||||||
|
_CredentialsProviderNotifier, List<OathCredential>?>((ref) {
|
||||||
|
final provider = _CredentialsProviderNotifier();
|
||||||
final node = ref.watch(currentDeviceProvider);
|
final node = ref.watch(currentDeviceProvider);
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
return ref.watch(credentialListProvider(node.path)
|
ref.listen<List<OathPair>?>(credentialListProvider(node.path),
|
||||||
.select((pairs) => pairs?.map((e) => e.credential).toList()));
|
(previous, next) {
|
||||||
|
provider._updatePairs(next);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return null;
|
return provider;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
class _CredentialsProviderNotifier
|
||||||
|
extends StateNotifier<List<OathCredential>?> {
|
||||||
|
_CredentialsProviderNotifier() : super(null);
|
||||||
|
|
||||||
|
void _updatePairs(List<OathPair>? pairs) {
|
||||||
|
if (mounted) {
|
||||||
|
if (pairs == null) {
|
||||||
|
if (state != null) {
|
||||||
|
state = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final creds = pairs.map((p) => p.credential).toList();
|
||||||
|
if (!const ListEquality().equals(creds, state)) {
|
||||||
|
state = creds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final codeProvider =
|
final codeProvider =
|
||||||
Provider.autoDispose.family<OathCode?, OathCredential>((ref, credential) {
|
Provider.autoDispose.family<OathCode?, OathCredential>((ref, credential) {
|
||||||
final node = ref.watch(currentDeviceProvider);
|
final node = ref.watch(currentDeviceProvider);
|
||||||
|
@ -3,11 +3,11 @@ import 'dart:async';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import '../../app/message.dart';
|
||||||
import '../../app/shortcuts.dart';
|
import '../../app/shortcuts.dart';
|
||||||
import '../../app/state.dart';
|
import '../../app/state.dart';
|
||||||
import '../../core/models.dart';
|
import '../../core/models.dart';
|
||||||
import '../../core/state.dart';
|
import '../../core/state.dart';
|
||||||
import '../../widgets/dialog_frame.dart';
|
|
||||||
import '../models.dart';
|
import '../models.dart';
|
||||||
import 'account_mixin.dart';
|
import 'account_mixin.dart';
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ class AccountDialog extends ConsumerWidget with AccountMixin {
|
|||||||
// Replace this dialog with a new one, for the renamed credential.
|
// Replace this dialog with a new one, for the renamed credential.
|
||||||
await ref.read(withContextProvider)((context) async {
|
await ref.read(withContextProvider)((context) async {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
await showDialog(
|
await showBlurDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AccountDialog(renamed);
|
return AccountDialog(renamed);
|
||||||
@ -94,7 +94,7 @@ class AccountDialog extends ConsumerWidget with AccountMixin {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
// TODO: Solve this in a cleaner way
|
// TODO: Solve this in a cleaner way
|
||||||
if (ref.watch(currentDeviceDataProvider) == null) {
|
if (ref.watch(currentDeviceDataProvider) is! AsyncData) {
|
||||||
// The rest of this method assumes there is a device, and will throw an exception if not.
|
// 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
|
// This will never be shown, as the dialog will be immediately closed
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
@ -120,70 +120,68 @@ class AccountDialog extends ConsumerWidget with AccountMixin {
|
|||||||
return null;
|
return null;
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
child: Focus(
|
child: FocusScope(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
child: DialogFrame(
|
child: AlertDialog(
|
||||||
child: AlertDialog(
|
title: Center(
|
||||||
title: Center(
|
child: Text(
|
||||||
child: Text(
|
title,
|
||||||
title,
|
overflow: TextOverflow.fade,
|
||||||
overflow: TextOverflow.fade,
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
style: Theme.of(context).textTheme.headlineSmall,
|
maxLines: 1,
|
||||||
maxLines: 1,
|
softWrap: false,
|
||||||
softWrap: false,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0),
|
),
|
||||||
content: Column(
|
contentPadding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
content: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
mainAxisSize: MainAxisSize.min,
|
||||||
if (subtitle != null)
|
children: [
|
||||||
Text(
|
if (subtitle != null)
|
||||||
subtitle!,
|
Text(
|
||||||
overflow: TextOverflow.fade,
|
subtitle!,
|
||||||
maxLines: 1,
|
overflow: TextOverflow.fade,
|
||||||
softWrap: false,
|
maxLines: 1,
|
||||||
// This is what ListTile uses for subtitle
|
softWrap: false,
|
||||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
// This is what ListTile uses for subtitle
|
||||||
color: Theme.of(context).textTheme.caption!.color,
|
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||||
),
|
color: Theme.of(context).textTheme.caption!.color,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12.0),
|
),
|
||||||
DecoratedBox(
|
const SizedBox(height: 12.0),
|
||||||
decoration: BoxDecoration(
|
DecoratedBox(
|
||||||
shape: BoxShape.rectangle,
|
decoration: BoxDecoration(
|
||||||
color: CardTheme.of(context).color,
|
shape: BoxShape.rectangle,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(30.0)),
|
color: CardTheme.of(context).color,
|
||||||
),
|
borderRadius: const BorderRadius.all(Radius.circular(30.0)),
|
||||||
child: Center(
|
),
|
||||||
child: FittedBox(
|
child: Center(
|
||||||
child: DefaultTextStyle.merge(
|
child: FittedBox(
|
||||||
style: const TextStyle(fontSize: 28),
|
child: DefaultTextStyle.merge(
|
||||||
child: IconTheme(
|
style: const TextStyle(fontSize: 28),
|
||||||
data: IconTheme.of(context).copyWith(size: 24),
|
child: IconTheme(
|
||||||
child: Padding(
|
data: IconTheme.of(context).copyWith(size: 24),
|
||||||
padding: const EdgeInsets.symmetric(
|
child: Padding(
|
||||||
horizontal: 16.0, vertical: 8.0),
|
padding: const EdgeInsets.symmetric(
|
||||||
child: buildCodeView(ref),
|
horizontal: 16.0, vertical: 8.0),
|
||||||
),
|
child: buildCodeView(ref),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
|
||||||
actionsPadding: const EdgeInsets.only(top: 10.0, right: -16.0),
|
|
||||||
actions: [
|
|
||||||
Center(
|
|
||||||
child: FittedBox(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Row(children: _buildActions(context, ref)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
actionsPadding: const EdgeInsets.only(top: 10.0, right: -16.0),
|
||||||
|
actions: [
|
||||||
|
Center(
|
||||||
|
child: FittedBox(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Row(children: _buildActions(context, ref)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import '../../app/models.dart';
|
import '../../app/models.dart';
|
||||||
@ -8,65 +7,14 @@ import '../models.dart';
|
|||||||
import '../state.dart';
|
import '../state.dart';
|
||||||
import 'account_view.dart';
|
import 'account_view.dart';
|
||||||
|
|
||||||
class AccountList extends ConsumerStatefulWidget {
|
class AccountList extends ConsumerWidget {
|
||||||
final DevicePath devicePath;
|
final DevicePath devicePath;
|
||||||
final OathState oathState;
|
final OathState oathState;
|
||||||
const AccountList(this.devicePath, this.oathState, {super.key});
|
const AccountList(this.devicePath, this.oathState, {super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<AccountList> createState() => _AccountListState();
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
}
|
final accounts = ref.watch(credentialListProvider(devicePath));
|
||||||
|
|
||||||
class _AccountListState extends ConsumerState<AccountList> {
|
|
||||||
List<OathCredential> _credentials = [];
|
|
||||||
Map<OathCredential, FocusNode> _focusNodes = {};
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
super.dispose();
|
|
||||||
for (var e in _focusNodes.values) {
|
|
||||||
e.dispose();
|
|
||||||
}
|
|
||||||
_focusNodes.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateFocusNodes() {
|
|
||||||
_focusNodes = {
|
|
||||||
for (var cred in _credentials)
|
|
||||||
cred: _focusNodes[cred] ??
|
|
||||||
FocusNode(
|
|
||||||
onKeyEvent: (node, event) {
|
|
||||||
if (event is KeyDownEvent) {
|
|
||||||
int index = -1;
|
|
||||||
ScrollPositionAlignmentPolicy policy =
|
|
||||||
ScrollPositionAlignmentPolicy.explicit;
|
|
||||||
if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
|
||||||
index = _credentials.indexOf(cred) + 1;
|
|
||||||
policy = ScrollPositionAlignmentPolicy.keepVisibleAtEnd;
|
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
|
||||||
index = _credentials.indexOf(cred) - 1;
|
|
||||||
policy = ScrollPositionAlignmentPolicy.keepVisibleAtStart;
|
|
||||||
}
|
|
||||||
if (index >= 0 && index < _credentials.length) {
|
|
||||||
final targetNode = _focusNodes[_credentials[index]]!;
|
|
||||||
targetNode.requestFocus();
|
|
||||||
Scrollable.ensureVisible(
|
|
||||||
targetNode.context!,
|
|
||||||
alignmentPolicy: policy,
|
|
||||||
);
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
},
|
|
||||||
)
|
|
||||||
};
|
|
||||||
_focusNodes.removeWhere((cred, _) => !_credentials.contains(cred));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final accounts = ref.watch(credentialListProvider(widget.devicePath));
|
|
||||||
if (accounts == null) {
|
if (accounts == null) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@ -88,27 +36,24 @@ class _AccountListState extends ConsumerState<AccountList> {
|
|||||||
final creds =
|
final creds =
|
||||||
credentials.where((entry) => !favorites.contains(entry.credential.id));
|
credentials.where((entry) => !favorites.contains(entry.credential.id));
|
||||||
|
|
||||||
_credentials =
|
return FocusTraversalGroup(
|
||||||
pinnedCreds.followedBy(creds).map((e) => e.credential).toList();
|
policy: WidgetOrderTraversalPolicy(),
|
||||||
_updateFocusNodes();
|
child: Column(
|
||||||
|
children: [
|
||||||
return Column(
|
if (pinnedCreds.isNotEmpty) const ListTitle('Pinned'),
|
||||||
children: [
|
...pinnedCreds.map(
|
||||||
if (pinnedCreds.isNotEmpty) const ListTitle('Pinned'),
|
(entry) => AccountView(
|
||||||
...pinnedCreds.map(
|
entry.credential,
|
||||||
(entry) => AccountView(
|
),
|
||||||
entry.credential,
|
|
||||||
focusNode: _focusNodes[entry.credential],
|
|
||||||
),
|
),
|
||||||
),
|
if (creds.isNotEmpty) const ListTitle('Accounts'),
|
||||||
if (creds.isNotEmpty) const ListTitle('Accounts'),
|
...creds.map(
|
||||||
...creds.map(
|
(entry) => AccountView(
|
||||||
(entry) => AccountView(
|
entry.credential,
|
||||||
entry.credential,
|
),
|
||||||
focusNode: _focusNodes[entry.credential],
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -78,7 +79,7 @@ mixin AccountMixin {
|
|||||||
Future<OathCredential?> renameCredential(
|
Future<OathCredential?> renameCredential(
|
||||||
BuildContext context, WidgetRef ref) async {
|
BuildContext context, WidgetRef ref) async {
|
||||||
final node = ref.read(currentDeviceProvider)!;
|
final node = ref.read(currentDeviceProvider)!;
|
||||||
return await showDialog(
|
return await showBlurDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => RenameAccountDialog(node, credential),
|
builder: (context) => RenameAccountDialog(node, credential),
|
||||||
);
|
);
|
||||||
@ -87,7 +88,7 @@ mixin AccountMixin {
|
|||||||
@protected
|
@protected
|
||||||
Future<bool> deleteCredential(BuildContext context, WidgetRef ref) async {
|
Future<bool> deleteCredential(BuildContext context, WidgetRef ref) async {
|
||||||
final node = ref.read(currentDeviceProvider)!;
|
final node = ref.read(currentDeviceProvider)!;
|
||||||
return await showDialog(
|
return await showBlurDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => DeleteAccountDialog(node, credential),
|
builder: (context) => DeleteAccountDialog(node, credential),
|
||||||
) ??
|
) ??
|
||||||
@ -95,67 +96,72 @@ mixin AccountMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
List<MenuAction> buildActions(BuildContext context, WidgetRef ref) {
|
List<MenuAction> buildActions(BuildContext context, WidgetRef ref) =>
|
||||||
final deviceData = ref.watch(currentDeviceDataProvider);
|
ref.watch(currentDeviceDataProvider).maybeWhen(
|
||||||
if (deviceData == null) {
|
data: (data) {
|
||||||
return [];
|
final code = getCode(ref);
|
||||||
}
|
final expired = isExpired(code, ref);
|
||||||
final code = getCode(ref);
|
final manual = credential.touchRequired ||
|
||||||
final expired = isExpired(code, ref);
|
credential.oathType == OathType.hotp;
|
||||||
final manual =
|
final ready = expired || credential.oathType == OathType.hotp;
|
||||||
credential.touchRequired || credential.oathType == OathType.hotp;
|
final pinned = isPinned(ref);
|
||||||
final ready = expired || credential.oathType == OathType.hotp;
|
|
||||||
final pinned = isPinned(ref);
|
|
||||||
|
|
||||||
return [
|
final shortcut = Platform.isMacOS ? '\u2318 C' : 'Ctrl+C';
|
||||||
MenuAction(
|
return [
|
||||||
text: 'Copy to clipboard',
|
MenuAction(
|
||||||
icon: const Icon(Icons.copy),
|
text: 'Copy to clipboard ($shortcut)',
|
||||||
action: code == null || expired
|
icon: const Icon(Icons.copy),
|
||||||
? null
|
action: code == null || expired
|
||||||
: (context) {
|
? null
|
||||||
Clipboard.setData(ClipboardData(text: code.value));
|
: (context) {
|
||||||
showMessage(context, 'Code copied to clipboard');
|
Clipboard.setData(ClipboardData(text: code.value));
|
||||||
},
|
showMessage(context, 'Code copied to clipboard');
|
||||||
),
|
},
|
||||||
if (manual)
|
),
|
||||||
MenuAction(
|
if (manual)
|
||||||
text: 'Calculate',
|
MenuAction(
|
||||||
icon: const Icon(Icons.refresh),
|
text: 'Calculate',
|
||||||
action: ready
|
icon: const Icon(Icons.refresh),
|
||||||
? (context) async {
|
action: ready
|
||||||
try {
|
? (context) async {
|
||||||
await calculateCode(context, ref);
|
try {
|
||||||
} on CancellationException catch (_) {
|
await calculateCode(context, ref);
|
||||||
// ignored
|
} on CancellationException catch (_) {
|
||||||
}
|
// ignored
|
||||||
}
|
}
|
||||||
: null,
|
}
|
||||||
),
|
: null,
|
||||||
MenuAction(
|
),
|
||||||
text: pinned ? 'Unpin account' : 'Pin account',
|
MenuAction(
|
||||||
icon: pinned ? pushPinStrokeIcon : const Icon(Icons.push_pin_outlined),
|
text: pinned ? 'Unpin account' : 'Pin account',
|
||||||
action: (context) {
|
icon: pinned
|
||||||
ref.read(favoritesProvider.notifier).toggleFavorite(credential.id);
|
? pushPinStrokeIcon
|
||||||
},
|
: const Icon(Icons.push_pin_outlined),
|
||||||
),
|
action: (context) {
|
||||||
if (deviceData.info.version.isAtLeast(5, 3))
|
ref
|
||||||
MenuAction(
|
.read(favoritesProvider.notifier)
|
||||||
icon: const Icon(Icons.edit_outlined),
|
.toggleFavorite(credential.id);
|
||||||
text: 'Rename account',
|
},
|
||||||
action: (context) async {
|
),
|
||||||
await renameCredential(context, ref);
|
if (data.info.version.isAtLeast(5, 3))
|
||||||
},
|
MenuAction(
|
||||||
),
|
icon: const Icon(Icons.edit_outlined),
|
||||||
MenuAction(
|
text: 'Rename account',
|
||||||
text: 'Delete account',
|
action: (context) async {
|
||||||
icon: const Icon(Icons.delete_outline),
|
await renameCredential(context, ref);
|
||||||
action: (context) async {
|
},
|
||||||
await deleteCredential(context, ref);
|
),
|
||||||
},
|
MenuAction(
|
||||||
),
|
text: 'Delete account',
|
||||||
];
|
icon: const Icon(Icons.delete_outline),
|
||||||
}
|
action: (context) async {
|
||||||
|
await deleteCredential(context, ref);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
orElse: () => [],
|
||||||
|
);
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
Widget buildCodeView(WidgetRef ref) {
|
Widget buildCodeView(WidgetRef ref) {
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:yubico_authenticator/cancellation_exception.dart';
|
import 'package:yubico_authenticator/cancellation_exception.dart';
|
||||||
import 'package:yubico_authenticator/app/state.dart';
|
import 'package:yubico_authenticator/app/state.dart';
|
||||||
|
|
||||||
|
import '../../app/message.dart';
|
||||||
import '../../app/shortcuts.dart';
|
import '../../app/shortcuts.dart';
|
||||||
import '../../app/state.dart';
|
import '../../app/state.dart';
|
||||||
|
import '../../widgets/menu_list_tile.dart';
|
||||||
import '../models.dart';
|
import '../models.dart';
|
||||||
import '../state.dart';
|
import '../state.dart';
|
||||||
import 'account_dialog.dart';
|
import 'account_dialog.dart';
|
||||||
@ -16,8 +16,7 @@ import 'account_mixin.dart';
|
|||||||
class AccountView extends ConsumerWidget with AccountMixin {
|
class AccountView extends ConsumerWidget with AccountMixin {
|
||||||
@override
|
@override
|
||||||
final OathCredential credential;
|
final OathCredential credential;
|
||||||
final FocusNode? focusNode;
|
AccountView(this.credential, {super.key});
|
||||||
AccountView(this.credential, {super.key, this.focusNode});
|
|
||||||
|
|
||||||
Color _iconColor(int shade) {
|
Color _iconColor(int shade) {
|
||||||
final colors = [
|
final colors = [
|
||||||
@ -47,22 +46,16 @@ class AccountView extends ConsumerWidget with AccountMixin {
|
|||||||
List<PopupMenuItem> _buildPopupMenu(BuildContext context, WidgetRef ref) {
|
List<PopupMenuItem> _buildPopupMenu(BuildContext context, WidgetRef ref) {
|
||||||
return buildActions(context, ref).map((e) {
|
return buildActions(context, ref).map((e) {
|
||||||
final action = e.action;
|
final action = e.action;
|
||||||
return PopupMenuItem(
|
return buildMenuItem(
|
||||||
enabled: action != null,
|
leading: e.icon,
|
||||||
onTap: () {
|
title: Text(e.text),
|
||||||
// As soon as onTap returns, the Navigator is popped,
|
action: action != null
|
||||||
// closing the topmost item. Since we sometimes open new dialogs in
|
? () {
|
||||||
// the action, make sure that happens *after* the pop.
|
ref.read(withContextProvider)((context) async {
|
||||||
Timer(Duration.zero, () {
|
action.call(context);
|
||||||
action?.call(context);
|
});
|
||||||
});
|
}
|
||||||
},
|
: null,
|
||||||
child: ListTile(
|
|
||||||
leading: e.icon,
|
|
||||||
title: Text(e.text),
|
|
||||||
dense: true,
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
@ -122,13 +115,10 @@ class AccountView extends ConsumerWidget with AccountMixin {
|
|||||||
return ListTile(
|
return ListTile(
|
||||||
shape:
|
shape:
|
||||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)),
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)),
|
||||||
focusNode: focusNode,
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showDialog(
|
showBlurDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) => AccountDialog(credential),
|
||||||
return AccountDialog(credential);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onLongPress: triggerCopy,
|
onLongPress: triggerCopy,
|
||||||
|
@ -6,14 +6,15 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:yubico_authenticator/cancellation_exception.dart';
|
import 'package:yubico_authenticator/cancellation_exception.dart';
|
||||||
import 'package:yubico_authenticator/app/logging.dart';
|
|
||||||
|
|
||||||
|
import '../../app/logging.dart';
|
||||||
import '../../app/message.dart';
|
import '../../app/message.dart';
|
||||||
import '../../app/models.dart';
|
import '../../app/models.dart';
|
||||||
import '../../app/state.dart';
|
import '../../app/state.dart';
|
||||||
import '../../desktop/models.dart';
|
import '../../desktop/models.dart';
|
||||||
import '../../widgets/file_drop_target.dart';
|
import '../../widgets/file_drop_target.dart';
|
||||||
import '../../widgets/responsive_dialog.dart';
|
import '../../widgets/responsive_dialog.dart';
|
||||||
|
import '../../widgets/utf8_utils.dart';
|
||||||
import '../models.dart';
|
import '../models.dart';
|
||||||
import '../state.dart';
|
import '../state.dart';
|
||||||
import 'utils.dart';
|
import 'utils.dart';
|
||||||
@ -213,6 +214,8 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
|||||||
autofocus: true,
|
autofocus: true,
|
||||||
enabled: issuerRemaining > 0,
|
enabled: issuerRemaining > 0,
|
||||||
maxLength: max(issuerRemaining, 1),
|
maxLength: max(issuerRemaining, 1),
|
||||||
|
inputFormatters: [limitBytesLength(issuerRemaining)],
|
||||||
|
buildCounter: buildByteCounterFor(_issuerController.text),
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
labelText: 'Issuer (optional)',
|
labelText: 'Issuer (optional)',
|
||||||
@ -232,6 +235,8 @@ class _OathAddAccountPageState extends ConsumerState<OathAddAccountPage> {
|
|||||||
key: const Key('name'),
|
key: const Key('name'),
|
||||||
controller: _accountController,
|
controller: _accountController,
|
||||||
maxLength: max(nameRemaining, 1),
|
maxLength: max(nameRemaining, 1),
|
||||||
|
buildCounter: buildByteCounterFor(_accountController.text),
|
||||||
|
inputFormatters: [limitBytesLength(nameRemaining)],
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
labelText: 'Account name',
|
labelText: 'Account name',
|
||||||
|
@ -12,7 +12,7 @@ import '../../app/views/app_loading_screen.dart';
|
|||||||
import '../../app/views/app_page.dart';
|
import '../../app/views/app_page.dart';
|
||||||
import '../../app/views/graphics.dart';
|
import '../../app/views/graphics.dart';
|
||||||
import '../../app/views/message_page.dart';
|
import '../../app/views/message_page.dart';
|
||||||
import '../../theme.dart';
|
import '../../widgets/menu_list_tile.dart';
|
||||||
import '../models.dart';
|
import '../models.dart';
|
||||||
import '../state.dart';
|
import '../state.dart';
|
||||||
import 'account_list.dart';
|
import 'account_list.dart';
|
||||||
@ -52,34 +52,26 @@ class _LockedView extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) => AppPage(
|
Widget build(BuildContext context, WidgetRef ref) => AppPage(
|
||||||
title: const Text('Authenticator'),
|
title: const Text('Authenticator'),
|
||||||
actions: [
|
keyActions: [
|
||||||
OutlinedButton.icon(
|
buildMenuItem(
|
||||||
label: const Text('Options'),
|
title: const Text('Manage password'),
|
||||||
icon: const Icon(Icons.tune),
|
leading: const Icon(Icons.password),
|
||||||
onPressed: () {
|
action: () {
|
||||||
showBottomMenu(context, [
|
showBlurDialog(
|
||||||
MenuAction(
|
context: context,
|
||||||
text: 'Manage password',
|
builder: (context) =>
|
||||||
icon: const Icon(Icons.password),
|
ManagePasswordDialog(devicePath, oathState),
|
||||||
action: (context) {
|
);
|
||||||
showDialog(
|
},
|
||||||
context: context,
|
),
|
||||||
builder: (context) =>
|
buildMenuItem(
|
||||||
ManagePasswordDialog(devicePath, oathState),
|
title: const Text('Reset OATH'),
|
||||||
);
|
leading: const Icon(Icons.delete),
|
||||||
},
|
action: () {
|
||||||
),
|
showBlurDialog(
|
||||||
MenuAction(
|
context: context,
|
||||||
text: 'Reset OATH',
|
builder: (context) => ResetDialog(devicePath),
|
||||||
icon: const Icon(Icons.delete),
|
);
|
||||||
action: (context) {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => ResetDialog(devicePath),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -124,14 +116,17 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isEmpty = ref.watch(credentialListProvider(widget.devicePath)
|
final credentials = ref.watch(credentialsProvider);
|
||||||
.select((value) => value?.isEmpty == true));
|
if (credentials?.isEmpty == true) {
|
||||||
if (isEmpty) {
|
|
||||||
return MessagePage(
|
return MessagePage(
|
||||||
title: const Text('Authenticator'),
|
title: const Text('Authenticator'),
|
||||||
graphic: noAccounts,
|
graphic: noAccounts,
|
||||||
header: 'No accounts',
|
header: 'No accounts',
|
||||||
actions: _buildActions(context, true),
|
keyActions: _buildActions(
|
||||||
|
context,
|
||||||
|
used: 0,
|
||||||
|
capacity: widget.oathState.version.isAtLeast(4) ? 32 : null,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Actions(
|
return Actions(
|
||||||
@ -143,98 +138,92 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
|
|||||||
return null;
|
return null;
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
child: Focus(
|
child: AppPage(
|
||||||
autofocus: true,
|
title: Focus(
|
||||||
child: AppPage(
|
canRequestFocus: false,
|
||||||
title: Focus(
|
onKeyEvent: (node, event) {
|
||||||
canRequestFocus: false,
|
if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
||||||
onKeyEvent: (node, event) {
|
node.focusInDirection(TraversalDirection.down);
|
||||||
if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
return KeyEventResult.handled;
|
||||||
node.focusInDirection(TraversalDirection.down);
|
}
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.ignored;
|
||||||
}
|
},
|
||||||
return KeyEventResult.ignored;
|
child: Builder(builder: (context) {
|
||||||
},
|
return TextFormField(
|
||||||
child: Builder(builder: (context) {
|
key: const Key('search_accounts'),
|
||||||
return TextFormField(
|
controller: searchController,
|
||||||
key: const Key('search_accounts'),
|
focusNode: searchFocus,
|
||||||
controller: searchController,
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
focusNode: searchFocus,
|
decoration: const InputDecoration(
|
||||||
style: Theme.of(context).textTheme.titleSmall,
|
hintText: 'Search accounts',
|
||||||
decoration: const InputDecoration(
|
isDense: true,
|
||||||
hintText: 'Search accounts',
|
prefixIcon: Icon(Icons.search_outlined),
|
||||||
isDense: true,
|
prefixIconConstraints: BoxConstraints(
|
||||||
prefixIcon: Icon(Icons.search_outlined),
|
minHeight: 30,
|
||||||
prefixIconConstraints: BoxConstraints(
|
minWidth: 30,
|
||||||
minHeight: 30,
|
|
||||||
minWidth: 30,
|
|
||||||
),
|
|
||||||
border: InputBorder.none,
|
|
||||||
),
|
),
|
||||||
onChanged: (value) {
|
border: InputBorder.none,
|
||||||
ref.read(searchProvider.notifier).setFilter(value);
|
),
|
||||||
},
|
onChanged: (value) {
|
||||||
textInputAction: TextInputAction.next,
|
ref.read(searchProvider.notifier).setFilter(value);
|
||||||
onFieldSubmitted: (value) {
|
},
|
||||||
Focus.of(context).focusInDirection(TraversalDirection.down);
|
textInputAction: TextInputAction.next,
|
||||||
},
|
onFieldSubmitted: (value) {
|
||||||
);
|
Focus.of(context).focusInDirection(TraversalDirection.down);
|
||||||
}),
|
},
|
||||||
),
|
);
|
||||||
actions: _buildActions(context, false),
|
}),
|
||||||
child: AccountList(widget.devicePath, widget.oathState),
|
|
||||||
),
|
),
|
||||||
|
keyActions: _buildActions(
|
||||||
|
context,
|
||||||
|
used: credentials?.length ?? 0,
|
||||||
|
capacity: widget.oathState.version.isAtLeast(4) ? 32 : null,
|
||||||
|
),
|
||||||
|
child: AccountList(widget.devicePath, widget.oathState),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildActions(BuildContext context, bool isEmpty) {
|
List<PopupMenuEntry> _buildActions(BuildContext context,
|
||||||
|
{required int used, int? capacity}) {
|
||||||
return [
|
return [
|
||||||
OutlinedButton.icon(
|
buildMenuItem(
|
||||||
style: isEmpty ? AppTheme.primaryOutlinedButtonStyle(context) : null,
|
title: const Text('Add account'),
|
||||||
label: const Text('Add account'),
|
leading: const Icon(Icons.person_add_alt_1),
|
||||||
icon: const Icon(Icons.person_add_alt_1),
|
trailing: capacity != null ? '$used/$capacity' : null,
|
||||||
onPressed: () {
|
action: capacity == null || capacity > used
|
||||||
showDialog(
|
? () {
|
||||||
context: context,
|
showBlurDialog(
|
||||||
builder: (context) => OathAddAccountPage(
|
|
||||||
widget.devicePath,
|
|
||||||
widget.oathState,
|
|
||||||
openQrScanner: Platform.isAndroid,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
OutlinedButton.icon(
|
|
||||||
label: const Text('Options'),
|
|
||||||
icon: const Icon(Icons.tune),
|
|
||||||
onPressed: () {
|
|
||||||
showBottomMenu(context, [
|
|
||||||
MenuAction(
|
|
||||||
text:
|
|
||||||
widget.oathState.hasKey ? 'Manage password' : 'Set password',
|
|
||||||
icon: const Icon(Icons.password),
|
|
||||||
action: (context) {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) =>
|
builder: (context) => OathAddAccountPage(
|
||||||
ManagePasswordDialog(widget.devicePath, widget.oathState),
|
widget.devicePath,
|
||||||
|
widget.oathState,
|
||||||
|
openQrScanner: Platform.isAndroid,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
),
|
: null,
|
||||||
MenuAction(
|
|
||||||
text: 'Reset OATH',
|
|
||||||
icon: const Icon(Icons.delete),
|
|
||||||
action: (context) {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => ResetDialog(widget.devicePath),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
buildMenuItem(
|
||||||
|
title: Text(
|
||||||
|
widget.oathState.hasKey ? 'Manage password' : 'Set password'),
|
||||||
|
leading: const Icon(Icons.password),
|
||||||
|
action: () {
|
||||||
|
showBlurDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) =>
|
||||||
|
ManagePasswordDialog(widget.devicePath, widget.oathState),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
buildMenuItem(
|
||||||
|
title: const Text('Reset OATH'),
|
||||||
|
leading: const Icon(Icons.delete),
|
||||||
|
action: () {
|
||||||
|
showBlurDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ResetDialog(widget.devicePath),
|
||||||
|
);
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import '../../app/message.dart';
|
|||||||
import '../../app/models.dart';
|
import '../../app/models.dart';
|
||||||
import '../../desktop/models.dart';
|
import '../../desktop/models.dart';
|
||||||
import '../../widgets/responsive_dialog.dart';
|
import '../../widgets/responsive_dialog.dart';
|
||||||
|
import '../../widgets/utf8_utils.dart';
|
||||||
import '../models.dart';
|
import '../models.dart';
|
||||||
import '../state.dart';
|
import '../state.dart';
|
||||||
import 'utils.dart';
|
import 'utils.dart';
|
||||||
@ -100,6 +101,8 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
|
|||||||
initialValue: _issuer,
|
initialValue: _issuer,
|
||||||
enabled: issuerRemaining > 0,
|
enabled: issuerRemaining > 0,
|
||||||
maxLength: issuerRemaining > 0 ? issuerRemaining : null,
|
maxLength: issuerRemaining > 0 ? issuerRemaining : null,
|
||||||
|
buildCounter: buildByteCounterFor(_issuer),
|
||||||
|
inputFormatters: [limitBytesLength(issuerRemaining)],
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
labelText: 'Issuer (optional)',
|
labelText: 'Issuer (optional)',
|
||||||
@ -115,6 +118,8 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
|
|||||||
TextFormField(
|
TextFormField(
|
||||||
initialValue: _account,
|
initialValue: _account,
|
||||||
maxLength: nameRemaining,
|
maxLength: nameRemaining,
|
||||||
|
inputFormatters: [limitBytesLength(nameRemaining)],
|
||||||
|
buildCounter: buildByteCounterFor(_account),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
labelText: 'Account name',
|
labelText: 'Account name',
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import '../../widgets/utf8_utils.dart';
|
||||||
import '../models.dart';
|
import '../models.dart';
|
||||||
import '../../core/models.dart';
|
import '../../core/models.dart';
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ Pair<int, int> getRemainingKeySpace(
|
|||||||
// Non-standard TOTP periods are stored as part of this data, as a "D/"- prefix.
|
// Non-standard TOTP periods are stored as part of this data, as a "D/"- prefix.
|
||||||
remaining -= '$period/'.length;
|
remaining -= '$period/'.length;
|
||||||
}
|
}
|
||||||
int issuerSpace = issuer.length;
|
int issuerSpace = byteLength(issuer);
|
||||||
if (issuer.isNotEmpty) {
|
if (issuer.isNotEmpty) {
|
||||||
// Issuer is separated from name with a ":", if present.
|
// Issuer is separated from name with a ":", if present.
|
||||||
issuerSpace += 1;
|
issuerSpace += 1;
|
||||||
@ -26,7 +27,7 @@ Pair<int, int> getRemainingKeySpace(
|
|||||||
|
|
||||||
return Pair(
|
return Pair(
|
||||||
// Always reserve at least one character for name
|
// Always reserve at least one character for name
|
||||||
remaining - 1 - max(name.length, 1),
|
remaining - 1 - max(byteLength(name), 1),
|
||||||
remaining - issuerSpace,
|
remaining - issuerSpace,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class DialogFrame extends StatelessWidget {
|
|
||||||
final Widget child;
|
|
||||||
const DialogFrame({super.key, required this.child});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
// Shows Snackbars above modal
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
body: GestureDetector(
|
|
||||||
behavior: HitTestBehavior.deferToChild,
|
|
||||||
onTap: () {}, // Block onTap of parent gesture detector
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
31
lib/widgets/menu_list_tile.dart
Executable file
31
lib/widgets/menu_list_tile.dart
Executable file
@ -0,0 +1,31 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
PopupMenuItem buildMenuItem({
|
||||||
|
required Widget title,
|
||||||
|
Widget? leading,
|
||||||
|
String? trailing,
|
||||||
|
void Function()? action,
|
||||||
|
}) =>
|
||||||
|
PopupMenuItem(
|
||||||
|
enabled: action != null,
|
||||||
|
onTap: () {
|
||||||
|
// Wait for popup menu to close before running action.
|
||||||
|
Timer.run(action!);
|
||||||
|
},
|
||||||
|
child: ListTile(
|
||||||
|
enabled: action != null,
|
||||||
|
dense: true,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
minLeadingWidth: 0,
|
||||||
|
title: title,
|
||||||
|
leading: leading,
|
||||||
|
trailing: trailing != null
|
||||||
|
? Opacity(
|
||||||
|
opacity: 0.5,
|
||||||
|
child: Text(trailing, textScaleFactor: 0.7),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
@ -1,7 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'dialog_frame.dart';
|
|
||||||
|
|
||||||
class ResponsiveDialog extends StatefulWidget {
|
class ResponsiveDialog extends StatefulWidget {
|
||||||
final Widget? title;
|
final Widget? title;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
@ -48,25 +46,23 @@ class _ResponsiveDialogState extends State<ResponsiveDialog> {
|
|||||||
final cancelText = widget.onCancel == null && widget.actions.isEmpty
|
final cancelText = widget.onCancel == null && widget.actions.isEmpty
|
||||||
? 'Close'
|
? 'Close'
|
||||||
: 'Cancel';
|
: 'Cancel';
|
||||||
return DialogFrame(
|
return AlertDialog(
|
||||||
child: AlertDialog(
|
title: widget.title,
|
||||||
title: widget.title,
|
scrollable: true,
|
||||||
scrollable: true,
|
content: SizedBox(
|
||||||
content: SizedBox(
|
width: 380,
|
||||||
width: 380,
|
child: Container(key: _childKey, child: widget.child),
|
||||||
child: Container(key: _childKey, child: widget.child),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
child: Text(cancelText),
|
|
||||||
onPressed: () {
|
|
||||||
widget.onCancel?.call();
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
...widget.actions
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
child: Text(cancelText),
|
||||||
|
onPressed: () {
|
||||||
|
widget.onCancel?.call();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
...widget.actions
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
126
lib/widgets/toast.dart
Executable file
126
lib/widgets/toast.dart
Executable file
@ -0,0 +1,126 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class Toast extends StatefulWidget {
|
||||||
|
final String message;
|
||||||
|
final Duration duration;
|
||||||
|
final void Function() onComplete;
|
||||||
|
final Color? backgroundColor;
|
||||||
|
final TextStyle? textStyle;
|
||||||
|
const Toast(
|
||||||
|
this.message,
|
||||||
|
this.duration, {
|
||||||
|
required this.onComplete,
|
||||||
|
this.backgroundColor,
|
||||||
|
this.textStyle,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _ToastState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ToastState extends State<Toast> with SingleTickerProviderStateMixin {
|
||||||
|
late AnimationController _animator;
|
||||||
|
late Tween<double> _tween;
|
||||||
|
late Animation<double> _opacity;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
_animator = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 100), vsync: this);
|
||||||
|
_tween = Tween(begin: 0, end: 1);
|
||||||
|
_opacity = _tween.animate(_animator)
|
||||||
|
..addListener(() {
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
|
||||||
|
_animate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _animate() async {
|
||||||
|
await _animator.forward();
|
||||||
|
if (mounted) {
|
||||||
|
await Future.delayed(widget.duration);
|
||||||
|
}
|
||||||
|
if (mounted) {
|
||||||
|
await _animator.reverse();
|
||||||
|
}
|
||||||
|
widget.onComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_animator.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FadeTransition(
|
||||||
|
opacity: _opacity,
|
||||||
|
child: Material(
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(4.0)),
|
||||||
|
),
|
||||||
|
color: widget.backgroundColor,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
widget.message,
|
||||||
|
style: widget.textStyle,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Function() showToast(
|
||||||
|
BuildContext context,
|
||||||
|
String message, {
|
||||||
|
Duration duration = const Duration(seconds: 2),
|
||||||
|
}) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final colorScheme = theme.colorScheme;
|
||||||
|
final bool isThemeDark = theme.brightness == Brightness.dark;
|
||||||
|
final Color backgroundColor = isThemeDark
|
||||||
|
? colorScheme.onSurface
|
||||||
|
: Color.alphaBlend(
|
||||||
|
colorScheme.onSurface.withOpacity(0.80), colorScheme.surface);
|
||||||
|
final textStyle =
|
||||||
|
ThemeData(brightness: isThemeDark ? Brightness.light : Brightness.dark)
|
||||||
|
.textTheme
|
||||||
|
.subtitle1;
|
||||||
|
|
||||||
|
OverlayEntry? entry;
|
||||||
|
void close() {
|
||||||
|
if (entry != null && entry.mounted) {
|
||||||
|
entry.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = OverlayEntry(builder: (context) {
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Container(
|
||||||
|
height: 50,
|
||||||
|
width: 400,
|
||||||
|
margin: const EdgeInsets.all(8),
|
||||||
|
child: Toast(
|
||||||
|
message,
|
||||||
|
duration,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
textStyle: textStyle,
|
||||||
|
onComplete: close,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
Timer.run(() {
|
||||||
|
Overlay.of(context)!.insert(entry!);
|
||||||
|
});
|
||||||
|
|
||||||
|
return close;
|
||||||
|
}
|
29
lib/widgets/utf8_utils.dart
Executable file
29
lib/widgets/utf8_utils.dart
Executable file
@ -0,0 +1,29 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
/// Get the number of bytes used by a String when encoded to UTF-8.
|
||||||
|
int byteLength(String value) => utf8.encode(value).length;
|
||||||
|
|
||||||
|
/// Builds a counter widget showing number of bytes used/available.
|
||||||
|
///
|
||||||
|
/// Set this as a [TextField.buildCounter] callback to show the number of bytes
|
||||||
|
/// used rather than number of characters. [currentValue] should always match
|
||||||
|
/// the input text value to measure.
|
||||||
|
InputCounterWidgetBuilder buildByteCounterFor(String currentValue) =>
|
||||||
|
(context, {required currentLength, required isFocused, maxLength}) => Text(
|
||||||
|
maxLength != null ? '${byteLength(currentValue)}/$maxLength' : '',
|
||||||
|
style: Theme.of(context).textTheme.caption,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Limits the input in length based on the byte length when encoded.
|
||||||
|
/// This is generally used together with [buildByteCounterFor].
|
||||||
|
TextInputFormatter limitBytesLength(int maxByteLength) =>
|
||||||
|
TextInputFormatter.withFunction((oldValue, newValue) {
|
||||||
|
final newLength = byteLength(newValue.text);
|
||||||
|
if (newLength <= maxByteLength) {
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
return oldValue;
|
||||||
|
});
|
32
pubspec.lock
32
pubspec.lock
@ -7,14 +7,14 @@ packages:
|
|||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "38.0.0"
|
version: "41.0.0"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: "direct overridden"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.1"
|
version: "4.2.0"
|
||||||
archive:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -98,7 +98,7 @@ packages:
|
|||||||
name: built_value
|
name: built_value
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.3.2"
|
version: "8.3.3"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -189,7 +189,7 @@ packages:
|
|||||||
name: ffi
|
name: ffi
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.1"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -244,7 +244,7 @@ packages:
|
|||||||
name: freezed
|
name: freezed
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.3+1"
|
version: "2.0.4"
|
||||||
freezed_annotation:
|
freezed_annotation:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -270,7 +270,7 @@ packages:
|
|||||||
name: glob
|
name: glob
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.1.0"
|
||||||
graphs:
|
graphs:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -284,7 +284,7 @@ packages:
|
|||||||
name: http_multi_server
|
name: http_multi_server
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.0"
|
version: "3.2.1"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -373,7 +373,7 @@ packages:
|
|||||||
name: package_config
|
name: package_config
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.1.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -408,7 +408,7 @@ packages:
|
|||||||
name: pigeon
|
name: pigeon
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.5"
|
version: "3.2.3"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -429,7 +429,7 @@ packages:
|
|||||||
name: pool
|
name: pool
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.0"
|
version: "1.5.1"
|
||||||
process:
|
process:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -534,14 +534,14 @@ packages:
|
|||||||
name: shelf
|
name: shelf
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.1"
|
||||||
shelf_web_socket:
|
shelf_web_socket:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shelf_web_socket
|
name: shelf_web_socket
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.2"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -644,7 +644,7 @@ packages:
|
|||||||
name: url_launcher
|
name: url_launcher
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.3"
|
version: "6.1.4"
|
||||||
url_launcher_android:
|
url_launcher_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -679,14 +679,14 @@ packages:
|
|||||||
name: url_launcher_platform_interface
|
name: url_launcher_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.5"
|
version: "2.1.0"
|
||||||
url_launcher_web:
|
url_launcher_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_web
|
name: url_launcher_web
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.11"
|
version: "2.0.12"
|
||||||
url_launcher_windows:
|
url_launcher_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
7
pubspec.yaml
Executable file → Normal file
7
pubspec.yaml
Executable file → Normal file
@ -43,7 +43,7 @@ dependencies:
|
|||||||
collection: ^1.16.0
|
collection: ^1.16.0
|
||||||
shared_preferences: ^2.0.12
|
shared_preferences: ^2.0.12
|
||||||
flutter_riverpod: ^1.0.0
|
flutter_riverpod: ^1.0.0
|
||||||
json_annotation: ^4.4.0
|
json_annotation: ^4.5.0
|
||||||
freezed_annotation: ^2.0.3
|
freezed_annotation: ^2.0.3
|
||||||
window_manager: ^0.2.0
|
window_manager: ^0.2.0
|
||||||
qrscanner_zxing:
|
qrscanner_zxing:
|
||||||
@ -51,9 +51,6 @@ dependencies:
|
|||||||
desktop_drop: ^0.3.3
|
desktop_drop: ^0.3.3
|
||||||
url_launcher: ^6.1.2
|
url_launcher: ^6.1.2
|
||||||
|
|
||||||
dependency_overrides:
|
|
||||||
analyzer: ^3.4.1
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
integration_test:
|
integration_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
@ -70,7 +67,7 @@ dev_dependencies:
|
|||||||
build_runner: ^2.1.4
|
build_runner: ^2.1.4
|
||||||
freezed: ^2.0.3
|
freezed: ^2.0.3
|
||||||
json_serializable: ^6.0.0
|
json_serializable: ^6.0.0
|
||||||
pigeon: ^3.0.3
|
pigeon: ^3.1.6
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
BIN
resources/win/license.rtf
Normal file
BIN
resources/win/license.rtf
Normal file
Binary file not shown.
19
resources/win/release-win.ps1
Normal file
19
resources/win/release-win.ps1
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
$version=6.0.0-dev.0
|
||||||
|
|
||||||
|
echo "Renaming the Actions folder and moving it"
|
||||||
|
mv yubioath-desktop-* release
|
||||||
|
|
||||||
|
echo "Signing the executables"
|
||||||
|
signtool.exe sign /fd SHA256 /t http://timestamp.digicert.com/scripts/timstamp.dll release/authenticator.exe
|
||||||
|
signtool.exe sign /fd SHA256 /t http://timestamp.digicert.com/scripts/timstamp.dll release/helper/authenticator-helper.exe
|
||||||
|
|
||||||
|
echo "Setting env var and building installer"
|
||||||
|
$env:SRCDIR = ".\release\"
|
||||||
|
heat dir .\release -out fragment.wxs -gg -scom -srd -sfrag -dr INSTALLDIR -cg ApplicationFiles -var env.SRCDIR
|
||||||
|
candle .\fragment.wxs .\resources\win\yubioath-desktop.wxs -ext WixUtilExtension -arch x64
|
||||||
|
light fragment.wixobj yubioath-desktop.wixobj -ext WixUIExtension -ext WixUtilExtension -o yubioath-desktop-$version-win64.msi
|
||||||
|
|
||||||
|
echo "Signing the installer"
|
||||||
|
signtool.exe sign /d "Yubico Authenticator" /fd SHA256 /t http://timestamp.digicert.com/scripts/timstamp.dll yubioath-desktop-$version-win64.msi
|
||||||
|
|
||||||
|
echo "All done"
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
<WixVariable Id="WixUIDialogBmp" Value="resources\icons\yubico-msi-background.png" />
|
<WixVariable Id="WixUIDialogBmp" Value="resources\icons\yubico-msi-background.png" />
|
||||||
<WixVariable Id="WixUIBannerBmp" Value="resources\icons\yubico-msi-y-banner.png" />
|
<WixVariable Id="WixUIBannerBmp" Value="resources\icons\yubico-msi-y-banner.png" />
|
||||||
|
<WixVariable Id="WixUILicenseRtf" Value="license.rtf" />
|
||||||
|
|
||||||
<Icon Id="icon.ico" SourceFile="resources\icons\com.yubico.yubioath.ico"/>
|
<Icon Id="icon.ico" SourceFile="resources\icons\com.yubico.yubioath.ico"/>
|
||||||
<Property Id="ARPPRODUCTICON" Value="icon.ico" />
|
<Property Id="ARPPRODUCTICON" Value="icon.ico" />
|
||||||
@ -31,6 +32,11 @@
|
|||||||
<UI>
|
<UI>
|
||||||
<UIRef Id="WixUI_InstallDir" />
|
<UIRef Id="WixUI_InstallDir" />
|
||||||
<Publish Dialog="WelcomeDlg"
|
<Publish Dialog="WelcomeDlg"
|
||||||
|
Control="Next"
|
||||||
|
Event="NewDialog"
|
||||||
|
Value="LicenseAgreementDlg"
|
||||||
|
Order="2">1</Publish>
|
||||||
|
<Publish Dialog="LicenseAgreementDlg"
|
||||||
Control="Next"
|
Control="Next"
|
||||||
Event="NewDialog"
|
Event="NewDialog"
|
||||||
Value="InstallDirDlg"
|
Value="InstallDirDlg"
|
||||||
|
@ -131,7 +131,15 @@ def update_helper_version(buf):
|
|||||||
)
|
)
|
||||||
return buf
|
return buf
|
||||||
|
|
||||||
|
# release-win.ps1
|
||||||
|
def update_release_win(buf):
|
||||||
|
return sub1(
|
||||||
|
rf'\$version={version_pattern}',
|
||||||
|
f'$version={version}',
|
||||||
|
buf,
|
||||||
|
)
|
||||||
|
|
||||||
update_file("pubspec.yaml", update_pubspec)
|
update_file("pubspec.yaml", update_pubspec)
|
||||||
update_file("windows/runner/Runner.rc", update_runner_rc)
|
update_file("windows/runner/Runner.rc", update_runner_rc)
|
||||||
update_file("helper/version_info.txt", update_helper_version)
|
update_file("helper/version_info.txt", update_helper_version)
|
||||||
|
update_file("resources/win/release-win.ps1", update_release_win)
|
Loading…
Reference in New Issue
Block a user