Przeglądaj źródła

工具页搜索框让开状态栏

工具页是 IndexedStack 里唯一没有自己的 Scaffold/AppBar 的 tab,直接返回
一个 Column,于是搜索框顶到了状态栏上、和时钟叠在一起。加 SafeArea
(bottom: false —— 下面就是 Shell 的 NavigationBar,底部安全区它已经吃了)。
带一个回归测试:给视口一个 120px 的顶部安全区,断言搜索框落在它下面。

Co-Authored-By: Claude Code <noreply@anthropic.com>
nonvioce 4 tygodni temu
rodzic
commit
8e45e84726
2 zmienionych plików z 76 dodań i 51 usunięć
  1. 59 51
      lib/tools_page.dart
  2. 17 0
      test/widget_test.dart

+ 59 - 51
lib/tools_page.dart

@@ -38,60 +38,68 @@ class _ToolsPageState extends State<ToolsPage> {
   Widget build(BuildContext context) {
     final items = kTools.where((t) => t.matches(_q)).toList();
 
-    return Column(
-      children: [
-        Padding(
-          padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
-          child: TextField(
-            controller: _search,
-            decoration: InputDecoration(
-              isDense: true,
-              hintText: '搜索工具(支持拼音首字母)',
-              prefixIcon: const Icon(Icons.search),
-              suffixIcon: _q.isEmpty
-                  ? null
-                  : IconButton(
-                      icon: const Icon(Icons.clear),
-                      onPressed: () {
-                        _search.clear();
-                        setState(() => _q = '');
-                      },
-                    ),
-              border: const OutlineInputBorder(),
+    // 这一页是唯一没有自己的 Scaffold/AppBar 的 tab(另外两个都有),
+    // 所以得自己让开状态栏 —— 否则搜索框会顶到状态栏上,和时钟叠在一起。
+    // bottom 不用让:下面紧挨着的就是 Shell 的 NavigationBar,它已经把
+    // 底部安全区吃掉了。
+    return SafeArea(
+      bottom: false,
+      child: Column(
+        children: [
+          Padding(
+            padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
+            child: TextField(
+              controller: _search,
+              decoration: InputDecoration(
+                isDense: true,
+                hintText: '搜索工具(支持拼音首字母)',
+                prefixIcon: const Icon(Icons.search),
+                suffixIcon: _q.isEmpty
+                    ? null
+                    : IconButton(
+                        icon: const Icon(Icons.clear),
+                        onPressed: () {
+                          _search.clear();
+                          setState(() => _q = '');
+                        },
+                      ),
+                border: const OutlineInputBorder(),
+              ),
+              onChanged: (v) => setState(() => _q = v),
             ),
-            onChanged: (v) => setState(() => _q = v),
           ),
-        ),
-        Expanded(
-          child: items.isEmpty
-              ? const Center(child: Text('没有匹配的工具'))
-              : GridView.builder(
-                  padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
-                  gridDelegate:
-                      const SliverGridDelegateWithFixedCrossAxisCount(
-                    crossAxisCount: 2,
-                    mainAxisSpacing: 12,
-                    crossAxisSpacing: 12,
-                    childAspectRatio: 1.5,
-                  ),
-                  itemCount: items.length,
-                  itemBuilder: (_, i) {
-                    final t = items[i];
-                    final color = _palette[kTools.indexOf(t) % _palette.length];
-                    return _ToolCard(
-                      plugin: t,
-                      color: color,
-                      onTap: () => Navigator.of(context).push(
-                        MaterialPageRoute<void>(
-                          builder: (_) => t.build(),
-                          settings: RouteSettings(name: t.name),
+          Expanded(
+            child: items.isEmpty
+                ? const Center(child: Text('没有匹配的工具'))
+                : GridView.builder(
+                    padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
+                    gridDelegate:
+                        const SliverGridDelegateWithFixedCrossAxisCount(
+                          crossAxisCount: 2,
+                          mainAxisSpacing: 12,
+                          crossAxisSpacing: 12,
+                          childAspectRatio: 1.5,
                         ),
-                      ),
-                    );
-                  },
-                ),
-        ),
-      ],
+                    itemCount: items.length,
+                    itemBuilder: (_, i) {
+                      final t = items[i];
+                      final color =
+                          _palette[kTools.indexOf(t) % _palette.length];
+                      return _ToolCard(
+                        plugin: t,
+                        color: color,
+                        onTap: () => Navigator.of(context).push(
+                          MaterialPageRoute<void>(
+                            builder: (_) => t.build(),
+                            settings: RouteSettings(name: t.name),
+                          ),
+                        ),
+                      );
+                    },
+                  ),
+          ),
+        ],
+      ),
     );
   }
 }

+ 17 - 0
test/widget_test.dart

@@ -567,5 +567,22 @@ void main() {
       await tester.pumpAndSettle();
       expect(find.text('线性回归'), findsOneWidget);
     });
+
+    testWidgets('搜索框让开状态栏(回归:这页是唯一没有 AppBar 的 tab)', (tester) async {
+      // 真机上搜索框和状态栏的时钟叠在一起 —— 计算器/控制台都有自己的
+      // Scaffold 扛状态栏,工具页直接返回一个 Column,得自己让开安全区
+      tester.view.padding = const FakeViewPadding(top: 120);
+      addTearDown(tester.view.resetPadding);
+
+      await pumpApp(tester);
+      await goTo(tester, '工具');
+
+      final top = tester.getRect(inPage(ToolsPage, find.byType(TextField))).top;
+      expect(
+        top,
+        greaterThanOrEqualTo(120 / tester.view.devicePixelRatio),
+        reason: '搜索框钻到状态栏底下了',
+      );
+    });
   });
 }