feat: 初版终端设置支持

This commit is contained in:
chaoszhu
2026-06-13 16:46:55 +08:00
parent 7c535f8be5
commit 12db4a72cf
7 changed files with 676 additions and 28 deletions
+21
View File
@@ -64,4 +64,25 @@ class AppStorage {
String get editorTheme => _prefs.getString(_keyEditorTheme) ?? 'dark';
Future<void> setEditorTheme(String value) =>
_prefs.setString(_keyEditorTheme, value);
// ── Terminal settings ──
static const _keyTermFontSize = 'terminal.fontSize';
static const _keyTermFontFamily = 'terminal.fontFamily';
static const _keyTermTheme = 'terminal.themePreset';
double get terminalFontSize =>
_prefs.getDouble(_keyTermFontSize) ?? 12.0;
Future<void> setTerminalFontSize(double v) =>
_prefs.setDouble(_keyTermFontSize, v);
String get terminalFontFamily =>
_prefs.getString(_keyTermFontFamily) ?? 'monospace';
Future<void> setTerminalFontFamily(String v) =>
_prefs.setString(_keyTermFontFamily, v);
String get terminalThemePreset =>
_prefs.getString(_keyTermTheme) ?? 'warm';
Future<void> setTerminalThemePreset(String v) =>
_prefs.setString(_keyTermTheme, v);
}
@@ -0,0 +1,308 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:xterm/xterm.dart';
import '../../core/ui/palette.dart';
import '../../l10n/app_localizations.dart';
import '../../state/terminal_settings_notifier.dart';
import 'terminal_theme_presets.dart';
class TerminalSettingsPage extends ConsumerWidget {
const TerminalSettingsPage({super.key});
static const _fonts = ['monospace', 'Courier', 'Menlo', 'SF Mono', 'Consolas'];
@override
Widget build(BuildContext context, WidgetRef ref) {
final settings = ref.watch(terminalSettingsProvider);
final notifier = ref.read(terminalSettingsProvider.notifier);
final l = AppLocalizations.of(context);
return Scaffold(
backgroundColor: AppPalette.canvas,
appBar: AppBar(
title: Text(l.tr('terminal.settings')),
backgroundColor: AppPalette.canvas,
foregroundColor: AppPalette.text,
elevation: 0,
),
body: ListView(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 32),
children: [
_SectionTitle(l.tr('terminal.settings.themePreset')),
_ThemePresetGrid(
selectedId: settings.themePreset,
onSelected: notifier.setThemePreset,
),
const SizedBox(height: 24),
_SectionTitle(l.tr('terminal.settings.fontSize')),
_FontSizeSlider(
value: settings.fontSize,
onChanged: notifier.setFontSize,
),
const SizedBox(height: 24),
_SectionTitle(l.tr('terminal.settings.fontFamily')),
_FontFamilyPicker(
selected: settings.fontFamily,
onSelected: notifier.setFontFamily,
),
],
),
);
}
}
class _SectionTitle extends StatelessWidget {
const _SectionTitle(this.title);
final String title;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Text(
title,
style: const TextStyle(
color: AppPalette.primary,
fontSize: 14,
fontWeight: FontWeight.w700,
),
),
);
}
}
class _ThemePresetGrid extends StatelessWidget {
const _ThemePresetGrid({
required this.selectedId,
required this.onSelected,
});
final String selectedId;
final Future<void> Function(String) onSelected;
@override
Widget build(BuildContext context) {
final l = AppLocalizations.of(context);
return GridView.count(
crossAxisCount: 4,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 0.85,
children: [
for (final preset in terminalThemePresets)
_ThemePresetCard(
label: l.tr('terminal.settings.preset.${preset.id}'),
theme: preset.theme,
selected: preset.id == selectedId,
onTap: () => onSelected(preset.id),
),
],
);
}
}
class _ThemePresetCard extends StatelessWidget {
const _ThemePresetCard({
required this.label,
required this.theme,
required this.selected,
required this.onTap,
});
final String label;
final TerminalTheme theme;
final bool selected;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: selected ? AppPalette.accent : AppPalette.border,
width: selected ? 2 : 1,
),
),
child: Column(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
color: theme.background,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(9),
),
),
padding: const EdgeInsets.all(6),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
_colorRow([
theme.foreground,
theme.red,
theme.green,
theme.yellow,
]),
const SizedBox(height: 3),
_colorRow([
theme.blue,
theme.magenta,
theme.cyan,
theme.cursor,
]),
],
),
),
),
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 5),
decoration: BoxDecoration(
color: selected
? AppPalette.accent.withValues(alpha: 0.12)
: AppPalette.card,
borderRadius: const BorderRadius.vertical(
bottom: Radius.circular(9),
),
),
child: Text(
label,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 11,
fontWeight: selected ? FontWeight.w700 : FontWeight.w500,
color: selected ? AppPalette.accent : AppPalette.text,
),
),
),
],
),
),
);
}
static Widget _colorRow(List<Color> colors) {
return Row(
children: [
for (final c in colors) ...[
Container(
width: 10,
height: 10,
decoration: BoxDecoration(
color: c,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 3),
],
],
);
}
}
class _FontSizeSlider extends StatelessWidget {
const _FontSizeSlider({
required this.value,
required this.onChanged,
});
final double value;
final Future<void> Function(double) onChanged;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.fromLTRB(14, 8, 14, 8),
decoration: BoxDecoration(
color: AppPalette.card,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppPalette.border),
),
child: Row(
children: [
Text(
'${value.round()}',
style: const TextStyle(
color: AppPalette.text,
fontSize: 18,
fontWeight: FontWeight.w700,
fontFamily: 'monospace',
),
),
const SizedBox(width: 12),
Expanded(
child: SliderTheme(
data: SliderThemeData(
activeTrackColor: AppPalette.accent,
inactiveTrackColor: AppPalette.border,
thumbColor: AppPalette.accent,
overlayColor: AppPalette.accent.withValues(alpha: 0.15),
),
child: Slider(
value: value,
min: 10,
max: 24,
divisions: 14,
onChanged: (v) => onChanged(v),
),
),
),
],
),
);
}
}
class _FontFamilyPicker extends StatelessWidget {
const _FontFamilyPicker({
required this.selected,
required this.onSelected,
});
final String selected;
final Future<void> Function(String) onSelected;
@override
Widget build(BuildContext context) {
return Wrap(
spacing: 8,
runSpacing: 8,
children: [
for (final font in TerminalSettingsPage._fonts)
GestureDetector(
onTap: () => onSelected(font),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: font == selected ? AppPalette.primary : AppPalette.card,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: font == selected
? AppPalette.primary
: AppPalette.border,
),
),
child: Text(
font,
style: TextStyle(
fontFamily: font,
fontSize: 13,
fontWeight: FontWeight.w600,
color: font == selected
? AppPalette.fontOnPrimary
: AppPalette.text,
),
),
),
),
],
);
}
}
@@ -11,35 +11,11 @@ import 'package:xterm/xterm.dart';
import '../../core/ui/palette.dart';
import '../../l10n/app_localizations.dart';
import '../../state/terminal_providers.dart';
import '../../state/terminal_settings_notifier.dart';
import 'terminal_bottom_menu.dart';
import 'terminal_session.dart';
import 'terminal_session_manager.dart';
const _kWarmTerminalTheme = TerminalTheme(
cursor: Color(0xFFE5B33A),
selection: Color(0x55E5B33A),
foreground: Color(0xFF2A2418),
background: Color(0xFFF7EFE0),
black: Color(0xFF2A2418),
red: Color(0xFFB9473D),
green: Color(0xFF5A8E3A),
yellow: Color(0xFFD4940A),
blue: Color(0xFF2472C8),
magenta: Color(0xFFBC3FBC),
cyan: Color(0xFF11A8CD),
white: Color(0xFFF7EFE0),
brightBlack: Color(0xFF6B5E3F),
brightRed: Color(0xFFD4564A),
brightGreen: Color(0xFF6EAF48),
brightYellow: Color(0xFFE5B33A),
brightBlue: Color(0xFF3B8EEA),
brightMagenta: Color(0xFFD670D6),
brightCyan: Color(0xFF29B8DB),
brightWhite: Color(0xFFFBF5E6),
searchHitBackground: Color(0xAAE5B33A),
searchHitBackgroundCurrent: Color(0xDD5A8E3A),
searchHitForeground: Color(0xFF2A2418),
);
import 'terminal_settings_page.dart';
class TerminalShellPage extends ConsumerStatefulWidget {
const TerminalShellPage({super.key});
@@ -530,6 +506,7 @@ class _TerminalShellPageState extends ConsumerState<TerminalShellPage> {
@override
Widget build(BuildContext context) {
final manager = ref.watch(terminalSessionManagerProvider);
final termSettings = ref.watch(terminalSettingsProvider);
final bottomInset = MediaQuery.viewInsetsOf(context).bottom;
if (_terminalFocusNode.hasFocus && bottomInset > _lastKeyboardHeight) {
_lastKeyboardHeight = bottomInset;
@@ -569,10 +546,17 @@ class _TerminalShellPageState extends ConsumerState<TerminalShellPage> {
_TerminalTopBar(
active: active,
onClose: _closeActive,
onSettings: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const TerminalSettingsPage(),
),
);
},
),
Expanded(
child: ColoredBox(
color: AppPalette.canvas,
color: termSettings.terminalTheme.background,
child: sessions.isEmpty
? Center(
child: Text(
@@ -597,7 +581,8 @@ class _TerminalShellPageState extends ConsumerState<TerminalShellPage> {
controller: session.viewController,
scrollController:
session.scrollController,
theme: _kWarmTerminalTheme,
theme: termSettings.terminalTheme,
textStyle: termSettings.terminalStyle,
focusNode: session.id == active?.id
? _terminalFocusNode
: null,
@@ -742,10 +727,12 @@ class _TerminalTopBar extends StatelessWidget {
const _TerminalTopBar({
required this.active,
required this.onClose,
required this.onSettings,
});
final TerminalSession? active;
final VoidCallback onClose;
final VoidCallback onSettings;
@override
Widget build(BuildContext context) {
@@ -801,6 +788,11 @@ class _TerminalTopBar extends StatelessWidget {
// : null,
// ),
// ),
IconButton(
tooltip: l.tr('terminal.settings'),
onPressed: onSettings,
icon: const Icon(Icons.settings_outlined),
),
IconButton(
tooltip: l.tr('terminal.closeTerminal'),
onPressed: activeSession == null ? null : onClose,
@@ -0,0 +1,234 @@
import 'package:flutter/widgets.dart';
import 'package:xterm/xterm.dart';
class TerminalThemePreset {
const TerminalThemePreset({required this.id, required this.theme});
final String id;
final TerminalTheme theme;
}
const _warmTheme = TerminalTheme(
cursor: Color(0xFFE5B33A),
selection: Color(0x55E5B33A),
foreground: Color(0xFF2A2418),
background: Color(0xFFF7EFE0),
black: Color(0xFF2A2418),
red: Color(0xFFB9473D),
green: Color(0xFF5A8E3A),
yellow: Color(0xFFD4940A),
blue: Color(0xFF2472C8),
magenta: Color(0xFFBC3FBC),
cyan: Color(0xFF11A8CD),
white: Color(0xFFF7EFE0),
brightBlack: Color(0xFF6B5E3F),
brightRed: Color(0xFFD4564A),
brightGreen: Color(0xFF6EAF48),
brightYellow: Color(0xFFE5B33A),
brightBlue: Color(0xFF3B8EEA),
brightMagenta: Color(0xFFD670D6),
brightCyan: Color(0xFF29B8DB),
brightWhite: Color(0xFFFBF5E6),
searchHitBackground: Color(0xAAE5B33A),
searchHitBackgroundCurrent: Color(0xDD5A8E3A),
searchHitForeground: Color(0xFF2A2418),
);
const _darkTheme = TerminalTheme(
cursor: Color(0xFFAEAFAD),
selection: Color(0xAAAEAFAD),
foreground: Color(0xFFCCCCCC),
background: Color(0xFF1E1E1E),
black: Color(0xFF000000),
red: Color(0xFFCD3131),
green: Color(0xFF0DBC79),
yellow: Color(0xFFE5E510),
blue: Color(0xFF2472C8),
magenta: Color(0xFFBC3FBC),
cyan: Color(0xFF11A8CD),
white: Color(0xFFE5E5E5),
brightBlack: Color(0xFF666666),
brightRed: Color(0xFFF14C4C),
brightGreen: Color(0xFF23D18B),
brightYellow: Color(0xFFF5F543),
brightBlue: Color(0xFF3B8EEA),
brightMagenta: Color(0xFFD670D6),
brightCyan: Color(0xFF29B8DB),
brightWhite: Color(0xFFFFFFFF),
searchHitBackground: Color(0xFFFFFF2B),
searchHitBackgroundCurrent: Color(0xFF31FF26),
searchHitForeground: Color(0xFF000000),
);
const _lightTheme = TerminalTheme(
cursor: Color(0xFF333333),
selection: Color(0x44007ACC),
foreground: Color(0xFF1A1A1A),
background: Color(0xFFFFFFFF),
black: Color(0xFF000000),
red: Color(0xFFCD3131),
green: Color(0xFF008000),
yellow: Color(0xFF795E25),
blue: Color(0xFF0451A5),
magenta: Color(0xFFBC05BC),
cyan: Color(0xFF0598BC),
white: Color(0xFFA5A5A5),
brightBlack: Color(0xFF666666),
brightRed: Color(0xFFCD3131),
brightGreen: Color(0xFF14CE14),
brightYellow: Color(0xFFB5BA00),
brightBlue: Color(0xFF0451A5),
brightMagenta: Color(0xFFBC05BC),
brightCyan: Color(0xFF0598BC),
brightWhite: Color(0xFFFFFFFF),
searchHitBackground: Color(0xAAE8AB53),
searchHitBackgroundCurrent: Color(0xDD14CE14),
searchHitForeground: Color(0xFF000000),
);
const _draculaTheme = TerminalTheme(
cursor: Color(0xFFF8F8F2),
selection: Color(0xAA44475A),
foreground: Color(0xFFF8F8F2),
background: Color(0xFF282A36),
black: Color(0xFF21222C),
red: Color(0xFFFF5555),
green: Color(0xFF50FA7B),
yellow: Color(0xFFF1FA8C),
blue: Color(0xFFBD93F9),
magenta: Color(0xFFFF79C6),
cyan: Color(0xFF8BE9FD),
white: Color(0xFFF8F8F2),
brightBlack: Color(0xFF6272A4),
brightRed: Color(0xFFFF6E6E),
brightGreen: Color(0xFF69FF94),
brightYellow: Color(0xFFFFFFA5),
brightBlue: Color(0xFFD6ACFF),
brightMagenta: Color(0xFFFF92DF),
brightCyan: Color(0xFFA4FFFF),
brightWhite: Color(0xFFFFFFFF),
searchHitBackground: Color(0xAAF1FA8C),
searchHitBackgroundCurrent: Color(0xDD50FA7B),
searchHitForeground: Color(0xFF282A36),
);
const _nordTheme = TerminalTheme(
cursor: Color(0xFFD8DEE9),
selection: Color(0xAA434C5E),
foreground: Color(0xFFD8DEE9),
background: Color(0xFF2E3440),
black: Color(0xFF3B4252),
red: Color(0xFFBF616A),
green: Color(0xFFA3BE8C),
yellow: Color(0xFFEBCB8B),
blue: Color(0xFF81A1C1),
magenta: Color(0xFFB48EAD),
cyan: Color(0xFF88C0D0),
white: Color(0xFFE5E9F0),
brightBlack: Color(0xFF4C566A),
brightRed: Color(0xFFBF616A),
brightGreen: Color(0xFFA3BE8C),
brightYellow: Color(0xFFEBCB8B),
brightBlue: Color(0xFF81A1C1),
brightMagenta: Color(0xFFB48EAD),
brightCyan: Color(0xFF8FBCBB),
brightWhite: Color(0xFFECEFF4),
searchHitBackground: Color(0xAAEBCB8B),
searchHitBackgroundCurrent: Color(0xDDA3BE8C),
searchHitForeground: Color(0xFF2E3440),
);
const _solarizedDarkTheme = TerminalTheme(
cursor: Color(0xFF839496),
selection: Color(0xAA073642),
foreground: Color(0xFF839496),
background: Color(0xFF002B36),
black: Color(0xFF073642),
red: Color(0xFFDC322F),
green: Color(0xFF859900),
yellow: Color(0xFFB58900),
blue: Color(0xFF268BD2),
magenta: Color(0xFFD33682),
cyan: Color(0xFF2AA198),
white: Color(0xFFEEE8D5),
brightBlack: Color(0xFF586E75),
brightRed: Color(0xFFCB4B16),
brightGreen: Color(0xFF859900),
brightYellow: Color(0xFFB58900),
brightBlue: Color(0xFF268BD2),
brightMagenta: Color(0xFF6C71C4),
brightCyan: Color(0xFF2AA198),
brightWhite: Color(0xFFFDF6E3),
searchHitBackground: Color(0xAAB58900),
searchHitBackgroundCurrent: Color(0xDD859900),
searchHitForeground: Color(0xFF002B36),
);
const _monokaiTheme = TerminalTheme(
cursor: Color(0xFFF8F8F0),
selection: Color(0xAA49483E),
foreground: Color(0xFFF8F8F2),
background: Color(0xFF272822),
black: Color(0xFF272822),
red: Color(0xFFF92672),
green: Color(0xFFA6E22E),
yellow: Color(0xFFF4BF75),
blue: Color(0xFF66D9EF),
magenta: Color(0xFFAE81FF),
cyan: Color(0xFFA1EFE4),
white: Color(0xFFF8F8F2),
brightBlack: Color(0xFF75715E),
brightRed: Color(0xFFF92672),
brightGreen: Color(0xFFA6E22E),
brightYellow: Color(0xFFF4BF75),
brightBlue: Color(0xFF66D9EF),
brightMagenta: Color(0xFFAE81FF),
brightCyan: Color(0xFFA1EFE4),
brightWhite: Color(0xFFF9F8F5),
searchHitBackground: Color(0xAAF4BF75),
searchHitBackgroundCurrent: Color(0xDDA6E22E),
searchHitForeground: Color(0xFF272822),
);
const _oneDarkTheme = TerminalTheme(
cursor: Color(0xFF528BFF),
selection: Color(0xAA3E4451),
foreground: Color(0xFFD7DAE0),
background: Color(0xFF282C34),
black: Color(0xFF282C34),
red: Color(0xFFE06C75),
green: Color(0xFF98C379),
yellow: Color(0xFFD19A66),
blue: Color(0xFF61AFEF),
magenta: Color(0xFFC678DD),
cyan: Color(0xFF56B6C2),
white: Color(0xFFD7DAE0),
brightBlack: Color(0xFF5C6370),
brightRed: Color(0xFFE06C75),
brightGreen: Color(0xFF98C379),
brightYellow: Color(0xFFD19A66),
brightBlue: Color(0xFF61AFEF),
brightMagenta: Color(0xFFC678DD),
brightCyan: Color(0xFF56B6C2),
brightWhite: Color(0xFFFFFFFF),
searchHitBackground: Color(0xAAD19A66),
searchHitBackgroundCurrent: Color(0xDD98C379),
searchHitForeground: Color(0xFF282C34),
);
const terminalThemePresets = <TerminalThemePreset>[
TerminalThemePreset(id: 'warm', theme: _warmTheme),
TerminalThemePreset(id: 'dark', theme: _darkTheme),
TerminalThemePreset(id: 'light', theme: _lightTheme),
TerminalThemePreset(id: 'dracula', theme: _draculaTheme),
TerminalThemePreset(id: 'nord', theme: _nordTheme),
TerminalThemePreset(id: 'solarizedDark', theme: _solarizedDarkTheme),
TerminalThemePreset(id: 'monokai', theme: _monokaiTheme),
TerminalThemePreset(id: 'oneDark', theme: _oneDarkTheme),
];
TerminalTheme terminalThemeForPreset(String id) {
for (final preset in terminalThemePresets) {
if (preset.id == id) return preset.theme;
}
return _warmTheme;
}
+12
View File
@@ -478,6 +478,18 @@ const Map<String, String> stringsEn = {
'terminal.connections.disconnected': 'Disconnected',
'terminal.commandInput.hint': 'Enter command...',
'terminal.commandInput.send': 'Send',
'terminal.settings': 'Terminal Settings',
'terminal.settings.fontSize': 'Font Size',
'terminal.settings.fontFamily': 'Font',
'terminal.settings.themePreset': 'Theme',
'terminal.settings.preset.warm': 'Warm',
'terminal.settings.preset.dark': 'Dark',
'terminal.settings.preset.light': 'Light',
'terminal.settings.preset.dracula': 'Dracula',
'terminal.settings.preset.nord': 'Nord',
'terminal.settings.preset.solarizedDark': 'Solarized',
'terminal.settings.preset.monokai': 'Monokai',
'terminal.settings.preset.oneDark': 'One Dark',
'terminal.script.send': 'Send',
'terminal.script.saveAndSend': 'Save & send',
'terminal.script.noActive': 'No active terminal.',
+12
View File
@@ -451,6 +451,18 @@ const Map<String, String> stringsZh = {
'terminal.connections.disconnected': '未连接',
'terminal.commandInput.hint': '输入命令...',
'terminal.commandInput.send': '发送',
'terminal.settings': '终端设置',
'terminal.settings.fontSize': '字体大小',
'terminal.settings.fontFamily': '字体',
'terminal.settings.themePreset': '主题',
'terminal.settings.preset.warm': '暖色',
'terminal.settings.preset.dark': '暗黑',
'terminal.settings.preset.light': '亮色',
'terminal.settings.preset.dracula': 'Dracula',
'terminal.settings.preset.nord': 'Nord',
'terminal.settings.preset.solarizedDark': 'Solarized',
'terminal.settings.preset.monokai': 'Monokai',
'terminal.settings.preset.oneDark': 'One Dark',
'terminal.script.send': '发送',
'terminal.script.saveAndSend': '保存并发送',
'terminal.script.noActive': '没有活动的终端',
@@ -0,0 +1,69 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:xterm/xterm.dart';
import '../core/storage/app_storage.dart';
import '../features/terminal/terminal_theme_presets.dart';
import 'storage_providers.dart';
class TerminalSettings {
const TerminalSettings({
this.fontSize = 12.0,
this.fontFamily = 'monospace',
this.themePreset = 'warm',
});
final double fontSize;
final String fontFamily;
final String themePreset;
TerminalTheme get terminalTheme => terminalThemeForPreset(themePreset);
TerminalStyle get terminalStyle => TerminalStyle(
fontSize: fontSize,
fontFamily: fontFamily,
);
TerminalSettings copyWith({
double? fontSize,
String? fontFamily,
String? themePreset,
}) {
return TerminalSettings(
fontSize: fontSize ?? this.fontSize,
fontFamily: fontFamily ?? this.fontFamily,
themePreset: themePreset ?? this.themePreset,
);
}
}
class TerminalSettingsNotifier extends StateNotifier<TerminalSettings> {
TerminalSettingsNotifier(this._storage)
: super(TerminalSettings(
fontSize: _storage.terminalFontSize,
fontFamily: _storage.terminalFontFamily,
themePreset: _storage.terminalThemePreset,
));
final AppStorage _storage;
Future<void> setFontSize(double v) async {
final clamped = v.clamp(10.0, 24.0);
await _storage.setTerminalFontSize(clamped);
state = state.copyWith(fontSize: clamped);
}
Future<void> setFontFamily(String v) async {
await _storage.setTerminalFontFamily(v);
state = state.copyWith(fontFamily: v);
}
Future<void> setThemePreset(String v) async {
await _storage.setTerminalThemePreset(v);
state = state.copyWith(themePreset: v);
}
}
final terminalSettingsProvider =
StateNotifierProvider<TerminalSettingsNotifier, TerminalSettings>((ref) {
return TerminalSettingsNotifier(ref.watch(appStorageProvider));
});