From a9a1680f5df5b497d877349a8d5c63cb47db16d0 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 13 Sep 2025 01:20:09 +0800 Subject: [PATCH] :lipstick: Optimize styling --- devtools_options.yaml | 3 + lib/main.dart | 157 ++++++++++++++++------- lib/models/calculation_step.dart | 4 +- lib/{solver_service.dart => solver.dart} | 70 ++++++---- 4 files changed, 163 insertions(+), 71 deletions(-) create mode 100644 devtools_options.yaml rename lib/{solver_service.dart => solver.dart} (91%) diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/main.dart b/lib/main.dart index e8b90b9..80948c4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,20 +1,28 @@ import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:latext/latext.dart'; import 'package:simple_math_calc/models/calculation_step.dart'; -import 'package:simple_math_calc/solver_service.dart'; +import 'package:simple_math_calc/solver.dart'; +import 'dart:math'; void main() async { - runApp(const MyApp()); + runApp(const CalcApp()); } -class MyApp extends StatelessWidget { - const MyApp({super.key}); +class CalcApp extends StatelessWidget { + const CalcApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: '方程计算器', - theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true), + theme: ThemeData( + primarySwatch: Colors.blue, + useMaterial3: true, + textTheme: GoogleFonts.notoSerifScTextTheme( + Theme.of(context).textTheme, // Inherit existing text theme + ), + ), home: const CalculatorHomePage(), ); } @@ -68,32 +76,41 @@ class _CalculatorHomePageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('方程与表达式计算器')), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - TextField( - controller: _controller, - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: '输入方程或表达式', - hintText: '例如: 2x^2 - 8x + 6 = 0', - ), - onSubmitted: (_) => _solveEquation(), + body: Column( + children: [ + Padding( + padding: const EdgeInsets.only(left: 16.0, right: 16.0, top: 16.0), + child: Row( + spacing: 8, + children: [ + Expanded( + child: TextField( + controller: _controller, + textAlign: TextAlign.center, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: '输入方程或表达式', + floatingLabelAlignment: FloatingLabelAlignment.center, + hintText: '例如: 2x^2 - 8x + 6 = 0', + ), + onSubmitted: (_) => _solveEquation(), + ), + ), + IconButton( + onPressed: _solveEquation, + icon: Icon(Icons.play_arrow), + ), + ], ), - const SizedBox(height: 16), - ElevatedButton(onPressed: _solveEquation, child: const Text('计算')), - const SizedBox(height: 16), - const Divider(), - Expanded( - child: _isLoading - ? const Center(child: CircularProgressIndicator()) - : _result == null - ? const Center(child: Text('请输入方程开始计算')) - : buildResultView(_result!), - ), - ], - ), + ), + Expanded( + child: _isLoading + ? const Center(child: CircularProgressIndicator()) + : _result == null + ? const Center(child: Text('请输入方程开始计算')) + : buildResultView(_result!), + ), + ], ), ); } @@ -101,23 +118,66 @@ class _CalculatorHomePageState extends State { // 构建结果展示视图 Widget buildResultView(CalculationResult result) { return ListView( + padding: EdgeInsets.only( + left: 16, + right: 16, + bottom: MediaQuery.of(context).padding.bottom + 16, + top: 16, + ), children: [ ...result.steps.map( (step) => Card( margin: const EdgeInsets.symmetric(vertical: 8), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: Stack( children: [ - Text( - step.title, - style: Theme.of(context).textTheme.titleMedium, + Padding( + padding: const EdgeInsets.only( + left: 12, + right: 12, + bottom: 4, + top: 16, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + step.title, + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 4), + SelectableText(step.explanation), + Center( + child: LaTexT( + laTeXCode: Text( + step.formula, + style: Theme.of(context).textTheme.titleMedium, + ), + ), + ), + ], + ), + ), + Positioned( + left: -8, + top: -8, + child: Transform.rotate( + angle: pi / -5, + child: Opacity( + opacity: 0.8, + child: Badge( + backgroundColor: Theme.of( + context, + ).colorScheme.primary, + label: Text( + step.stepNumber.toString(), + style: TextStyle(fontSize: 32), + ), + ), + ), + ), ), - const SizedBox(height: 4), - SelectableText(step.explanation), - const SizedBox(height: 8), - Center(child: LaTexT(laTeXCode: Text(step.formula))), ], ), ), @@ -127,17 +187,24 @@ class _CalculatorHomePageState extends State { Card( color: Theme.of(context).colorScheme.primaryContainer, child: Padding( - padding: const EdgeInsets.all(16.0), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Column( children: [ + const SizedBox(height: 16), Text( - "最终答案:", + "最终答案", style: Theme.of(context).textTheme.titleLarge?.copyWith( color: Theme.of(context).colorScheme.onPrimaryContainer, ), ), - const SizedBox(height: 8), - LaTexT(laTeXCode: Text(result.finalAnswer)), + LaTexT( + laTeXCode: Text( + result.finalAnswer, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), + ), + ), ], ), ), diff --git a/lib/models/calculation_step.dart b/lib/models/calculation_step.dart index 17a1ae9..e183579 100644 --- a/lib/models/calculation_step.dart +++ b/lib/models/calculation_step.dart @@ -1,10 +1,12 @@ // 用来描述计算过程中的每一步 class CalculationStep { - final String title; // 这一步的标题,例如:“第一步:整理方程” + final int stepNumber; // 步骤编号,例如:1, 2, 3... + final String title; // 这一步的标题,例如:"整理方程" final String explanation; // 对这一步的具体文字描述 final String formula; // 这一步得到的数学式子 (可以使用 LaTeX 格式) CalculationStep({ + required this.stepNumber, required this.title, required this.explanation, required this.formula, diff --git a/lib/solver_service.dart b/lib/solver.dart similarity index 91% rename from lib/solver_service.dart rename to lib/solver.dart index 37358b6..d8f050d 100644 --- a/lib/solver_service.dart +++ b/lib/solver.dart @@ -56,16 +56,16 @@ class SolverService { final steps = []; steps.add( CalculationStep( - title: '第一步:表达式求值', + stepNumber: 1, + title: '表达式求值', explanation: '这是一个标准的数学表达式,我们将直接计算其结果。', formula: input, ), ); - Parser p = Parser(); + GrammarParser p = GrammarParser(); Expression exp = p.parse(input); - ContextModel cm = ContextModel(); - final result = exp.evaluate(EvaluationType.REAL, cm); + final result = RealEvaluator().evaluate(exp); return CalculationResult(steps: steps, finalAnswer: result.toString()); } @@ -75,6 +75,7 @@ class SolverService { final steps = []; steps.add( CalculationStep( + stepNumber: 0, title: '原方程', explanation: '这是一元一次方程。', formula: '\$\$$input\$\$', @@ -89,7 +90,8 @@ class SolverService { steps.add( CalculationStep( - title: '第一步:移项', + stepNumber: 1, + title: '移项', explanation: '将所有含 x 的项移到等式左边,常数项移到右边。', formula: '\$\$${a}x ${c >= 0 ? '-' : '+'} ${c.abs()}x = $d ${b >= 0 ? '-' : '+'} ${b.abs()}\$\$', @@ -98,7 +100,8 @@ class SolverService { steps.add( CalculationStep( - title: '第二步:合并同类项', + stepNumber: 2, + title: '合并同类项', explanation: '合并等式两边的项。', formula: '\$\$${newA}x = $newD\$\$', ), @@ -114,7 +117,8 @@ class SolverService { final x = newD / newA; steps.add( CalculationStep( - title: '第三步:求解 x', + stepNumber: 3, + title: '求解 x', explanation: '两边同时除以 x 的系数 ($newA)。', formula: '\$\$x = \frac{$newD}{$newA}\$\$', ), @@ -143,7 +147,8 @@ class SolverService { steps.add( CalculationStep( - title: '第一步:整理方程', + stepNumber: 1, + title: '整理方程', explanation: r'将方程整理成标准形式 ax^2+bx+c=0。', formula: '\$\$${a}x^2 ${b >= 0 ? '+' : ''} ${b}x ${c >= 0 ? '+' : ''} $c = 0\$\$', @@ -155,16 +160,18 @@ class SolverService { if (factors != null) { steps.add( CalculationStep( - title: '第二步:因式分解法 (十字相乘)', + stepNumber: 2, + title: '因式分解法 (十字相乘)', explanation: '我们发现可以将方程分解为两个一次因式的乘积。', formula: factors.formula, ), ); steps.add( CalculationStep( - title: '第三步:求解', + stepNumber: 3, + title: '求解', explanation: '分别令每个因式等于 0,解出 x。', - formula: '解得 ${factors.solution}', + formula: factors.solution, ), ); return CalculationResult(steps: steps, finalAnswer: factors.solution); @@ -173,7 +180,8 @@ class SolverService { steps.add( CalculationStep( - title: '第二步:选择解法', + stepNumber: 2, + title: '选择解法', explanation: '无法进行因式分解,我们选择使用求根公式法。', formula: '\$\$\\Delta = b^2 - 4ac\$\$', ), @@ -182,7 +190,8 @@ class SolverService { final delta = b * b - 4 * a * c; steps.add( CalculationStep( - title: '第三步:计算判别式 (Delta)', + stepNumber: 3, + title: '计算判别式 (Delta)', explanation: '\$\$\\Delta = b^2 - 4ac = ($b)^2 - 4 \\cdot ($a) \\cdot ($c) = $delta\$\$', formula: '\$\$\\Delta = $delta\$\$', @@ -194,7 +203,8 @@ class SolverService { final x2 = (-b - sqrt(delta)) / (2 * a); steps.add( CalculationStep( - title: '第四步:应用求根公式', + stepNumber: 4, + title: '应用求根公式', explanation: r'因为 $\Delta > 0$,方程有两个不相等的实数根。公式: $x = \frac{-b \pm \sqrt{\Delta}}{2a}$。', formula: @@ -210,7 +220,8 @@ class SolverService { final x = -b / (2 * a); steps.add( CalculationStep( - title: '第四步:应用求根公式', + stepNumber: 4, + title: '应用求根公式', explanation: r'因为 $\Delta = 0$,方程有两个相等的实数根。', formula: '\$\$x_1 = x_2 = ${x.toStringAsFixed(4)}\$\$', ), @@ -222,7 +233,8 @@ class SolverService { } else { steps.add( CalculationStep( - title: '第四步:判断解', + stepNumber: 4, + title: '判断解', explanation: r'因为 $\Delta < 0$,该方程在实数范围内无解。', formula: '无实数解', ), @@ -245,6 +257,7 @@ class SolverService { steps.add( CalculationStep( + stepNumber: 0, title: '原始方程组', explanation: '这是一个二元一次方程组,我们将使用加减消元法求解。', formula: @@ -272,7 +285,8 @@ ${a2}x ${b2 >= 0 ? '+' : ''} ${b2}y = $c2 & (2) steps.add( CalculationStep( - title: '第一步:消元', + stepNumber: 1, + title: '消元', explanation: '为了消去变量 y,将方程(1)两边乘以 $b2,方程(2)两边乘以 $b1。', formula: ''' @@ -291,17 +305,19 @@ ${newA2}x ${b1 * b2 >= 0 ? '+' : ''} ${b1 * b2}y = $newC2 & (4) steps.add( CalculationStep( - title: '第二步:相减', + stepNumber: 2, + title: '相减', explanation: '将方程(3)减去方程(4),得到一个只含 x 的方程。', formula: - '\$\$($newA1 - $newA2)x = $newC1 - $newC2 \Rightarrow ${xCoeff}x = $constCoeff\$\$', + '\$\$($newA1 - $newA2)x = $newC1 - $newC2 \\Rightarrow ${xCoeff}x = $constCoeff\$\$', ), ); final x = constCoeff / xCoeff; steps.add( CalculationStep( - title: '第三步:解出 x', + stepNumber: 3, + title: '解出 x', explanation: '求解上述方程得到 x 的值。', formula: '\$\$x = $x\$\$', ), @@ -313,7 +329,8 @@ ${newA2}x ${b1 * b2 >= 0 ? '+' : ''} ${b1 * b2}y = $newC2 & (4) final y = yConst / yCoeff; steps.add( CalculationStep( - title: '第四步:回代求解 y', + stepNumber: 4, + title: '回代求解 y', explanation: '将 x = $x 代入原方程(2)中。', formula: ''' @@ -330,14 +347,15 @@ ${b2}y &= ${c2 - a2 * x} ); steps.add( CalculationStep( - title: '第五步:解出 y', + stepNumber: 5, + title: '解出 y', explanation: '求解得到 y 的值。', formula: '\$\$y = $y\$\$', ), ); return CalculationResult( steps: steps, - finalAnswer: '\$\$x = $x, \quad y = $y\$\$', + finalAnswer: '\$\$x = $x, \\quad y = $y\$\$', ); } else { final yCoeff = b1; @@ -345,7 +363,8 @@ ${b2}y &= ${c2 - a2 * x} final y = yConst / yCoeff; steps.add( CalculationStep( - title: '第四步:回代求解 y', + stepNumber: 4, + title: '回代求解 y', explanation: '将 x = $x 代入原方程(1)中。', formula: ''' @@ -362,7 +381,8 @@ ${b1}y &= ${c1 - a1 * x} ); steps.add( CalculationStep( - title: '第五步:解出 y', + stepNumber: 5, + title: '解出 y', explanation: '求解得到 y 的值。', formula: '\$\$y = $y\$\$', ),