diff --git a/.github/workflows/env b/.github/workflows/env index c91c3513..5b14656c 100644 --- a/.github/workflows/env +++ b/.github/workflows/env @@ -1,2 +1,2 @@ -FLUTTER=3.24.1 +FLUTTER=3.24.2 PYVER=3.12.5 diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 5cc2e39d..42932db6 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -56,13 +56,6 @@ jobs: - run: flutter config --enable-macos-desktop - run: flutter --version - - name: Apply Flutter Patch - run: | - cd $FLUTTER_ROOT - git apply $GITHUB_WORKSPACE/macos_assemble.patch - env: - GITHUB_WORKSPACE: ${{ github.workspace }} - - name: Run lints/tests env: SKIP: ${{ steps.cache-helper.outputs.cache-hit == 'true' && 'mypy,flake8,black,bandit' || ''}} diff --git a/helper/helper/fido.py b/helper/helper/fido.py index 9c2f3d9d..181cac42 100644 --- a/helper/helper/fido.py +++ b/helper/helper/fido.py @@ -186,8 +186,13 @@ class Ctap2Node(RpcNode): try: self.ctap.reset(event=event) except CtapError as e: - if e.code == CtapError.ERR.USER_ACTION_TIMEOUT: + if e.code in ( + # Different keys respond with different errors here + CtapError.ERR.USER_ACTION_TIMEOUT, + CtapError.ERR.ACTION_TIMEOUT, + ): raise InactivityException() + raise self._info = self.ctap.get_info() self._token = None return RpcResponse(dict(), ["device_info"]) diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index e4d053db..e631b957 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -132,7 +132,7 @@ class _DialogProvider extends Notifier { return setNfcView( NfcContentWidget( title: l10n.s_nfc_ready_to_scan, - subtitle: l10n.s_nfc_done, + subtitle: l10n.s_done, icon: const NfcIconSuccess(), ), showIfHidden: false); diff --git a/lib/app/views/app_page.dart b/lib/app/views/app_page.dart index f204637d..741eb9fa 100755 --- a/lib/app/views/app_page.dart +++ b/lib/app/views/app_page.dart @@ -62,7 +62,6 @@ final _navKey = GlobalKey(); final _navExpandedKey = GlobalKey(); final _sliverTitleGlobalKey = GlobalKey(); final _sliverTitleWrapperGlobalKey = GlobalKey(); -final _headerSliverGlobalKey = GlobalKey(); final _detailsViewGlobalKey = GlobalKey(); final _mainContentGlobalKey = GlobalKey(); @@ -446,16 +445,19 @@ class _AppPageState extends ConsumerState { targetKey: _sliverTitleGlobalKey, controller: _sliverTitleController, subTargetKey: - widget.headerSliver != null ? _headerSliverGlobalKey : null, + widget.headerSliver != null ? headerSliverGlobalKey : null, subController: widget.headerSliver != null ? _headerSliverController : null, subAnchorKey: widget.headerSliver != null ? _sliverTitleWrapperGlobalKey : null, child: CustomScrollView( physics: isAndroid - ? const ClampingScrollPhysics( - parent: AlwaysScrollableScrollPhysics()) - : null, + ? const _NoImplicitScrollPhysics( + parent: ClampingScrollPhysics( + parent: AlwaysScrollableScrollPhysics(), + ), + ) + : const _NoImplicitScrollPhysics(), controller: _sliverTitleScrollController, key: _mainContentGlobalKey, slivers: [ @@ -487,11 +489,11 @@ class _AppPageState extends ConsumerState { _sliverTitleScrollController, _headerSliverController.scrollDirection, _headerSliverController, - _headerSliverGlobalKey, + headerSliverGlobalKey, _sliverTitleWrapperGlobalKey); return Container( - key: _headerSliverGlobalKey, + key: headerSliverGlobalKey, child: widget.headerSliver); }, )) @@ -1041,3 +1043,15 @@ class _SliverTitleDelegate extends SliverPersistentHeaderDelegate { @override bool shouldRebuild(_SliverTitleDelegate oldDelegate) => true; } + +class _NoImplicitScrollPhysics extends ScrollPhysics { + const _NoImplicitScrollPhysics({super.parent}); + + @override + bool get allowImplicitScrolling => false; + + @override + _NoImplicitScrollPhysics applyTo(ScrollPhysics? ancestor) { + return _NoImplicitScrollPhysics(parent: buildParent(ancestor)); + } +} diff --git a/lib/app/views/keys.dart b/lib/app/views/keys.dart index 33f1e73c..1ef73f19 100644 --- a/lib/app/views/keys.dart +++ b/lib/app/views/keys.dart @@ -18,6 +18,7 @@ import 'package:flutter/material.dart'; // global keys final scaffoldGlobalKey = GlobalKey(); +final headerSliverGlobalKey = GlobalKey(); // This is global so we can access it from the global Ctrl+F shortcut. final searchField = GlobalKey(); diff --git a/lib/fido/views/passkeys_screen.dart b/lib/fido/views/passkeys_screen.dart index 09afe129..b1231088 100644 --- a/lib/fido/views/passkeys_screen.dart +++ b/lib/fido/views/passkeys_screen.dart @@ -238,7 +238,23 @@ class _FidoUnlockedPageState extends ConsumerState<_FidoUnlockedPage> { super.dispose(); } + void _scrollSearchField() { + // Ensures the search field is fully visible when in focus + final headerSliverContext = headerSliverGlobalKey.currentContext; + if (searchFocus.hasFocus && headerSliverContext != null) { + final scrollable = Scrollable.of(headerSliverContext); + if (scrollable.deltaToScrollOrigin.dy > 0) { + scrollable.position.animateTo( + 0, + duration: const Duration(milliseconds: 100), + curve: Curves.ease, + ); + } + } + } + void _onFocusChange() { + _scrollSearchField(); setState(() {}); } @@ -518,6 +534,7 @@ class _FidoUnlockedPageState extends ConsumerState<_FidoUnlockedPage> { ref .read(passkeysSearchProvider.notifier) .setFilter(value); + _scrollSearchField(); setState(() {}); }, textInputAction: TextInputAction.next, @@ -587,7 +604,8 @@ class _FidoUnlockedPageState extends ConsumerState<_FidoUnlockedPage> { builder: (context, ref, child) { final layout = ref.watch(passkeysLayoutProvider); return Padding( - padding: const EdgeInsets.symmetric(horizontal: 10.0), + padding: + const EdgeInsets.only(left: 10.0, right: 10.0, top: 8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 866aaef0..37c15acd 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -38,6 +38,7 @@ "s_calculate": "Berechnen", "s_import": "Importieren", "s_overwrite": "Überschreiben", + "s_done": null, "s_label": "Beschriftung", "s_name": "Name", "s_usb": "USB", @@ -898,11 +899,11 @@ "l_launch_app_on_usb_off": "Andere Anwendungen können den YubiKey über USB nutzen", "s_allow_screenshots": "Bildschirmfotos erlauben", + "@_nfc": {}, "s_nfc_ready_to_scan": null, "s_nfc_scanning": null, "s_nfc_tap_your_yubikey": null, "l_nfc_failed_to_scan": null, - "s_nfc_done": null, "@_ndef": {}, "p_ndef_set_otp": "OTP-Code wurde erfolgreich von Ihrem YubiKey in die Zwischenablage kopiert.", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 593aa337..6a13798c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -38,6 +38,7 @@ "s_calculate": "Calculate", "s_import": "Import", "s_overwrite": "Overwrite", + "s_done": "Done", "s_label": "Label", "s_name": "Name", "s_usb": "USB", @@ -898,11 +899,11 @@ "l_launch_app_on_usb_off": "Other apps can use the YubiKey over USB", "s_allow_screenshots": "Allow screenshots", + "@_nfc": {}, "s_nfc_ready_to_scan": "Ready to scan", "s_nfc_scanning": "Scanning\u2026", "s_nfc_tap_your_yubikey": "Tap your YubiKey", "l_nfc_failed_to_scan": "Failed to scan, try again", - "s_nfc_done": "Done", "@_ndef": {}, "p_ndef_set_otp": "Successfully copied OTP code from YubiKey to clipboard.", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 79ef7a23..170746d7 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -38,6 +38,7 @@ "s_calculate": "Calculer", "s_import": "Importer", "s_overwrite": "Écraser", + "s_done": null, "s_label": "Étiquette", "s_name": "Nom", "s_usb": "USB", @@ -898,11 +899,11 @@ "l_launch_app_on_usb_off": "D'autres applications peuvent utiliser la YubiKey en USB", "s_allow_screenshots": "Autoriser captures d'écran", + "@_nfc": {}, "s_nfc_ready_to_scan": null, "s_nfc_scanning": null, "s_nfc_tap_your_yubikey": null, "l_nfc_failed_to_scan": null, - "s_nfc_done": null, "@_ndef": {}, "p_ndef_set_otp": "Code OTP copié de la YubiKey dans le presse-papiers.", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 87df5ac2..c7a1fde5 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -38,6 +38,7 @@ "s_calculate": "計算", "s_import": "インポート", "s_overwrite": "上書き", + "s_done": null, "s_label": "ラベル", "s_name": "名前", "s_usb": "USB", @@ -898,11 +899,11 @@ "l_launch_app_on_usb_off": "他のアプリがUSB経由でYubiKeyを使用できます", "s_allow_screenshots": "スクリーンショットを許可", + "@_nfc": {}, "s_nfc_ready_to_scan": null, "s_nfc_scanning": null, "s_nfc_tap_your_yubikey": null, "l_nfc_failed_to_scan": null, - "s_nfc_done": null, "@_ndef": {}, "p_ndef_set_otp": "OTPコードがYubiKeyからクリップボードに正常にコピーされました。", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 5eb0b2dc..91327c7b 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -38,6 +38,7 @@ "s_calculate": "Oblicz", "s_import": "Importuj", "s_overwrite": "Nadpisz", + "s_done": null, "s_label": "Etykieta", "s_name": "Nazwa", "s_usb": "USB", @@ -898,11 +899,11 @@ "l_launch_app_on_usb_off": "Inne aplikacje mogą korzystać z YubiKey przez USB", "s_allow_screenshots": "Zezwalaj na zrzuty ekranu", + "@_nfc": {}, "s_nfc_ready_to_scan": null, "s_nfc_scanning": null, "s_nfc_tap_your_yubikey": null, "l_nfc_failed_to_scan": null, - "s_nfc_done": null, "@_ndef": {}, "p_ndef_set_otp": "OTP zostało skopiowane do schowka.", diff --git a/lib/l10n/app_vi.arb b/lib/l10n/app_vi.arb index b925de68..2c3d68b4 100644 --- a/lib/l10n/app_vi.arb +++ b/lib/l10n/app_vi.arb @@ -38,6 +38,7 @@ "s_calculate": "Tính toán", "s_import": "Nhập khẩu", "s_overwrite": "Ghi đè", + "s_done": null, "s_label": "Nhãn", "s_name": "Tên", "s_usb": "USB", @@ -898,11 +899,11 @@ "l_launch_app_on_usb_off": "Các ứng dụng khác có thể sử dụng YubiKey qua USB", "s_allow_screenshots": "Cho phép chụp ảnh màn hình", + "@_nfc": {}, "s_nfc_ready_to_scan": null, "s_nfc_scanning": null, "s_nfc_tap_your_yubikey": null, "l_nfc_failed_to_scan": null, - "s_nfc_done": null, "@_ndef": {}, "p_ndef_set_otp": "Đã sao chép mã OTP từ YubiKey vào clipboard.", diff --git a/lib/oath/views/oath_screen.dart b/lib/oath/views/oath_screen.dart index e1927218..9ac6af30 100755 --- a/lib/oath/views/oath_screen.dart +++ b/lib/oath/views/oath_screen.dart @@ -154,7 +154,23 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> { super.dispose(); } + void _scrollSearchField() { + // Ensures the search field is fully visible when in focus + final headerSliverContext = headerSliverGlobalKey.currentContext; + if (searchFocus.hasFocus && headerSliverContext != null) { + final scrollable = Scrollable.of(headerSliverContext); + if (scrollable.deltaToScrollOrigin.dy > 0) { + scrollable.position.animateTo( + 0, + duration: const Duration(milliseconds: 100), + curve: Curves.ease, + ); + } + } + } + void _onFocusChange() { + _scrollSearchField(); setState(() {}); } @@ -563,6 +579,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> { ref .read(accountsSearchProvider.notifier) .setFilter(value); + _scrollSearchField(); setState(() {}); }, textInputAction: TextInputAction.next, diff --git a/macos_assemble.patch b/macos_assemble.patch deleted file mode 100644 index 1db8932f..00000000 --- a/macos_assemble.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/packages/flutter_tools/bin/macos_assemble.sh b/packages/flutter_tools/bin/macos_assemble.sh -index 40c6a5051f..a7f05d9113 100755 ---- a/packages/flutter_tools/bin/macos_assemble.sh -+++ b/packages/flutter_tools/bin/macos_assemble.sh -@@ -222,6 +222,7 @@ EmbedFrameworks() { - - # Iterate through all .frameworks in native assets directory. - for native_asset in "${native_assets_path}"*.framework; do -+ [ -e "$native_asset" ] || continue # Skip when there are no matches. - # Codesign the framework inside the app bundle. - RunCommand codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${xcode_frameworks_dir}/$(basename "$native_asset")" - done