tools_page.dart 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import 'package:flutter/material.dart';
  2. import 'plugins.dart';
  3. /// 工具网格:可搜索的插件入口。
  4. ///
  5. /// 工具页面的实现全部在 lib/tools/ 下(从 mathtool2 搬过来的那批)。
  6. /// **计算一律交给 Symja**,走 lib/tools/tool_kit.dart 那套外壳;Dart 侧只有
  7. /// 绘图采样和排版。原来那批纯 Dart 的算法(rational / math_expressions)
  8. /// 已经全部撤掉 —— 同一个算式不该有 Dart 和 Symja 两份实现。
  9. class ToolsPage extends StatefulWidget {
  10. const ToolsPage({super.key});
  11. @override
  12. State<ToolsPage> createState() => _ToolsPageState();
  13. }
  14. class _ToolsPageState extends State<ToolsPage> {
  15. final _search = TextEditingController();
  16. String _q = '';
  17. static const _palette = <Color>[
  18. Color(0xFF5C6BC0),
  19. Color(0xFF26A69A),
  20. Color(0xFFEF5350),
  21. Color(0xFFAB47BC),
  22. Color(0xFF42A5F5),
  23. Color(0xFFFFA726),
  24. Color(0xFF66BB6A),
  25. Color(0xFF8D6E63),
  26. ];
  27. @override
  28. void dispose() {
  29. _search.dispose();
  30. super.dispose();
  31. }
  32. @override
  33. Widget build(BuildContext context) {
  34. final items = kTools.where((t) => t.matches(_q)).toList();
  35. // 这一页是唯一没有自己的 Scaffold/AppBar 的 tab(另外两个都有),
  36. // 所以得自己让开状态栏 —— 否则搜索框会顶到状态栏上,和时钟叠在一起。
  37. // bottom 不用让:下面紧挨着的就是 Shell 的 NavigationBar,它已经把
  38. // 底部安全区吃掉了。
  39. return SafeArea(
  40. bottom: false,
  41. child: Column(
  42. children: [
  43. Padding(
  44. padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
  45. child: TextField(
  46. controller: _search,
  47. decoration: InputDecoration(
  48. isDense: true,
  49. hintText: '搜索工具(支持拼音首字母)',
  50. prefixIcon: const Icon(Icons.search),
  51. suffixIcon: _q.isEmpty
  52. ? null
  53. : IconButton(
  54. icon: const Icon(Icons.clear),
  55. onPressed: () {
  56. _search.clear();
  57. setState(() => _q = '');
  58. },
  59. ),
  60. border: const OutlineInputBorder(),
  61. ),
  62. onChanged: (v) => setState(() => _q = v),
  63. ),
  64. ),
  65. Expanded(
  66. child: items.isEmpty
  67. ? const Center(child: Text('没有匹配的工具'))
  68. : GridView.builder(
  69. padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
  70. gridDelegate:
  71. const SliverGridDelegateWithFixedCrossAxisCount(
  72. crossAxisCount: 2,
  73. mainAxisSpacing: 12,
  74. crossAxisSpacing: 12,
  75. childAspectRatio: 1.5,
  76. ),
  77. itemCount: items.length,
  78. itemBuilder: (_, i) {
  79. final t = items[i];
  80. final color =
  81. _palette[kTools.indexOf(t) % _palette.length];
  82. return _ToolCard(
  83. plugin: t,
  84. color: color,
  85. onTap: () => Navigator.of(context).push(
  86. MaterialPageRoute<void>(
  87. builder: (_) => t.build(),
  88. settings: RouteSettings(name: t.name),
  89. ),
  90. ),
  91. );
  92. },
  93. ),
  94. ),
  95. ],
  96. ),
  97. );
  98. }
  99. }
  100. class _ToolCard extends StatelessWidget {
  101. const _ToolCard({
  102. required this.plugin,
  103. required this.color,
  104. required this.onTap,
  105. });
  106. final ToolPlugin plugin;
  107. final Color color;
  108. final VoidCallback onTap;
  109. @override
  110. Widget build(BuildContext context) {
  111. return Material(
  112. color: Colors.transparent,
  113. child: InkWell(
  114. onTap: onTap,
  115. borderRadius: BorderRadius.circular(16),
  116. child: Ink(
  117. decoration: BoxDecoration(
  118. borderRadius: BorderRadius.circular(16),
  119. gradient: LinearGradient(
  120. begin: Alignment.topLeft,
  121. end: Alignment.bottomRight,
  122. colors: [color, Color.lerp(color, Colors.black, 0.22)!],
  123. ),
  124. ),
  125. padding: const EdgeInsets.all(14),
  126. child: Column(
  127. crossAxisAlignment: CrossAxisAlignment.start,
  128. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  129. children: [
  130. Icon(plugin.icon, color: Colors.white, size: 26),
  131. Text(
  132. plugin.name,
  133. style: const TextStyle(
  134. color: Colors.white,
  135. fontSize: 15,
  136. fontWeight: FontWeight.w600,
  137. ),
  138. ),
  139. ],
  140. ),
  141. ),
  142. ),
  143. );
  144. }
  145. }