tools_page.dart 4.7 KB

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