import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'calculator_page.dart'; import 'console_page.dart'; import 'settings.dart'; import 'symja.dart'; import 'tools_page.dart'; void main() => runApp(const CalApp()); class CalApp extends StatelessWidget { const CalApp({super.key}); @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => SymjaSession()), // 设置异步读盘,读完 notifyListeners 一次,界面自己会换上 E-Ink ChangeNotifierProvider(create: (_) => AppSettings()..load()), ], child: const _App(), ); } } class _App extends StatelessWidget { const _App(); @override Widget build(BuildContext context) { final settings = context.watch(); final eInk = settings.eInk; // 近似位数只有一个来源(设置里那个滑动条):读出来直接给会话, // 计算器的"近似"和工具页的"(近似)"就都是这个位数。 context.read().approximateDigits = settings.approxDigits; return MaterialApp( title: '某人的计算器2', debugShowCheckedModeBanner: false, theme: _theme(eInk), themeAnimationDuration: eInk ? Duration.zero : kThemeAnimationDuration, // **始终**套同样两层的 widget,只改参数:切 E-Ink 时若把这两层加进来/ // 拿出去,Navigator 会因为"这一格的 widget 类型变了"被整个重建, // 表现是"一开 E-Ink 就被踢回首页、弹着的面板也没了"。 builder: (context, child) => MediaQuery( data: MediaQuery.of(context) .copyWith(disableAnimations: eInk), child: ColorFiltered( // 关掉时用单位矩阵,等于没过滤镜(`ColorFiltered` 本身还在, // 但任何颜色乘上单位矩阵都不变) colorFilter: eInk ? _Grayscale.filter : _Grayscale.identity, child: child!, ), ), home: const ShellPage(), ); } /// E-Ink 模式下额外关掉动画:路由切换和点击水波纹是屏幕上最明显的两处动画。 ThemeData _theme(bool eInk) { final base = ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true); if (!eInk) return base; // 白底黑字。只靠灰阶滤镜不够 —— 配色本来就有浅灰底、浅灰卡片, // 灰阶之后还是灰,看着发闷(而且键盘的浅色按键会跟浅色底糊在一起)。 // 这里把整个配色压成"白底、黑字、黑线",再让下面的控件自己描边。 final cs = base.colorScheme.copyWith( primary: Colors.black, onPrimary: Colors.white, primaryContainer: Colors.white, onPrimaryContainer: Colors.black, secondary: Colors.black, onSecondary: Colors.white, secondaryContainer: const Color(0xFFE0E0E0), onSecondaryContainer: Colors.black, surface: Colors.white, onSurface: Colors.black, surfaceContainerLowest: Colors.white, surfaceContainerLow: Colors.white, surfaceContainer: Colors.white, surfaceContainerHigh: Colors.white, surfaceContainerHighest: Colors.white, onSurfaceVariant: Colors.black, outline: Colors.black, outlineVariant: const Color(0xFF9E9E9E), inverseSurface: Colors.black, onInverseSurface: Colors.white, error: Colors.black, onError: Colors.white, errorContainer: Colors.white, onErrorContainer: Colors.black, shadow: Colors.black, scrim: Colors.black, ); return base.copyWith( colorScheme: cs, scaffoldBackgroundColor: Colors.white, canvasColor: Colors.white, appBarTheme: const AppBarTheme( backgroundColor: Colors.white, foregroundColor: Colors.black, elevation: 0, scrolledUnderElevation: 0, ), navigationBarTheme: const NavigationBarThemeData( backgroundColor: Colors.white, indicatorColor: Color(0xFFE0E0E0), ), // 卡片全靠描边分辨,不靠阴影(墨水屏上阴影就是一团脏灰) cardTheme: CardThemeData( color: Colors.white, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: const BorderSide(color: Color(0xFF9E9E9E)), ), ), dividerTheme: const DividerThemeData(color: Color(0xFF9E9E9E)), splashFactory: NoSplash.splashFactory, pageTransitionsTheme: const PageTransitionsTheme( builders: { TargetPlatform.android: _NoTransition(), TargetPlatform.iOS: _NoTransition(), TargetPlatform.fuchsia: _NoTransition(), TargetPlatform.linux: _NoTransition(), TargetPlatform.macOS: _NoTransition(), TargetPlatform.windows: _NoTransition(), }, ), ); } } class _NoTransition extends PageTransitionsBuilder { const _NoTransition(); @override Widget buildTransitions( PageRoute route, BuildContext context, Animation animation, Animation secondaryAnimation, Widget child, ) => child; } /// 灰阶滤镜。 /// /// 用**一个**颜色滤镜覆盖全局,而不是去给每个配色/每张图表换一套灰阶调色板 —— /// 那些颜色散在主题、工具卡片、绘图组件、Symja 的 PNG 里,逐个改必然漏。 /// E-Ink 关掉时套的是单位矩阵(同样的 widget、同样的开销,只是颜色不变), /// 这样切换不会动到 widget 树的形状。 class _Grayscale { const _Grayscale._(); /// 亮度权重就是通常的 Rec.709 公式。 static const ColorFilter filter = ColorFilter.matrix([ 0.2126, 0.7152, 0.0722, 0, 0, // 0.2126, 0.7152, 0.0722, 0, 0, // 0.2126, 0.7152, 0.0722, 0, 0, // 0, 0, 0, 1, 0, // ]); static const ColorFilter identity = ColorFilter.matrix([ 1, 0, 0, 0, 0, // 0, 1, 0, 0, 0, // 0, 0, 1, 0, 0, // 0, 0, 0, 1, 0, // ]); } /// 三个界面(Q16 的「双界面」+ 插件页): /// 计算器 —— 默认入口,卡西欧式按键 /// 工具 —— 16 个插件的网格入口 /// 控制台 —— 表达式 REPL /// /// 用 IndexedStack 保住各页状态:切回来时控制台的历史、计算器正在输入的 /// 表达式都还在。 class ShellPage extends StatefulWidget { const ShellPage({super.key}); @override State createState() => _ShellPageState(); } class _ShellPageState extends State { int _index = 0; @override Widget build(BuildContext context) { return Scaffold( body: IndexedStack( index: _index, children: const [ CalculatorPage(), ToolsPage(), ConsolePage(), ], ), bottomNavigationBar: NavigationBar( selectedIndex: _index, onDestinationSelected: (i) => setState(() => _index = i), destinations: const [ NavigationDestination( icon: Icon(Icons.calculate_outlined), selectedIcon: Icon(Icons.calculate), label: '计算器', ), NavigationDestination( icon: Icon(Icons.widgets_outlined), selectedIcon: Icon(Icons.widgets), label: '工具', ), NavigationDestination( icon: Icon(Icons.terminal_outlined), selectedIcon: Icon(Icons.terminal), label: '控制台', ), ], ), ); } }