tools_page.dart 5.4 KB

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