فهرست منبع

键盘加一行运算符;顶栏去标题并压扁

补上一直缺的五个**运算符**(它们是运算符不是函数,所以放键盘而不是函数面板):

  =   赋值        x = 1
  ==  方程        Solve[x^2-1==0, x] —— 实测 == 不能省,
                  Solve[x^2-1, x] 会被 Symja 原样退回
  /.  代入        2x+1 /. x -> 1
  ->  规则        /. 的另一半,也是 Limit 的写法
  _   模式占位    f[x_] := x^2 + 1

没有它们,"解方程/赋值/代入"在计算器里根本没法输入,只能去控制台。

顺带两处排版:
  · 顶栏去掉"计算器"标题、高度压到 44 —— 省下的竖向空间给多出来的一行
  · 提交键改名 ↵(键盘负责敲字符,↵ 负责执行)。原来它和赋值键都叫 =,会撞车

键盘 9 行:
  ∫ Σ d/dx lim solve
  sin cos tan ln √
  x a b t n
  = == /. -> _
  7 8 9 ( /
  4 5 6 × !
  1 2 3 + −
  0 . ^ { },{
  ⌫ CLR , ANS ↵

90 个测试通过(新增方程、赋值两个端到端)。

Co-Authored-By: Claude Code <noreply@anthropic.com>
nonvioce 4 هفته پیش
والد
کامیت
5de096e062
2فایلهای تغییر یافته به همراه41 افزوده شده و 8 حذف شده
  1. 15 4
      lib/calculator_page.dart
  2. 26 4
      test/widget_test.dart

+ 15 - 4
lib/calculator_page.dart

@@ -328,7 +328,8 @@ class _CalculatorPageState extends State<CalculatorPage> {
 
     return Scaffold(
       appBar: AppBar(
-        title: const Text('计算器'),
+        // 不显示标题、顶栏压扁 —— 省下来的竖向空间给键盘
+        toolbarHeight: 44,
         actions: [
           // 精确 / 近似 切换。默认精确:`1/3` 就是 `1/3`,这是符号计算的意义所在。
           TextButton(
@@ -506,7 +507,16 @@ class _CalculatorPageState extends State<CalculatorPage> {
             _Key('t', _Act.char, payload: 't'),
             _Key('n', _Act.char, payload: 'n'),
           ],
-          // 4 数字与括号
+          // 4 运算符:解方程、赋值、代入、定义函数全靠它们。
+          // 都是"运算符"不是"函数",所以放键盘上而不是函数面板里。
+          [
+            _Key('=', _Act.char, payload: '=', tint: true),
+            _Key('==', _Act.char, payload: '==', tint: true),
+            _Key('/.', _Act.char, payload: '/.', tint: true),
+            _Key('->', _Act.char, payload: '->', tint: true),
+            _Key('_', _Act.char, payload: '_', tint: true),
+          ],
+          // 5 数字与括号
           [
             _Key('7', _Act.char, payload: '7', mergeDigit: true),
             _Key('8', _Act.char, payload: '8', mergeDigit: true),
@@ -541,13 +551,14 @@ class _CalculatorPageState extends State<CalculatorPage> {
             _Key('CLR', _Act.clear, tint: true),
             _Key(',', _Act.comma),
             _Key('ANS', _Act.char, payload: '%', tint: true, display: 'ANS'),
-            _Key('=', _Act.commit, primary: true),
+            // 键盘负责"敲字符",↵ 负责"执行"。两者都叫 = 会撞车。
+            _Key('↵', _Act.commit, primary: true),
           ],
         ];
 
         // 7 行 + 6 个间隙,再扣掉 Padding 上下各 6 ——
         // box.maxHeight 是 padding **之前**的高度,漏掉会溢出。
-        final h = (box.maxHeight - 12 - 7 * 6) / 8;
+        final h = (box.maxHeight - 12 - 8 * 6) / 9;
         return Padding(
           padding: const EdgeInsets.all(6),
           child: Column(

+ 26 - 4
test/widget_test.dart

@@ -182,7 +182,7 @@ void main() {
       await pumpApp(tester);
       await press(tester, 'sin');
       await press(tester, 'x');
-      await press(tester, '=');
+      await press(tester, '↵');
 
       final evals = calls.where((c) => c.method == 'evaluate').toList();
       final committed = evals.where((c) => c.arguments['preview'] == false);
@@ -236,6 +236,28 @@ void main() {
       expect(await lastExpr(tester), 'x2+');
     });
 
+    testWidgets('用新加的运算符键敲出一个可解的方程', (tester) async {
+      await pumpApp(tester);
+      await press(tester, 'solve'); // Solve[□, x],光标在第一个参数
+      await press(tester, 'x');
+      await press(tester, '−');
+      await press(tester, '1');
+      await press(tester, '==');
+      await press(tester, '0');
+
+      // 说明:这里刻意不用 ^。按了 ^ 之后光标在**指数槽**里,
+      // 要回到主层得点一下幂节点(点定位),不是靠按键。
+      expect(await lastExpr(tester), 'Solve[x-1==0, x]');
+    });
+
+    testWidgets('赋值:x = 1', (tester) async {
+      await pumpApp(tester);
+      await press(tester, 'x');
+      await press(tester, '=');
+      await press(tester, '1');
+      expect(await lastExpr(tester), 'x=1');
+    });
+
     testWidgets('ANS 键在编辑框显示 ANS,送进引擎的仍是 %', (tester) async {
       await pumpApp(tester);
       await press(tester, '2');
@@ -257,7 +279,7 @@ void main() {
       await pumpApp(tester);
       await press(tester, 'sin');
       await press(tester, 'x');
-      await press(tester, '=');
+      await press(tester, '↵');
 
       final committed = calls.where(
           (c) => c.method == 'evaluate' && c.arguments['preview'] == false);
@@ -273,7 +295,7 @@ void main() {
       await tester.pumpAndSettle();
       expect(find.text('≈ 近似'), findsOneWidget);
 
-      await press(tester, '=');
+      await press(tester, '↵');
       final committed = calls.where(
           (c) => c.method == 'evaluate' && c.arguments['preview'] == false);
       expect(committed.last.arguments['expr'], 'N[Sin[x], 15]');
@@ -410,7 +432,7 @@ void main() {
       await press(tester, '2');
       await press(tester, '+');
       await press(tester, '3');
-      await press(tester, '=');
+      await press(tester, '↵');
 
       // 结果区渲染的是 LaTeX,纯文本那份不在,所以点整个结果条
       await tester.tap(find.byKey(const ValueKey('calc-result')));