mirror of
https://github.com/qinglong-app/qinglong_app.git
synced 2025-10-09 16:48:19 +08:00
add function
This commit is contained in:
284
lib/base/code_editor/code_editor.dart
Normal file
284
lib/base/code_editor/code_editor.dart
Normal file
@@ -0,0 +1,284 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_highlight/flutter_highlight.dart';
|
||||
import 'package:qinglong_app/utils/codeeditor_theme.dart';
|
||||
import 'EditorModel.dart';
|
||||
import 'EditorModelStyleOptions.dart';
|
||||
import 'FileEditor.dart';
|
||||
|
||||
/// Creates a code editor that helps users to write and read code.
|
||||
///
|
||||
/// In order to use it, you must define :
|
||||
/// * [model] an EditorModel, to control the editor, its content and its files
|
||||
/// * [onSubmit] a Function(String language, String value) executed when the user submits changes in a file.
|
||||
class CodeEditor extends StatefulWidget {
|
||||
/// The EditorModel in order to control the editor.
|
||||
///
|
||||
/// This argument is @required.
|
||||
late final EditorModel? model;
|
||||
|
||||
/// onSubmit function to execute when the user saves changes in a file.
|
||||
/// This is a function that takes [language] and [value] as arguments.
|
||||
///
|
||||
/// * [language] is the language of the file edited by the user.
|
||||
/// * [value] is the content of the file.
|
||||
final Function(String? language, String? value)? onSubmit;
|
||||
|
||||
/// You can disable the edit button (it won't show up at all) just like this :
|
||||
///
|
||||
/// ```
|
||||
/// CodeEditor(
|
||||
/// model: model, // my EditorModel()
|
||||
/// edit: false, // disable the edit button
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// By default, the value is true.
|
||||
final bool edit;
|
||||
|
||||
/// You can disable the navigation bar like this :
|
||||
///
|
||||
/// ```
|
||||
/// CodeEditor(
|
||||
/// model: model, // my EditorModel()
|
||||
/// disableNavigationbar: true, // hide the navigation bar
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// By default, the value is `false`.
|
||||
///
|
||||
/// WARNING : if you set the value to true, only the first
|
||||
/// file will be displayed in the editor because
|
||||
/// it's not possible to switch betweens other files without the navigation bar.
|
||||
final bool disableNavigationbar;
|
||||
|
||||
/// An optional TextEditingController that can be passed in.
|
||||
late final TextEditingController? textEditingController;
|
||||
|
||||
CodeEditor({
|
||||
Key? key,
|
||||
this.model,
|
||||
this.onSubmit,
|
||||
this.edit = true,
|
||||
this.disableNavigationbar = false,
|
||||
this.textEditingController,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
CodeEditorState createState() => CodeEditorState();
|
||||
}
|
||||
|
||||
class CodeEditorState extends State<CodeEditor> {
|
||||
/// We need it to control the content of the text field.
|
||||
late TextEditingController editingController;
|
||||
|
||||
/// The new content of a file when the user is editing one.
|
||||
String? newValue;
|
||||
|
||||
|
||||
/// The text field wants a focus node.
|
||||
FocusNode focusNode = FocusNode();
|
||||
|
||||
/// Initialize the formKey for the text field
|
||||
static final GlobalKey<FormState> editableTextKey = GlobalKey<FormState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
if (widget.textEditingController != null) {
|
||||
// Use the user-provide controller
|
||||
editingController = widget.textEditingController!;
|
||||
} else {
|
||||
/// Initialize the controller for the text field.
|
||||
editingController = TextEditingController(text: "");
|
||||
}
|
||||
newValue = ""; // if there are no changes
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
editingController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Set the cursor at the end of the editableText.
|
||||
void placeCursorAtTheEnd() {
|
||||
editingController.selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: editingController.text.length),
|
||||
);
|
||||
}
|
||||
|
||||
/// Place the cursor where wanted.
|
||||
///
|
||||
/// [pos] places the cursor in the text field
|
||||
void placeCursor(int pos) {
|
||||
try {
|
||||
editingController.selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: pos),
|
||||
);
|
||||
} catch (e) {
|
||||
throw Exception("code_editor : placeCursor(int pos), pos is not valid.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int getCurrentIndex(){
|
||||
return widget.model?.position ?? 0;
|
||||
}
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
/// Gets the model from the parent widget.
|
||||
EditorModel model = widget.model ??= EditorModel(files: []);
|
||||
|
||||
/// Gets the style options from the parent widget.
|
||||
EditorModelStyleOptions? opt = model.styleOptions;
|
||||
|
||||
String? language = model.currentLanguage;
|
||||
|
||||
/// Which file in the list of file ?
|
||||
int? position = model.position;
|
||||
|
||||
/// The content of the file where position corresponds to the list of file.
|
||||
String? code = model.getCodeWithIndex(position ?? 0);
|
||||
|
||||
bool disableNavigationbar = widget.disableNavigationbar;
|
||||
|
||||
// When we change the file in the navbar, the code in the text field
|
||||
// isn't updated, so we update it here.
|
||||
//
|
||||
// With newValue = code if the user does not change the value
|
||||
// in the text field
|
||||
editingController = TextEditingController(text: code);
|
||||
newValue = code;
|
||||
|
||||
/// The filename in green.
|
||||
Text showFilename(String name, bool isSelected) {
|
||||
return Text(
|
||||
name,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: opt?.fontSizeOfFilename,
|
||||
color: isSelected ? opt?.editorFilenameColor : opt?.editorNormalFilenameColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build the navigation bar.
|
||||
Container buildNavbar() {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: opt?.editorColor,
|
||||
border: Border(bottom: BorderSide(color: opt?.editorBorderColor ?? Colors.blue)),
|
||||
),
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.only(left: 15),
|
||||
itemCount: model.numberOfFiles,
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: (context, int index) {
|
||||
final FileEditor file = model.getFileWithIndex(index);
|
||||
|
||||
return Container(
|
||||
margin: EdgeInsets.only(right: 15),
|
||||
child: Center(
|
||||
child: GestureDetector(
|
||||
// Checks if the position of the navbar is the current file.
|
||||
child: showFilename(file.name, model.position == index),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
model.changeIndexTo(index);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/// This button won't appear if `edit = false`.
|
||||
Widget editButton(String name, Function() press) {
|
||||
if (widget.edit == true) {
|
||||
return Positioned(
|
||||
bottom: opt?.editButtonPosBottom,
|
||||
right: opt?.editButtonPosRight,
|
||||
top: (model.isEditing && opt != null && opt.editButtonPosTop != null && opt.editButtonPosTop! < 50) ? 50 : opt?.editButtonPosTop,
|
||||
left: opt?.editButtonPosLeft,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: opt?.editButtonBackgroundColor,
|
||||
),
|
||||
onPressed: press,
|
||||
child: Text(
|
||||
name,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontFamily: "monospace",
|
||||
fontWeight: FontWeight.normal,
|
||||
color: opt?.editButtonTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
// We place the cursor in the end of the text field.
|
||||
|
||||
if (model.isEditing && (model.styleOptions?.placeCursorAtTheEndOnEdit ?? true)) {
|
||||
placeCursorAtTheEnd();
|
||||
}
|
||||
|
||||
/// We toggle the editor and the text field.
|
||||
Widget buildContentEditor() {
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: opt?.heightOfContainer,
|
||||
color: opt?.editorColor,
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: opt?.padding ?? const EdgeInsets.all(3.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
HighlightView(
|
||||
code ?? "code is null",
|
||||
language: language,
|
||||
theme: opt?.theme ?? qinglongLightTheme,
|
||||
tabSize: opt?.tabSize ?? 4,
|
||||
textStyle: TextStyle(
|
||||
fontFamily: opt?.fontFamily,
|
||||
letterSpacing: opt?.letterSpacing,
|
||||
fontSize: opt?.fontSize,
|
||||
height: opt?.lineHeight, // line-height
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
editButton(opt?.editButtonName ?? "Edit", () {
|
||||
setState(() {
|
||||
model.toggleEditing();
|
||||
});
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
disableNavigationbar ? SizedBox.shrink() : buildNavbar(),
|
||||
buildContentEditor(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user