mirror of
https://github.com/chaos-zhu/easynode.git
synced 2026-10-07 17:08:29 +08:00
feat: 初版终端设置支持
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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.',
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
Reference in New Issue
Block a user