import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'session_store.dart'; /// 全局设置。目前两项:MCP 端口、E-Ink 模式。 /// /// 存成单独一个 `settings.json`(见 [SessionStore]),不跟会话存档混在一起 —— /// 会话存档每次求值都会重写,设置却是偶尔改一次的东西。 class AppSettings extends ChangeNotifier { AppSettings({SessionStore? store}) : _store = store ?? SessionStore(fileName: 'settings.json'); final SessionStore _store; static const int defaultMcpPort = 8734; static const int minPort = 1024; static const int maxPort = 65535; int _mcpPort = defaultMcpPort; bool _eInk = false; bool _loaded = false; /// MCP 服务器监听的端口。 int get mcpPort => _mcpPort; /// 是否已从存档读过一次(界面据此决定要不要提示"设置已恢复")。 bool get loaded => _loaded; /// E-Ink 模式:所有颜色换成灰阶,动画关掉。 /// /// 给墨水屏/阳光下看屏幕用 —— 彩色在这种屏上会被抖成难看的网点, /// 动画则会留下拖影。 bool get eInk => _eInk; Future load() async { final data = await _store.load(); if (data != null) { final p = (data['mcpPort'] as num?)?.toInt(); if (p != null && p >= minPort && p <= maxPort) _mcpPort = p; _eInk = data['eInk'] == true; } _loaded = true; notifyListeners(); } Future setMcpPort(int port) async { if (port < minPort || port > maxPort || port == _mcpPort) return; _mcpPort = port; notifyListeners(); await _save(); } Future setEInk(bool on) async { if (on == _eInk) return; _eInk = on; notifyListeners(); await _save(); } Future _save() => _store.save({ 'mcpPort': _mcpPort, 'eInk': _eInk, }); } /// 弹出设置面板。控制台顶栏的齿轮按钮走这里。 Future showSettingsPanel(BuildContext context) async { await showModalBottomSheet( context: context, showDragHandle: true, isScrollControlled: true, builder: (_) => const _SettingsSheet(), ); } class _SettingsSheet extends StatefulWidget { const _SettingsSheet(); @override State<_SettingsSheet> createState() => _SettingsSheetState(); } class _SettingsSheetState extends State<_SettingsSheet> { late final AppSettings _settings = context.read(); late final TextEditingController _port = TextEditingController(text: '${_settings.mcpPort}'); String? _portError; @override void dispose() { _port.dispose(); super.dispose(); } void _applyPort() { final v = int.tryParse(_port.text.trim()); if (v == null || v < AppSettings.minPort || v > AppSettings.maxPort) { setState(() => _portError = '端口要在 ${AppSettings.minPort}~${AppSettings.maxPort} 之间'); return; } setState(() => _portError = null); _settings.setMcpPort(v); } @override Widget build(BuildContext context) { final eInk = context.watch().eInk; return SafeArea( child: Padding( padding: EdgeInsets.fromLTRB( 20, 0, 20, 20 + MediaQuery.viewInsetsOf(context).bottom, ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('设置', style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 16), TextField( controller: _port, keyboardType: TextInputType.number, onSubmitted: (_) => _applyPort(), onTapOutside: (_) { FocusScope.of(context).unfocus(); _applyPort(); }, decoration: InputDecoration( labelText: 'MCP 服务器端口', helperText: '默认 ${AppSettings.defaultMcpPort}。服务器正在运行时改端口会重启它。', errorText: _portError, border: const OutlineInputBorder(), isDense: true, ), ), const SizedBox(height: 8), SwitchListTile( contentPadding: EdgeInsets.zero, value: eInk, title: const Text('E-Ink 模式'), subtitle: const Text('所有颜色换成灰阶,动画关闭'), onChanged: (v) => _settings.setEInk(v), ), ], ), ), ); } }