main.dart 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  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. return base.copyWith(
  54. splashFactory: NoSplash.splashFactory,
  55. pageTransitionsTheme: const PageTransitionsTheme(
  56. builders: <TargetPlatform, PageTransitionsBuilder>{
  57. TargetPlatform.android: _NoTransition(),
  58. TargetPlatform.iOS: _NoTransition(),
  59. TargetPlatform.fuchsia: _NoTransition(),
  60. TargetPlatform.linux: _NoTransition(),
  61. TargetPlatform.macOS: _NoTransition(),
  62. TargetPlatform.windows: _NoTransition(),
  63. },
  64. ),
  65. );
  66. }
  67. }
  68. class _NoTransition extends PageTransitionsBuilder {
  69. const _NoTransition();
  70. @override
  71. Widget buildTransitions<T>(
  72. PageRoute<T> route,
  73. BuildContext context,
  74. Animation<double> animation,
  75. Animation<double> secondaryAnimation,
  76. Widget child,
  77. ) =>
  78. child;
  79. }
  80. /// 灰阶滤镜。
  81. ///
  82. /// 用**一个**颜色滤镜覆盖全局,而不是去给每个配色/每张图表换一套灰阶调色板 ——
  83. /// 那些颜色散在主题、工具卡片、绘图组件、Symja 的 PNG 里,逐个改必然漏。
  84. /// E-Ink 关掉时套的是单位矩阵(同样的 widget、同样的开销,只是颜色不变),
  85. /// 这样切换不会动到 widget 树的形状。
  86. class _Grayscale {
  87. const _Grayscale._();
  88. /// 亮度权重就是通常的 Rec.709 公式。
  89. static const ColorFilter filter = ColorFilter.matrix(<double>[
  90. 0.2126, 0.7152, 0.0722, 0, 0, //
  91. 0.2126, 0.7152, 0.0722, 0, 0, //
  92. 0.2126, 0.7152, 0.0722, 0, 0, //
  93. 0, 0, 0, 1, 0, //
  94. ]);
  95. static const ColorFilter identity = ColorFilter.matrix(<double>[
  96. 1, 0, 0, 0, 0, //
  97. 0, 1, 0, 0, 0, //
  98. 0, 0, 1, 0, 0, //
  99. 0, 0, 0, 1, 0, //
  100. ]);
  101. }
  102. /// 三个界面(Q16 的「双界面」+ 插件页):
  103. /// 计算器 —— 默认入口,卡西欧式按键
  104. /// 工具 —— 16 个插件的网格入口
  105. /// 控制台 —— 表达式 REPL
  106. ///
  107. /// 用 IndexedStack 保住各页状态:切回来时控制台的历史、计算器正在输入的
  108. /// 表达式都还在。
  109. class ShellPage extends StatefulWidget {
  110. const ShellPage({super.key});
  111. @override
  112. State<ShellPage> createState() => _ShellPageState();
  113. }
  114. class _ShellPageState extends State<ShellPage> {
  115. int _index = 0;
  116. @override
  117. Widget build(BuildContext context) {
  118. return Scaffold(
  119. body: IndexedStack(
  120. index: _index,
  121. children: const [
  122. CalculatorPage(),
  123. ToolsPage(),
  124. ConsolePage(),
  125. ],
  126. ),
  127. bottomNavigationBar: NavigationBar(
  128. selectedIndex: _index,
  129. onDestinationSelected: (i) => setState(() => _index = i),
  130. destinations: const [
  131. NavigationDestination(
  132. icon: Icon(Icons.calculate_outlined),
  133. selectedIcon: Icon(Icons.calculate),
  134. label: '计算器',
  135. ),
  136. NavigationDestination(
  137. icon: Icon(Icons.widgets_outlined),
  138. selectedIcon: Icon(Icons.widgets),
  139. label: '工具',
  140. ),
  141. NavigationDestination(
  142. icon: Icon(Icons.terminal_outlined),
  143. selectedIcon: Icon(Icons.terminal),
  144. label: '控制台',
  145. ),
  146. ],
  147. ),
  148. );
  149. }
  150. }