From b4b2fc5aab7f3e7b5a12a0e3a0a5c12774e7a742 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 17:22:15 +0000 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20add=20Semantics?= =?UTF-8?q?=20to=20TaskCard=20checkbox=20and=20Tooltips=20to=20icon=20butt?= =?UTF-8?q?ons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Wrap TaskCard custom checkbox with Semantics (label, value, checked, button) - Add Tooltip to GlassIconButton in CustomAppbar and M3EIconButtons in TaskCreationPage - Add widget test for TaskCard checkbox semantics and update .Jules/palette.md --- .Jules/palette.md | 5 ++ .../TaskCreationPage/task_creation_page.dart | 26 +++++--- lib/shared/custom_appbar.dart | 9 ++- lib/shared/task_card.dart | 48 ++++++++------- test/widgets/task_card_test.dart | 61 +++++++++++++++++++ 5 files changed, 115 insertions(+), 34 deletions(-) create mode 100644 .Jules/palette.md create mode 100644 test/widgets/task_card_test.dart diff --git a/.Jules/palette.md b/.Jules/palette.md new file mode 100644 index 0000000..c1cc027 --- /dev/null +++ b/.Jules/palette.md @@ -0,0 +1,5 @@ +# Palette Journal + +## 2026-03-31 - Custom Checkbox Semantics and Tooltip Overlay Patterns +经验心得: 在 Flutter 中,通过 GestureDetector 和 Container 构建的自定义 Checkbox 默认不具备任何屏幕阅读器可读属性。如果不显示声明 `Semantics(label, value, checked, button)`,TalkBack 与 VoiceOver 会完全忽略其交互状态。此外,针对全图标按钮(如 M3EIconButton / GlassIconButton),统一外包裹 `Tooltip` 既能提升桌面端的悬停提示体验,又能为移动端无障碍套件自动注入语义属性。 +后续行动: 凡在此 codebase 中定义非原生 UI 控件(如自定义 Checkbox、Radio 或 Toggle 卡片)时,必须以 `Semantics` 包裹并配置 `label`、`value` 及 `checked`;全图标按钮一律包裹 `Tooltip` 提供显式文字说明。 diff --git a/lib/pages/TaskCreationPage/task_creation_page.dart b/lib/pages/TaskCreationPage/task_creation_page.dart index 25f2e02..e095ada 100644 --- a/lib/pages/TaskCreationPage/task_creation_page.dart +++ b/lib/pages/TaskCreationPage/task_creation_page.dart @@ -28,10 +28,13 @@ class TaskCreationPage { onPrimary: Theme.of(context).colorScheme.onSurface, ), ), - child: M3EIconButton( - icon: Icon(Icons.close_rounded), - variant: M3EIconButtonVariant.filled, - onPressed: () => Navigator.of(context).pop(), + child: Tooltip( + message: '关闭', + child: M3EIconButton( + icon: const Icon(Icons.close_rounded), + variant: M3EIconButtonVariant.filled, + onPressed: () => Navigator.of(context).pop(), + ), ), ), ), @@ -46,13 +49,16 @@ class TaskCreationPage { onPrimary: Theme.of(context).colorScheme.onPrimaryContainer, ), ), - child: M3EIconButton( - icon: Icon( - Icons.add_rounded, - color: Theme.of(context).colorScheme.onPrimaryContainer, + child: Tooltip( + message: '创建任务', + child: M3EIconButton( + icon: Icon( + Icons.add_rounded, + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), + variant: M3EIconButtonVariant.filled, + onPressed: () => _handleCreate(context, ref), ), - variant: M3EIconButtonVariant.filled, - onPressed: () => _handleCreate(context, ref), ), ), ); diff --git a/lib/shared/custom_appbar.dart b/lib/shared/custom_appbar.dart index d0857a8..dd495de 100644 --- a/lib/shared/custom_appbar.dart +++ b/lib/shared/custom_appbar.dart @@ -110,9 +110,12 @@ class CustomAppbar extends StatelessWidget implements PreferredSizeWidget { top: 0, bottom: 0, child: Center( - child: GlassIconButton( - icon: const Icon(Icons.arrow_back_rounded), - onPressed: () => context.pop(), + child: Tooltip( + message: '返回', + child: GlassIconButton( + icon: const Icon(Icons.arrow_back_rounded), + onPressed: () => context.pop(), + ), ), ), ), diff --git a/lib/shared/task_card.dart b/lib/shared/task_card.dart index 955229d..998d08c 100644 --- a/lib/shared/task_card.dart +++ b/lib/shared/task_card.dart @@ -115,29 +115,35 @@ class _TaskCardState extends ConsumerState { // ── Checkbox ───────────────────────────────────────────────── Padding( padding: const EdgeInsets.only(top: 2, right: 12), - child: GestureDetector( - onTap: () { - setState(() => _completed = !_completed); - widget.onToggle?.call(_completed); - }, - child: AnimatedContainer( - duration: const Duration(milliseconds: 250), - curve: Curves.easeInOut, - width: 22, - height: 22, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: _completed ? cs.primary : Colors.transparent, - border: Border.all( - color: _completed - ? cs.primary - : cs.onSurface.withValues(alpha: 0.4), - width: 2, + child: Semantics( + label: '完成任务', + value: _completed ? '已完成' : '未完成', + checked: _completed, + button: true, + child: GestureDetector( + onTap: () { + setState(() => _completed = !_completed); + widget.onToggle?.call(_completed); + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + width: 22, + height: 22, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _completed ? cs.primary : Colors.transparent, + border: Border.all( + color: _completed + ? cs.primary + : cs.onSurface.withValues(alpha: 0.4), + width: 2, + ), ), + child: _completed + ? Icon(Icons.check_rounded, size: 14, color: cs.onPrimary) + : null, ), - child: _completed - ? Icon(Icons.check_rounded, size: 14, color: cs.onPrimary) - : null, ), ), ), diff --git a/test/widgets/task_card_test.dart b/test/widgets/task_card_test.dart new file mode 100644 index 0000000..c64164a --- /dev/null +++ b/test/widgets/task_card_test.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:janus/shared/task_card.dart'; +import 'package:janus/theme/theme.dart'; + +void main() { + testWidgets('TaskCard exposes correct Semantics for checkbox', (tester) async { + bool toggledState = false; + + await tester.pumpWidget( + ProviderScope( + child: MaterialApp( + theme: AppTheme.light, + home: Scaffold( + body: TaskCard( + title: '测试任务', + isCompleted: false, + priority: '中', + ddl: DateTime.now().add(const Duration(hours: 1)), + est: const TimeOfDay(hour: 1, minute: 0), + onToggle: (completed) { + toggledState = completed; + }, + ), + ), + ), + ), + ); + + // Verify initial checkbox semantics + final checkboxSemantics = find.byWidgetPredicate( + (widget) => + widget is Semantics && + widget.properties.label == '完成任务' && + widget.properties.value == '未完成' && + widget.properties.checked == false && + widget.properties.button == true, + ); + + expect(checkboxSemantics, findsOneWidget); + + // Tap checkbox semantics target + await tester.tap(checkboxSemantics); + await tester.pumpAndSettle(); + + expect(toggledState, isTrue); + + // Verify updated checkbox semantics + final updatedSemantics = find.byWidgetPredicate( + (widget) => + widget is Semantics && + widget.properties.label == '完成任务' && + widget.properties.value == '已完成' && + widget.properties.checked == true && + widget.properties.button == true, + ); + + expect(updatedSemantics, findsOneWidget); + }); +} From 921513ef2e893ef459334f17bad9aa0f86de4533 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 17:30:46 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20add=20Semantics?= =?UTF-8?q?=20to=20TaskCard=20checkbox=20and=20Tooltips=20to=20icon=20butt?= =?UTF-8?q?ons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Wrap TaskCard custom checkbox with Semantics (label, value, checked, button) - Add Tooltip to GlassIconButton in CustomAppbar and M3EIconButtons in TaskCreationPage - Add widget test for TaskCard checkbox semantics and update .Jules/palette.md --- lib/shared/task_card.dart | 6 +++++- test/widgets/task_card_test.dart | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/shared/task_card.dart b/lib/shared/task_card.dart index 998d08c..6b9c485 100644 --- a/lib/shared/task_card.dart +++ b/lib/shared/task_card.dart @@ -141,7 +141,11 @@ class _TaskCardState extends ConsumerState { ), ), child: _completed - ? Icon(Icons.check_rounded, size: 14, color: cs.onPrimary) + ? Icon( + Icons.check_rounded, + size: 14, + color: cs.onPrimary, + ) : null, ), ), diff --git a/test/widgets/task_card_test.dart b/test/widgets/task_card_test.dart index c64164a..5ee81b6 100644 --- a/test/widgets/task_card_test.dart +++ b/test/widgets/task_card_test.dart @@ -5,7 +5,9 @@ import 'package:janus/shared/task_card.dart'; import 'package:janus/theme/theme.dart'; void main() { - testWidgets('TaskCard exposes correct Semantics for checkbox', (tester) async { + testWidgets('TaskCard exposes correct Semantics for checkbox', ( + tester, + ) async { bool toggledState = false; await tester.pumpWidget( From ad1c7362b1b707ff8bc8884074095abc5ec59302 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 17:42:25 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20add=20Semantics?= =?UTF-8?q?=20to=20TaskCard=20checkbox=20and=20Tooltips=20to=20icon=20butt?= =?UTF-8?q?ons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Wrap TaskCard custom checkbox with Semantics (label, value, checked, button) - Add Tooltip to GlassIconButton in CustomAppbar and M3EIconButtons in TaskCreationPage - Add widget test for TaskCard checkbox semantics and update .Jules/palette.md From 5d1fd84e3c6d949cf6e3ad3625385a931f262b82 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 12 Sep 2026 10:17:50 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20fix=20coverage=20?= =?UTF-8?q?report=20step=20resilience=20in=20CI=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add apt-get update fallback and command -v genhtml check in ci.yml - Add Semantics to TaskCard custom checkbox - Add Tooltips to icon buttons in CustomAppbar and TaskCreationPage --- .github/workflows/ci.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5631d35..24f802b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,11 +82,16 @@ jobs: - name: Generate Coverage Report if: always() && hashFiles('coverage/lcov.info') != '' run: | - sudo apt-get update -qq && sudo apt-get install -y -qq lcov - genhtml coverage/lcov.info --output-directory coverage/html - COVERAGE=$(lcov --summary coverage/lcov.info 2>&1 | grep lines | grep -oP '\d+\.\d+%' | head -1 || echo "N/A") - echo "### ✅ Test Coverage: $COVERAGE" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" + sudo apt-get update -qq || true + sudo apt-get install -y -qq lcov || true + if command -v genhtml >/dev/null 2>&1; then + genhtml coverage/lcov.info --output-directory coverage/html + COVERAGE=$(lcov --summary coverage/lcov.info 2>&1 | grep lines | grep -oP '\d+\.\d+%' | head -1 || echo "N/A") + echo "### ✅ Test Coverage: $COVERAGE" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + else + echo "::warning::lcov/genhtml package install failed; skipping HTML coverage generation." + fi - name: Upload Coverage Artifact if: always() && hashFiles('coverage/html') != ''