main.dart 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import 'package:flutter/material.dart';
  2. import 'package:provider/provider.dart';
  3. import 'calculator_page.dart';
  4. import 'console_page.dart';
  5. import 'settings.dart';
  6. import 'symja.dart';
  7. import 'tools_page.dart';
  8. void main() => runApp(const CalApp());
  9. class CalApp extends StatelessWidget {
  10. const CalApp({super.key});
  11. @override
  12. Widget build(BuildContext context) {
  13. return MultiProvider(
  14. providers: [
  15. ChangeNotifierProvider(create: (_) => SymjaSession()),
  16. // 设置异步读盘,读完 notifyListeners 一次,界面自己会换上 E-Ink
  17. ChangeNotifierProvider(create: (_) => AppSettings()..load()),
  18. ],
  19. child: const _App(),
  20. );
  21. }
  22. }
  23. class _App extends StatelessWidget {
  24. const _App();
  25. @override
  26. Widget build(BuildContext context) {
  27. final eInk = context.watch<AppSettings>().eInk;
  28. return MaterialApp(
  29. title: '某人的计算器2',
  30. debugShowCheckedModeBanner: false,
  31. theme: _theme(eInk),
  32. themeAnimationDuration: eInk ? Duration.zero : kThemeAnimationDuration,
  33. // **始终**套同样两层的 widget,只改参数:切 E-Ink 时若把这两层加进来/
  34. // 拿出去,Navigator 会因为"这一格的 widget 类型变了"被整个重建,
  35. // 表现是"一开 E-Ink 就被踢回首页、弹着的面板也没了"。
  36. builder: (context, child) => MediaQuery(
  37. data: MediaQuery.of(context)
  38. .copyWith(disableAnimations: eInk),
  39. child: ColorFiltered(
  40. // 关掉时用单位矩阵,等于没过滤镜(`ColorFiltered` 本身还在,
  41. // 但任何颜色乘上单位矩阵都不变)
  42. colorFilter: eInk ? _Grayscale.filter : _Grayscale.identity,
  43. child: child!,
  44. ),
  45. ),
  46. home: const ShellPage(),
  47. );
  48. }
  49. /// E-Ink 模式下额外关掉动画:路由切换和点击水波纹是屏幕上最明显的两处动画。
  50. ThemeData _theme(bool eInk) {
  51. final base = ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true);
  52. if (!eInk) return base;
  53. // 白底黑字。只靠灰阶滤镜不够 —— 配色本来就有浅灰底、浅灰卡片,
  54. // 灰阶之后还是灰,看着发闷(而且键盘的浅色按键会跟浅色底糊在一起)。
  55. // 这里把整个配色压成"白底、黑字、黑线",再让下面的控件自己描边。
  56. final cs = base.colorScheme.copyWith(
  57. primary: Colors.black,
  58. onPrimary: Colors.white,
  59. primaryContainer: Colors.white,
  60. onPrimaryContainer: Colors.black,
  61. secondary: Colors.black,
  62. onSecondary: Colors.white,
  63. secondaryContainer: const Color(0xFFE0E0E0),
  64. onSecondaryContainer: Colors.black,
  65. surface: Colors.white,
  66. onSurface: Colors.black,
  67. surfaceContainerLowest: Colors.white,
  68. surfaceContainerLow: Colors.white,
  69. surfaceContainer: Colors.white,
  70. surfaceContainerHigh: Colors.white,
  71. surfaceContainerHighest: Colors.white,
  72. onSurfaceVariant: Colors.black,
  73. outline: Colors.black,
  74. outlineVariant: const Color(0xFF9E9E9E),
  75. inverseSurface: Colors.black,
  76. onInverseSurface: Colors.white,
  77. error: Colors.black,
  78. onError: Colors.white,
  79. errorContainer: Colors.white,
  80. onErrorContainer: Colors.black,
  81. shadow: Colors.black,
  82. scrim: Colors.black,
  83. );
  84. return base.copyWith(
  85. colorScheme: cs,
  86. scaffoldBackgroundColor: Colors.white,
  87. canvasColor: Colors.white,
  88. appBarTheme: const AppBarTheme(
  89. backgroundColor: Colors.white,
  90. foregroundColor: Colors.black,
  91. elevation: 0,
  92. scrolledUnderElevation: 0,
  93. ),
  94. navigationBarTheme: const NavigationBarThemeData(
  95. backgroundColor: Colors.white,
  96. indicatorColor: Color(0xFFE0E0E0),
  97. ),
  98. // 卡片全靠描边分辨,不靠阴影(墨水屏上阴影就是一团脏灰)
  99. cardTheme: CardThemeData(
  100. color: Colors.white,
  101. elevation: 0,
  102. shape: RoundedRectangleBorder(
  103. borderRadius: BorderRadius.circular(12),
  104. side: const BorderSide(color: Color(0xFF9E9E9E)),
  105. ),
  106. ),
  107. dividerTheme: const DividerThemeData(color: Color(0xFF9E9E9E)),
  108. splashFactory: NoSplash.splashFactory,
  109. pageTransitionsTheme: const PageTransitionsTheme(
  110. builders: <TargetPlatform, PageTransitionsBuilder>{
  111. TargetPlatform.android: _NoTransition(),
  112. TargetPlatform.iOS: _NoTransition(),
  113. TargetPlatform.fuchsia: _NoTransition(),
  114. TargetPlatform.linux: _NoTransition(),
  115. TargetPlatform.macOS: _NoTransition(),
  116. TargetPlatform.windows: _NoTransition(),
  117. },
  118. ),
  119. );
  120. }
  121. }
  122. class _NoTransition extends PageTransitionsBuilder {
  123. const _NoTransition();
  124. @override
  125. Widget buildTransitions<T>(
  126. PageRoute<T> route,
  127. BuildContext context,
  128. Animation<double> animation,
  129. Animation<double> secondaryAnimation,
  130. Widget child,
  131. ) =>
  132. child;
  133. }
  134. /// 灰阶滤镜。
  135. ///
  136. /// 用**一个**颜色滤镜覆盖全局,而不是去给每个配色/每张图表换一套灰阶调色板 ——
  137. /// 那些颜色散在主题、工具卡片、绘图组件、Symja 的 PNG 里,逐个改必然漏。
  138. /// E-Ink 关掉时套的是单位矩阵(同样的 widget、同样的开销,只是颜色不变),
  139. /// 这样切换不会动到 widget 树的形状。
  140. class _Grayscale {
  141. const _Grayscale._();
  142. /// 亮度权重就是通常的 Rec.709 公式。
  143. static const ColorFilter filter = ColorFilter.matrix(<double>[
  144. 0.2126, 0.7152, 0.0722, 0, 0, //
  145. 0.2126, 0.7152, 0.0722, 0, 0, //
  146. 0.2126, 0.7152, 0.0722, 0, 0, //
  147. 0, 0, 0, 1, 0, //
  148. ]);
  149. static const ColorFilter identity = ColorFilter.matrix(<double>[
  150. 1, 0, 0, 0, 0, //
  151. 0, 1, 0, 0, 0, //
  152. 0, 0, 1, 0, 0, //
  153. 0, 0, 0, 1, 0, //
  154. ]);
  155. }
  156. /// 三个界面(Q16 的「双界面」+ 插件页):
  157. /// 计算器 —— 默认入口,卡西欧式按键
  158. /// 工具 —— 16 个插件的网格入口
  159. /// 控制台 —— 表达式 REPL
  160. ///
  161. /// 用 IndexedStack 保住各页状态:切回来时控制台的历史、计算器正在输入的
  162. /// 表达式都还在。
  163. class ShellPage extends StatefulWidget {
  164. const ShellPage({super.key});
  165. @override
  166. State<ShellPage> createState() => _ShellPageState();
  167. }
  168. class _ShellPageState extends State<ShellPage> {
  169. int _index = 0;
  170. @override
  171. Widget build(BuildContext context) {
  172. return Scaffold(
  173. body: IndexedStack(
  174. index: _index,
  175. children: const [
  176. CalculatorPage(),
  177. ToolsPage(),
  178. ConsolePage(),
  179. ],
  180. ),
  181. bottomNavigationBar: NavigationBar(
  182. selectedIndex: _index,
  183. onDestinationSelected: (i) => setState(() => _index = i),
  184. destinations: const [
  185. NavigationDestination(
  186. icon: Icon(Icons.calculate_outlined),
  187. selectedIcon: Icon(Icons.calculate),
  188. label: '计算器',
  189. ),
  190. NavigationDestination(
  191. icon: Icon(Icons.widgets_outlined),
  192. selectedIcon: Icon(Icons.widgets),
  193. label: '工具',
  194. ),
  195. NavigationDestination(
  196. icon: Icon(Icons.terminal_outlined),
  197. selectedIcon: Icon(Icons.terminal),
  198. label: '控制台',
  199. ),
  200. ],
  201. ),
  202. );
  203. }
  204. }