mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
feat: searching in editor page (#756)
* feat: searching in editor page Fixes #734 * opt.: editor searching ui
This commit is contained in:
@@ -14,6 +14,7 @@ import 'package:server_box/data/store/setting.dart';
|
|||||||
|
|
||||||
import 'package:server_box/view/widget/two_line_text.dart';
|
import 'package:server_box/view/widget/two_line_text.dart';
|
||||||
import 'package:re_editor/re_editor.dart';
|
import 'package:re_editor/re_editor.dart';
|
||||||
|
import 'package:server_box/view/widget/code_find_panel_view.dart';
|
||||||
|
|
||||||
enum EditorPageRetType { path, text }
|
enum EditorPageRetType { path, text }
|
||||||
|
|
||||||
@@ -67,6 +68,7 @@ class _EditorPageState extends State<EditorPage> {
|
|||||||
final _focusNode = FocusNode();
|
final _focusNode = FocusNode();
|
||||||
|
|
||||||
late CodeLineEditingController _controller;
|
late CodeLineEditingController _controller;
|
||||||
|
late CodeFindController _findController;
|
||||||
late Map<String, TextStyle> _codeTheme;
|
late Map<String, TextStyle> _codeTheme;
|
||||||
|
|
||||||
String? _langCode;
|
String? _langCode;
|
||||||
@@ -74,14 +76,17 @@ class _EditorPageState extends State<EditorPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
|
_findController.dispose();
|
||||||
_focusNode.dispose();
|
_focusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_controller = CodeLineEditingController();
|
||||||
|
_findController = CodeFindController(_controller);
|
||||||
_init();
|
_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +125,11 @@ class _EditorPageState extends State<EditorPage> {
|
|||||||
down: l10n.editor,
|
down: l10n.editor,
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.search),
|
||||||
|
tooltip: libL10n.search,
|
||||||
|
onPressed: () => _findController.findMode(),
|
||||||
|
),
|
||||||
PopupMenuButton<String>(
|
PopupMenuButton<String>(
|
||||||
icon: const Icon(Icons.language),
|
icon: const Icon(Icons.language),
|
||||||
tooltip: libL10n.language,
|
tooltip: libL10n.language,
|
||||||
@@ -150,6 +160,7 @@ class _EditorPageState extends State<EditorPage> {
|
|||||||
color: _EditorReTheme.getBackground(_codeTheme),
|
color: _EditorReTheme.getBackground(_codeTheme),
|
||||||
child: CodeEditor(
|
child: CodeEditor(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
|
findController: _findController,
|
||||||
wordWrap: Stores.setting.editorSoftWrap.fetch(),
|
wordWrap: Stores.setting.editorSoftWrap.fetch(),
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
indicatorBuilder: (context, editingController, chunkController, notifier) {
|
indicatorBuilder: (context, editingController, chunkController, notifier) {
|
||||||
@@ -162,7 +173,8 @@ class _EditorPageState extends State<EditorPage> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
// findBuilder: (context, controller, readOnly) => CodeFindPanelView(controller: controller, readOnly: readOnly),
|
findBuilder: (context, controller, readOnly) =>
|
||||||
|
CodeFindPanelView(controller: controller, readOnly: readOnly),
|
||||||
// toolbarController: const ContextMenuControllerImpl(),
|
// toolbarController: const ContextMenuControllerImpl(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -175,7 +187,6 @@ extension on _EditorPageState {
|
|||||||
if (Stores.setting.editorHighlight.fetch()) {
|
if (Stores.setting.editorHighlight.fetch()) {
|
||||||
_langCode = widget.args?.langCode ?? Highlights.getCode(widget.args?.path);
|
_langCode = widget.args?.langCode ?? Highlights.getCode(widget.args?.path);
|
||||||
}
|
}
|
||||||
_controller = CodeLineEditingController();
|
|
||||||
if (_langCode == null) {
|
if (_langCode == null) {
|
||||||
_setupCtrl();
|
_setupCtrl();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
239
lib/view/widget/code_find_panel_view.dart
Normal file
239
lib/view/widget/code_find_panel_view.dart
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:fl_lib/fl_lib.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:re_editor/re_editor.dart';
|
||||||
|
|
||||||
|
const EdgeInsetsGeometry _kDefaultFindMargin = EdgeInsets.only(right: 10);
|
||||||
|
const double _kDefaultFindPanelWidth = 360;
|
||||||
|
const double _kDefaultFindPanelHeight = 36;
|
||||||
|
const double _kDefaultReplacePanelHeight = _kDefaultFindPanelHeight * 2;
|
||||||
|
const double _kDefaultFindIconSize = 16;
|
||||||
|
const double _kDefaultFindIconWidth = 30;
|
||||||
|
const double _kDefaultFindIconHeight = 30;
|
||||||
|
const double _kDefaultFindInputFontSize = 13;
|
||||||
|
const double _kDefaultFindResultFontSize = 12;
|
||||||
|
const EdgeInsetsGeometry _kDefaultFindPadding = EdgeInsets.only(left: 5, right: 5, top: 2.5, bottom: 2.5);
|
||||||
|
const EdgeInsetsGeometry _kDefaultFindInputContentPadding = EdgeInsets.only(left: 5, right: 5);
|
||||||
|
|
||||||
|
class CodeFindPanelView extends StatelessWidget implements PreferredSizeWidget {
|
||||||
|
final CodeFindController controller;
|
||||||
|
final EdgeInsetsGeometry margin;
|
||||||
|
final bool readOnly;
|
||||||
|
final Color? iconColor;
|
||||||
|
final Color? iconSelectedColor;
|
||||||
|
final double iconSize;
|
||||||
|
final double inputFontSize;
|
||||||
|
final double resultFontSize;
|
||||||
|
final Color? inputTextColor;
|
||||||
|
final Color? resultFontColor;
|
||||||
|
final EdgeInsetsGeometry padding;
|
||||||
|
final InputDecoration decoration;
|
||||||
|
|
||||||
|
const CodeFindPanelView({
|
||||||
|
super.key,
|
||||||
|
required this.controller,
|
||||||
|
this.margin = _kDefaultFindMargin,
|
||||||
|
required this.readOnly,
|
||||||
|
this.iconSelectedColor,
|
||||||
|
this.iconColor,
|
||||||
|
this.iconSize = _kDefaultFindIconSize,
|
||||||
|
this.inputFontSize = _kDefaultFindInputFontSize,
|
||||||
|
this.resultFontSize = _kDefaultFindResultFontSize,
|
||||||
|
this.inputTextColor,
|
||||||
|
this.resultFontColor,
|
||||||
|
this.padding = _kDefaultFindPadding,
|
||||||
|
this.decoration = const InputDecoration(
|
||||||
|
filled: true,
|
||||||
|
contentPadding: _kDefaultFindInputContentPadding,
|
||||||
|
border: OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(0)), gapPadding: 0),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Size get preferredSize {
|
||||||
|
final value = controller.value;
|
||||||
|
final height = value == null
|
||||||
|
? 0.0
|
||||||
|
: (value.replaceMode ? _kDefaultReplacePanelHeight : _kDefaultFindPanelHeight) + margin.vertical;
|
||||||
|
return Size(double.infinity, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (controller.value == null) return UIs.placeholder;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: margin,
|
||||||
|
alignment: Alignment.topRight,
|
||||||
|
height: preferredSize.height,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: SizedBox(
|
||||||
|
width: _kDefaultFindPanelWidth,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_buildFindInputView(context),
|
||||||
|
if (controller.value!.replaceMode) _buildReplaceInputView(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFindInputView(BuildContext context) {
|
||||||
|
final CodeFindValue value = controller.value!;
|
||||||
|
final String result;
|
||||||
|
if (value.result == null) {
|
||||||
|
result = libL10n.empty;
|
||||||
|
} else {
|
||||||
|
final index = value.result?.index;
|
||||||
|
final count = value.result?.matches.length;
|
||||||
|
result = '$index/$count';
|
||||||
|
}
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: _kDefaultFindPanelWidth / 1.75,
|
||||||
|
height: _kDefaultFindPanelHeight,
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
_buildTextField(
|
||||||
|
context: context,
|
||||||
|
controller: controller.findInputController,
|
||||||
|
focusNode: controller.findInputFocusNode,
|
||||||
|
iconsWidth: _kDefaultFindIconWidth * 1.5,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
_buildCheckText(
|
||||||
|
context: context,
|
||||||
|
text: 'Aa',
|
||||||
|
checked: value.option.caseSensitive,
|
||||||
|
onPressed: controller.toggleCaseSensitive,
|
||||||
|
),
|
||||||
|
_buildCheckText(
|
||||||
|
context: context,
|
||||||
|
text: '.*',
|
||||||
|
checked: value.option.regex,
|
||||||
|
onPressed: controller.toggleRegex,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
Text(result, style: TextStyle(color: resultFontColor, fontSize: resultFontSize)),
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
_buildIconButton(
|
||||||
|
onPressed: value.result == null ? null : controller.previousMatch,
|
||||||
|
icon: Icons.arrow_upward,
|
||||||
|
tooltip: libL10n.previous,
|
||||||
|
),
|
||||||
|
_buildIconButton(
|
||||||
|
onPressed: value.result == null ? null : controller.nextMatch,
|
||||||
|
icon: Icons.arrow_downward,
|
||||||
|
tooltip: libL10n.next,
|
||||||
|
),
|
||||||
|
_buildIconButton(
|
||||||
|
onPressed: controller.close,
|
||||||
|
icon: Icons.close,
|
||||||
|
tooltip: libL10n.close,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildReplaceInputView(BuildContext context) {
|
||||||
|
final CodeFindValue value = controller.value!;
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: _kDefaultFindPanelWidth / 1.75,
|
||||||
|
height: _kDefaultFindPanelHeight,
|
||||||
|
child: _buildTextField(
|
||||||
|
context: context,
|
||||||
|
controller: controller.replaceInputController,
|
||||||
|
focusNode: controller.replaceInputFocusNode,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildIconButton(
|
||||||
|
onPressed: value.result == null ? null : controller.replaceMatch,
|
||||||
|
icon: Icons.done,
|
||||||
|
tooltip: libL10n.replace,
|
||||||
|
),
|
||||||
|
_buildIconButton(
|
||||||
|
onPressed: value.result == null ? null : controller.replaceAllMatches,
|
||||||
|
icon: Icons.done_all,
|
||||||
|
tooltip: libL10n.replaceAll,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTextField({
|
||||||
|
required BuildContext context,
|
||||||
|
required TextEditingController controller,
|
||||||
|
required FocusNode focusNode,
|
||||||
|
double iconsWidth = 0,
|
||||||
|
}) {
|
||||||
|
return Padding(
|
||||||
|
padding: padding,
|
||||||
|
child: TextField(
|
||||||
|
maxLines: 1,
|
||||||
|
focusNode: focusNode,
|
||||||
|
style: TextStyle(color: inputTextColor, fontSize: inputFontSize),
|
||||||
|
decoration: decoration.copyWith(
|
||||||
|
contentPadding:
|
||||||
|
(decoration.contentPadding ?? EdgeInsets.zero).add(EdgeInsets.only(right: iconsWidth))),
|
||||||
|
controller: controller,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCheckText({
|
||||||
|
required BuildContext context,
|
||||||
|
required String text,
|
||||||
|
required bool checked,
|
||||||
|
required VoidCallback onPressed,
|
||||||
|
}) {
|
||||||
|
final Color selectedColor = iconSelectedColor ?? Theme.of(context).primaryColor;
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onPressed,
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
child: SizedBox(
|
||||||
|
width: _kDefaultFindIconWidth * 0.75,
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: TextStyle(
|
||||||
|
color: checked ? selectedColor : iconColor,
|
||||||
|
fontSize: inputFontSize,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildIconButton({required IconData icon, VoidCallback? onPressed, String? tooltip}) {
|
||||||
|
return IconButton(
|
||||||
|
onPressed: onPressed,
|
||||||
|
icon: Icon(
|
||||||
|
icon,
|
||||||
|
size: iconSize,
|
||||||
|
),
|
||||||
|
constraints: const BoxConstraints(maxWidth: _kDefaultFindIconWidth, maxHeight: _kDefaultFindIconHeight),
|
||||||
|
tooltip: tooltip,
|
||||||
|
splashRadius: max(_kDefaultFindIconWidth, _kDefaultFindIconHeight) / 2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -534,8 +534,8 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "v1.0.283"
|
ref: "v1.0.285"
|
||||||
resolved-ref: "6e25f9f00bbf5c2e2900009f9f2e28454050c4ec"
|
resolved-ref: "5bcde3c934290093bc468d24c6f308da8fb8dd4e"
|
||||||
url: "https://github.com/lppcg/fl_lib"
|
url: "https://github.com/lppcg/fl_lib"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ dependencies:
|
|||||||
fl_lib:
|
fl_lib:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/lppcg/fl_lib
|
url: https://github.com/lppcg/fl_lib
|
||||||
ref: v1.0.283
|
ref: v1.0.285
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
# webdav_client_plus:
|
# webdav_client_plus:
|
||||||
|
|||||||
Reference in New Issue
Block a user