♻️ Split up the function graph mode and solving mode

This commit is contained in:
2025-09-14 14:03:02 +08:00
parent 50857f2d2e
commit 587e243ee3

View File

@@ -21,6 +21,7 @@ class _CalculatorHomePageState extends State<CalculatorHomePage> {
CalculationResult? _result;
bool _isLoading = false;
bool _isFunctionMode = false;
double _zoomFactor = 1.0;
@override
@@ -44,11 +45,12 @@ class _CalculatorHomePageState extends State<CalculatorHomePage> {
/// 生成函数图表的点
List<FlSpot> _generatePlotPoints(String expression, double zoomFactor) {
try {
// 如果是方程,取左边作为函数
String functionExpr = expression;
if (expression.contains('=')) {
functionExpr = expression.split('=')[0].trim();
// 只处理 y=... 格式的函数
String normalized = expression.replaceAll(' ', '');
if (!normalized.toLowerCase().startsWith('y=')) {
return [];
}
String functionExpr = normalized.substring(2);
// 如果表达式不包含 x返回空列表
if (!functionExpr.contains('x') && !functionExpr.contains('X')) {
@@ -159,7 +161,19 @@ class _CalculatorHomePageState extends State<CalculatorHomePage> {
if (_controller.text.isEmpty) {
return;
}
final input = _controller.text.trim();
final normalizedInput = input.replaceAll(' ', '');
if (normalizedInput.toLowerCase().startsWith('y=')) {
setState(() {
_isFunctionMode = true;
_result = null;
});
return;
}
setState(() {
_isFunctionMode = false;
_isLoading = true;
_result = null; // 清除上次结果
});
@@ -197,6 +211,142 @@ class _CalculatorHomePageState extends State<CalculatorHomePage> {
});
}
// 构建函数图像卡片
Widget _buildGraphCard() {
return ListView(
padding: EdgeInsets.only(
left: 16,
right: 16,
bottom: MediaQuery.of(context).padding.bottom + 16,
top: 16,
),
children: [
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(left: 4),
child: Text(
'函数图像',
style: Theme.of(context).textTheme.titleMedium,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: _zoomIn,
icon: Icon(Icons.zoom_in),
tooltip: '放大',
padding: EdgeInsets.zero,
visualDensity: VisualDensity.compact,
),
IconButton(
onPressed: _zoomOut,
icon: Icon(Icons.zoom_out),
tooltip: '缩小',
padding: EdgeInsets.zero,
visualDensity: VisualDensity.compact,
),
],
),
],
),
const SizedBox(height: 24),
SizedBox(
height: 340,
child: Builder(
builder: (context) {
final points = _generatePlotPoints(
_controller.text,
_zoomFactor,
);
final bounds = _calculateChartBounds(points, _zoomFactor);
return LineChart(
LineChartData(
gridData: FlGridData(show: true),
titlesData: FlTitlesData(
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 80,
getTitlesWidget: (value, meta) =>
SideTitleWidget(
axisSide: meta.axisSide,
child: Text(value.toStringAsFixed(2)),
),
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 24,
getTitlesWidget: (value, meta) =>
SideTitleWidget(
axisSide: meta.axisSide,
child: Text(value.toStringAsFixed(2)),
),
),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
borderData: FlBorderData(
show: true,
border: Border.all(
color: Theme.of(context).colorScheme.outline,
),
),
lineTouchData: LineTouchData(
enabled: true,
touchTooltipData: LineTouchTooltipData(
getTooltipItems: (touchedSpots) {
return touchedSpots.map((spot) {
return LineTooltipItem(
'x = ${spot.x.toStringAsFixed(2)}',
const TextStyle(color: Colors.white),
);
}).toList();
},
),
),
lineBarsData: [
LineChartBarData(
spots: points,
isCurved: true,
color: Theme.of(context).colorScheme.primary,
barWidth: 3,
belowBarData: BarAreaData(show: false),
dotData: FlDotData(show: false),
),
],
minX: bounds.minX,
maxX: bounds.maxX,
minY: bounds.minY,
maxY: bounds.maxY,
),
);
},
),
),
],
),
),
),
],
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -240,6 +390,8 @@ class _CalculatorHomePageState extends State<CalculatorHomePage> {
Expanded(
child: _isLoading
? const Center(child: CircularProgressIndicator())
: _isFunctionMode
? _buildGraphCard()
: _result == null
? const Center(child: Text('请输入方程开始计算'))
: buildResultView(_result!),
@@ -354,129 +506,6 @@ class _CalculatorHomePageState extends State<CalculatorHomePage> {
),
),
),
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(left: 4),
child: Text(
'函数图像',
style: Theme.of(context).textTheme.titleMedium,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: _zoomIn,
icon: Icon(Icons.zoom_in),
tooltip: '放大',
padding: EdgeInsets.zero,
visualDensity: VisualDensity.compact,
),
IconButton(
onPressed: _zoomOut,
icon: Icon(Icons.zoom_out),
tooltip: '缩小',
padding: EdgeInsets.zero,
visualDensity: VisualDensity.compact,
),
],
),
],
),
const SizedBox(height: 24),
SizedBox(
height: 340,
child: Builder(
builder: (context) {
final points = _generatePlotPoints(
_controller.text,
_zoomFactor,
);
final bounds = _calculateChartBounds(points, _zoomFactor);
return LineChart(
LineChartData(
gridData: FlGridData(show: true),
titlesData: FlTitlesData(
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
getTitlesWidget: (value, meta) =>
SideTitleWidget(
axisSide: meta.axisSide,
child: Text(value.toStringAsFixed(2)),
),
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
getTitlesWidget: (value, meta) =>
SideTitleWidget(
axisSide: meta.axisSide,
child: Text(value.toStringAsFixed(2)),
),
),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
borderData: FlBorderData(
show: true,
border: Border.all(
color: Theme.of(context).colorScheme.outline,
),
),
lineTouchData: LineTouchData(
enabled: true,
touchTooltipData: LineTouchTooltipData(
getTooltipItems: (touchedSpots) {
return touchedSpots.map((spot) {
return LineTooltipItem(
'x = ${spot.x.toStringAsFixed(2)}',
const TextStyle(color: Colors.white),
);
}).toList();
},
),
),
lineBarsData: [
LineChartBarData(
spots: points,
isCurved: true,
color: Theme.of(context).colorScheme.primary,
barWidth: 3,
belowBarData: BarAreaData(show: false),
dotData: FlDotData(show: false),
),
],
minX: bounds.minX,
maxX: bounds.maxX,
minY: bounds.minY,
maxY: bounds.maxY,
),
);
},
),
),
],
),
),
),
],
);
}