Support override script

Support proxies search

Support svg display

Optimize config persistence

Add some scenes auto close connections

Update core

Optimize more details
This commit is contained in:
chen08209
2025-05-02 02:24:12 +08:00
parent 76c9f08d4a
commit afbc5adb05
174 changed files with 8940 additions and 5433 deletions

View File

@@ -2,31 +2,40 @@ import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/common.dart';
import 'package:fl_clash/providers/app.dart';
import 'package:fl_clash/widgets/pop_scope.dart';
import 'package:fl_clash/widgets/popup.dart';
import 'package:fl_clash/widgets/scaffold.dart';
import 'package:fl_clash/widgets/scroll.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:re_editor/re_editor.dart';
import 'package:re_highlight/languages/javascript.dart';
import 'package:re_highlight/languages/yaml.dart';
import 'package:re_highlight/styles/atom-one-light.dart';
typedef EditingValueChangeBuilder = Widget Function(CodeLineEditingValue value);
typedef TextEditingValueChangeBuilder = Widget Function(TextEditingValue value);
class EditorPage extends ConsumerStatefulWidget {
final String title;
final String content;
final Function(BuildContext context, String text)? onSave;
final Future<bool> Function(BuildContext context, String text)? onPop;
final List<Language> languages;
final bool supportRemoteDownload;
final bool titleEditable;
final Function(BuildContext context, String title, String content)? onSave;
final Future<bool> Function(
BuildContext context, String title, String content)? onPop;
const EditorPage({
super.key,
required this.title,
required this.content,
this.titleEditable = false,
this.onSave,
this.onPop,
this.supportRemoteDownload = false,
this.languages = const [
Language.yaml,
],
});
@override
@@ -36,6 +45,7 @@ class EditorPage extends ConsumerStatefulWidget {
class _EditorPageState extends ConsumerState<EditorPage> {
late CodeLineEditingController _controller;
late CodeFindController _findController;
late TextEditingController _titleController;
final _focusNode = FocusNode();
@override
@@ -43,6 +53,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
super.initState();
_controller = CodeLineEditingController.fromText(widget.content);
_findController = CodeFindController(_controller);
_titleController = TextEditingController(text: widget.title);
if (system.isDesktop) {
return;
}
@@ -87,10 +98,58 @@ class _EditorPageState extends ConsumerState<EditorPage> {
);
}
Widget _wrapTitleController(TextEditingValueChangeBuilder builder) {
return ValueListenableBuilder(
valueListenable: _titleController,
builder: (_, value, ___) {
return builder(value);
},
);
}
_handleSearch() {
_findController.findMode();
}
_handleImport() async {
final option = await globalState.showCommonDialog<ImportOption>(
child: _ImportOptionsDialog(),
);
if (option == null) {
return;
}
if (option == ImportOption.file) {
final file = await picker.pickerFile();
if (file == null) {
return;
}
final res = String.fromCharCodes(file.bytes?.toList() ?? []);
_controller.text = res;
return;
}
final url = await globalState.showCommonDialog(
child: InputDialog(
title: "导入",
value: "",
labelText: appLocalizations.url,
validator: (value) {
if (value == null || value.isEmpty) {
return appLocalizations.emptyTip(appLocalizations.value);
}
if (!value.isUrl) {
return appLocalizations.urlTip(appLocalizations.value);
}
return null;
},
),
);
if (url == null) {
return;
}
final res = await request.getTextResponseForUrl(url);
_controller.text = res.data;
}
@override
Widget build(BuildContext context) {
final isMobileView = ref.watch(isMobileViewProvider);
@@ -99,56 +158,89 @@ class _EditorPageState extends ConsumerState<EditorPage> {
if (widget.onPop == null) {
return true;
}
final res = await widget.onPop!(context, _controller.text);
final res = await widget.onPop!(
context,
_titleController.text,
_controller.text,
);
if (res && context.mounted) {
return true;
}
return false;
},
child: CommonScaffold(
actions: [
if (widget.onSave != null)
_wrapController(
(value) => IconButton(
onPressed: _controller.text == widget.content
? null
: () {
widget.onSave!(context, _controller.text);
},
icon: const Icon(Icons.save_sharp),
),
),
_wrapController(
(value) => CommonPopupBox(
targetBuilder: (open) {
return IconButton(
onPressed: open,
icon: const Icon(Icons.more_vert),
);
},
popup: CommonPopupMenu(
minWidth: 180,
items: [
PopupMenuItemData(
icon: Icons.search,
label: appLocalizations.search,
onPressed: _handleSearch,
),
PopupMenuItemData(
icon: Icons.undo,
label: appLocalizations.undo,
onPressed: _controller.canUndo ? _controller.undo : null,
),
PopupMenuItemData(
icon: Icons.redo,
label: appLocalizations.redo,
onPressed: _controller.canRedo ? _controller.redo : null,
),
],
),
appBar: AppBar(
title: TextField(
enabled: widget.titleEditable,
controller: _titleController,
decoration: InputDecoration(
border: _NoInputBorder(),
hintText: appLocalizations.unnamed,
),
style: context.textTheme.titleLarge,
autofocus: false,
),
],
actions: genActions([
if (widget.onSave != null)
_wrapController(
(value) => _wrapTitleController(
(value) => IconButton(
onPressed: _controller.text != widget.content ||
_titleController.text != widget.title
? () {
widget.onSave!(
context,
_titleController.text,
_controller.text,
);
}
: null,
icon: const Icon(Icons.save_sharp),
),
),
),
if (widget.supportRemoteDownload)
IconButton(
onPressed: _handleImport,
icon: Icon(
Icons.arrow_downward,
),
),
_wrapController(
(value) => CommonPopupBox(
targetBuilder: (open) {
return IconButton(
onPressed: () {
open(
offset: Offset(-20, 20),
);
},
icon: const Icon(Icons.more_vert),
);
},
popup: CommonPopupMenu(
items: [
PopupMenuItemData(
icon: Icons.search,
label: appLocalizations.search,
onPressed: _handleSearch,
),
PopupMenuItemData(
icon: Icons.undo,
label: appLocalizations.undo,
onPressed: _controller.canUndo ? _controller.undo : null,
),
PopupMenuItemData(
icon: Icons.redo,
label: appLocalizations.redo,
onPressed: _controller.canRedo ? _controller.redo : null,
),
],
),
),
),
]),
),
body: CodeEditor(
findController: _findController,
findBuilder: (context, controller, readOnly) => FindPanel(
@@ -159,6 +251,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
padding: EdgeInsets.only(
right: 16,
),
autocompleteSymbols: true,
focusNode: _focusNode,
scrollbarBuilder: (context, child, details) {
return CommonScrollBar(
@@ -190,19 +283,23 @@ class _EditorPageState extends ConsumerState<EditorPage> {
shortcutsActivatorsBuilder: DefaultCodeShortcutsActivatorsBuilder(),
controller: _controller,
style: CodeEditorStyle(
fontSize: 14.ap,
fontSize: context.textTheme.bodyLarge?.fontSize?.ap,
fontFamily: FontFamily.jetBrainsMono.value,
codeTheme: CodeHighlightTheme(
languages: {
'yaml': CodeHighlightThemeMode(
mode: langYaml,
)
if (widget.languages.contains(Language.yaml))
'yaml': CodeHighlightThemeMode(
mode: langYaml,
),
if (widget.languages.contains(Language.javaScript))
"javascript": CodeHighlightThemeMode(
mode: langJavascript,
),
},
theme: atomOneLightTheme,
),
),
),
title: widget.title,
),
);
}
@@ -538,3 +635,88 @@ class ContextMenuControllerImpl implements SelectionToolbarController {
Overlay.of(context).insert(_overlayEntry!);
}
}
class _NoInputBorder extends InputBorder {
const _NoInputBorder() : super(borderSide: BorderSide.none);
@override
_NoInputBorder copyWith({BorderSide? borderSide}) => const _NoInputBorder();
@override
bool get isOutline => false;
@override
EdgeInsetsGeometry get dimensions => EdgeInsets.zero;
@override
_NoInputBorder scale(double t) => const _NoInputBorder();
@override
Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
return Path()..addRect(rect);
}
@override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
return Path()..addRect(rect);
}
@override
void paintInterior(Canvas canvas, Rect rect, Paint paint,
{TextDirection? textDirection}) {
canvas.drawRect(rect, paint);
}
@override
bool get preferPaintInterior => true;
@override
void paint(
Canvas canvas,
Rect rect, {
double? gapStart,
double gapExtent = 0.0,
double gapPercentage = 0.0,
TextDirection? textDirection,
}) {}
}
class _ImportOptionsDialog extends StatefulWidget {
const _ImportOptionsDialog();
@override
State<_ImportOptionsDialog> createState() => _ImportOptionsDialogState();
}
class _ImportOptionsDialogState extends State<_ImportOptionsDialog> {
_handleOnTab(ImportOption value) {
Navigator.of(context).pop(value);
}
@override
Widget build(BuildContext context) {
return CommonDialog(
title: appLocalizations.import,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 16,
),
child: Wrap(
children: [
ListItem(
onTap: () {
_handleOnTab(ImportOption.url);
},
title: Text(appLocalizations.importUrl),
),
ListItem(
onTap: () {
_handleOnTab(ImportOption.file);
},
title: Text(appLocalizations.importFile),
)
],
),
);
}
}