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:
101
lib/base/code_editor/EditorModel.dart
Normal file
101
lib/base/code_editor/EditorModel.dart
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:qinglong_app/utils/codeeditor_theme.dart';
|
||||||
|
import 'FileEditor.dart';
|
||||||
|
import 'EditorModelStyleOptions.dart';
|
||||||
|
/// Use the EditorModel into CodeEditor in order to control the editor.
|
||||||
|
///
|
||||||
|
/// EditorModel extends ChangeNotifier because we use the provider package
|
||||||
|
/// to simplify the work.
|
||||||
|
class EditorModel extends ChangeNotifier {
|
||||||
|
late int _currentPositionInFiles;
|
||||||
|
bool _isEditing = false;
|
||||||
|
EditorModelStyleOptions? styleOptions;
|
||||||
|
late List<String?> _languages;
|
||||||
|
late List<FileEditor> allFiles;
|
||||||
|
|
||||||
|
/// Define the required parameters for the editor to work properly.
|
||||||
|
/// For that, you need to define [files] wich is a `List<FileEditor>`.
|
||||||
|
///
|
||||||
|
/// You can also define your own preferences with [styleOptions].
|
||||||
|
EditorModel({required List<FileEditor> files, this.styleOptions}) {
|
||||||
|
if (this.styleOptions == null) {
|
||||||
|
this.styleOptions = new EditorModelStyleOptions(theme: qinglongLightTheme);
|
||||||
|
}
|
||||||
|
this._languages = [];
|
||||||
|
this._currentPositionInFiles = 0;
|
||||||
|
if (files.length == 0) {
|
||||||
|
files.add(
|
||||||
|
new FileEditor(
|
||||||
|
name: "index.html",
|
||||||
|
language: "html",
|
||||||
|
code: "",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
files.forEach((FileEditor file) {
|
||||||
|
this._languages.add(file.language);
|
||||||
|
});
|
||||||
|
this.allFiles = files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks in all the given files if [language] is found,
|
||||||
|
/// then returns a List<String> of the files' content that uses [language].
|
||||||
|
List<String?> getCodeWithLanguage(String language) {
|
||||||
|
List<String?> listOfCode = [];
|
||||||
|
this.allFiles.forEach((FileEditor file) {
|
||||||
|
if (file.language == language) {
|
||||||
|
listOfCode.add(file.code);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return listOfCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the code of the file where [index] corresponds.
|
||||||
|
String? getCodeWithIndex(int index) {
|
||||||
|
return this.allFiles[index].code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the file where [index] corresponds.
|
||||||
|
FileEditor getFileWithIndex(int index) {
|
||||||
|
return this.allFiles[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Switch the file using [i] as index of the List<FileEditor> files.
|
||||||
|
///
|
||||||
|
/// The user can't change the file if he is editing another one.
|
||||||
|
void changeIndexTo(int i) {
|
||||||
|
if (this._isEditing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._currentPositionInFiles = i;
|
||||||
|
this.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Toggle the text field.
|
||||||
|
void toggleEditing() {
|
||||||
|
this._isEditing = !this._isEditing;
|
||||||
|
this.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Overwite the previous code of the file where [index] corresponds by [newCode].
|
||||||
|
void updateCodeOfIndex(int index, String? newCode) {
|
||||||
|
this.allFiles[index].code = newCode;
|
||||||
|
// this.allFiles[index].setCode = newCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void notify() => notifyListeners();
|
||||||
|
|
||||||
|
/// Gets the index of wich file is currently displayed in the editor.
|
||||||
|
int? get position => this._currentPositionInFiles;
|
||||||
|
|
||||||
|
/// Gets which language is currently shown.
|
||||||
|
String? get currentLanguage =>
|
||||||
|
this.allFiles[this._currentPositionInFiles].language;
|
||||||
|
|
||||||
|
/// Is the text field shown ?
|
||||||
|
bool get isEditing => this._isEditing;
|
||||||
|
|
||||||
|
/// Gets the number of files.
|
||||||
|
int get numberOfFiles => this.allFiles.length;
|
||||||
|
}
|
||||||
139
lib/base/code_editor/EditorModelStyleOptions.dart
Normal file
139
lib/base/code_editor/EditorModelStyleOptions.dart
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Set the style of CodeEditor.
|
||||||
|
/// You have to use it in EditorModel() just like this :
|
||||||
|
/// ```
|
||||||
|
/// EditorModel model = new EditorModel(
|
||||||
|
/// files, // My files...
|
||||||
|
/// styleOptions: new EditorModelStyleOptions(
|
||||||
|
/// fontSize: 13, // Example
|
||||||
|
/// ),
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
/// An EditorModel instance has the default values of EditorModelStyleOptions.
|
||||||
|
class EditorModelStyleOptions {
|
||||||
|
/// Set the padding of the file's content. By default `15.0`.
|
||||||
|
final EdgeInsets padding;
|
||||||
|
|
||||||
|
/// Set the height of the container. By default `300`.
|
||||||
|
final double heightOfContainer;
|
||||||
|
|
||||||
|
/// Set the theme of the syntax. code_editor has its own theme.
|
||||||
|
/// You can create your own or use others themes by looking at :
|
||||||
|
/// `import 'package:flutter_highlight/themes/'`.
|
||||||
|
final Map<String, TextStyle> theme;
|
||||||
|
|
||||||
|
/// Set the font family of the entire editor. By default `"monospace"`.
|
||||||
|
final String fontFamily;
|
||||||
|
|
||||||
|
/// Set the letter spacing property of the text. By default `null`.
|
||||||
|
final double? letterSpacing;
|
||||||
|
|
||||||
|
/// Set the fontSize of the file's content. By default `15`.
|
||||||
|
final double fontSize;
|
||||||
|
|
||||||
|
/// Set the height (line-height in CSS) property of the text. By default `1.6`.
|
||||||
|
final double lineHeight;
|
||||||
|
|
||||||
|
/// Set the size of the tabulation. By default `2`. Do not use a too big number.
|
||||||
|
final int tabSize;
|
||||||
|
|
||||||
|
/// Set the background color of the editor. By default `Color(0xff2E3152)`.
|
||||||
|
final Color editorColor;
|
||||||
|
|
||||||
|
/// Set the color of the borders between the navigation bar and the content.
|
||||||
|
/// By default `Color(0xFF3E416E)`.
|
||||||
|
final Color editorBorderColor;
|
||||||
|
|
||||||
|
/// Set the color of the file name in the navigation bar. By default `Color(0xFF6CD07A)`.
|
||||||
|
final Color editorFilenameColor;
|
||||||
|
final Color editorNormalFilenameColor;
|
||||||
|
|
||||||
|
/// Set the color property of the edit button. By default `Color(0xFF4650c7)`.
|
||||||
|
final Color editorToolButtonColor;
|
||||||
|
|
||||||
|
/// Set the color of the edit button's text. By default `Color(0xFF4650c7)`.
|
||||||
|
final Color editorToolButtonTextColor;
|
||||||
|
|
||||||
|
/// Set the font size of the file's name in the navigation bar.
|
||||||
|
final double? fontSizeOfFilename;
|
||||||
|
|
||||||
|
/// Set the textStyle of the text field. By default :
|
||||||
|
/// ```
|
||||||
|
/// TextStyle(
|
||||||
|
/// color: Colors.black87,
|
||||||
|
/// fontSize: 16,
|
||||||
|
/// letterSpacing: 1.25,
|
||||||
|
/// fontWeight: FontWeight.w500,
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
final TextStyle textStyleOfTextField;
|
||||||
|
|
||||||
|
/// The background color of the button "Edit".
|
||||||
|
/// By default `Color(0xFFEEEEEE)`.
|
||||||
|
final Color editButtonBackgroundColor;
|
||||||
|
|
||||||
|
/// The text color fo the button "Edit".
|
||||||
|
/// By default `Colors.black`.
|
||||||
|
final Color editButtonTextColor;
|
||||||
|
|
||||||
|
/// The name of the "Edit" button.
|
||||||
|
/// By default `Edit`.
|
||||||
|
final String editButtonName;
|
||||||
|
|
||||||
|
final ToolbarOptions toolbarOptions;
|
||||||
|
final bool placeCursorAtTheEndOnEdit;
|
||||||
|
|
||||||
|
static const Color defaultColorEditor = Color(0xff2E3152);
|
||||||
|
static const Color defaultColorBorder = Color(0xFF3E416E);
|
||||||
|
static const Color defaultColorFileName = Color(0xFF6CD07A);
|
||||||
|
static const Color defaultToolButtonColor = Color(0xFF4650c7);
|
||||||
|
static const Color defaultEditBackgroundColor = Color(0xFFEEEEEE);
|
||||||
|
|
||||||
|
EditorModelStyleOptions(
|
||||||
|
{this.padding = const EdgeInsets.all(15.0),
|
||||||
|
this.heightOfContainer = 300,
|
||||||
|
required this.theme,
|
||||||
|
this.fontFamily = "monospace",
|
||||||
|
this.letterSpacing,
|
||||||
|
this.fontSize = 15,
|
||||||
|
this.lineHeight = 1.6,
|
||||||
|
this.tabSize = 2,
|
||||||
|
this.editorColor = defaultColorEditor,
|
||||||
|
this.editorBorderColor = defaultColorBorder,
|
||||||
|
this.editorFilenameColor = defaultColorFileName,
|
||||||
|
this.editorToolButtonColor = defaultToolButtonColor,
|
||||||
|
this.editorToolButtonTextColor = Colors.white,
|
||||||
|
this.editButtonBackgroundColor = defaultEditBackgroundColor,
|
||||||
|
this.editButtonTextColor = Colors.black,
|
||||||
|
this.editButtonName = "Edit",
|
||||||
|
this.editorNormalFilenameColor = Colors.black,
|
||||||
|
this.fontSizeOfFilename,
|
||||||
|
this.textStyleOfTextField = const TextStyle(
|
||||||
|
color: Colors.black87,
|
||||||
|
fontSize: 16,
|
||||||
|
letterSpacing: 1.25,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
this.toolbarOptions = const ToolbarOptions(),
|
||||||
|
this.placeCursorAtTheEndOnEdit = true});
|
||||||
|
|
||||||
|
double? editButtonPosTop; // this will become 50 while editing because of the toolbar's height
|
||||||
|
double? editButtonPosLeft;
|
||||||
|
double? editButtonPosBottom = 10;
|
||||||
|
double? editButtonPosRight = 15;
|
||||||
|
|
||||||
|
/// You can change the position of the button "Edit" / "OK".
|
||||||
|
/// By default, `bottom: 10`, `right: 15`.
|
||||||
|
void defineEditButtonPosition({
|
||||||
|
required top,
|
||||||
|
left,
|
||||||
|
bottom,
|
||||||
|
right,
|
||||||
|
}) {
|
||||||
|
this.editButtonPosTop = top;
|
||||||
|
this.editButtonPosLeft = left;
|
||||||
|
this.editButtonPosBottom = bottom;
|
||||||
|
this.editButtonPosRight = right;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
lib/base/code_editor/FileEditor.dart
Normal file
18
lib/base/code_editor/FileEditor.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/// A file in the editor, used by EditorModel [model.code].
|
||||||
|
///
|
||||||
|
/// * [name] is the name of the file shown in the navbar.
|
||||||
|
/// * [language] is the language used by the theme
|
||||||
|
/// * [code] is the content of the file, the code
|
||||||
|
///
|
||||||
|
/// Tip: to simplify writing code in a String,
|
||||||
|
/// write line by line your code in a list List<String> and pass as argument list.join("\n").
|
||||||
|
class FileEditor {
|
||||||
|
late String name;
|
||||||
|
String? language;
|
||||||
|
String? code;
|
||||||
|
FileEditor({String? name, String? language, String? code}) {
|
||||||
|
this.name = name ?? "file.${language ?? 'txt'}";
|
||||||
|
this.language = language ?? "text";
|
||||||
|
this.code = code ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
59
lib/base/code_editor/Theme.dart
Normal file
59
lib/base/code_editor/Theme.dart
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
const FontWeight fontWeight = FontWeight.w400;
|
||||||
|
|
||||||
|
// HTML
|
||||||
|
const Color tagColor = Color(0xFFF79AA5); // tag
|
||||||
|
const Color quoteColor = Color(0xFF6CD07A); // ""
|
||||||
|
|
||||||
|
// CSS
|
||||||
|
const Color attrColor = Color(0xFFCBBA7D); // CSS selectors
|
||||||
|
const Color propertyColor = Color(0xFF8CDCFE); // property
|
||||||
|
const Color idColor = Color(0xFFCBBA7D);
|
||||||
|
const Color classColor = Color(0xFFCBBA7D);
|
||||||
|
|
||||||
|
// JS
|
||||||
|
const Color keywordColor = Color(0xFF3E9CD6); // keywords (function, ...)
|
||||||
|
const Color methodsColor = Color(0xFFDCDC9D); // methods built in
|
||||||
|
const Color titlesColor = Color(0xFFDCDC9D); // titles (function's title)
|
||||||
|
|
||||||
|
/// The theme used by code_editor and created by code_editor. This is the default theme of the editor.
|
||||||
|
///
|
||||||
|
/// You can create your own or use
|
||||||
|
/// others themes by looking at :
|
||||||
|
///
|
||||||
|
/// `import 'package:flutter_highlight/themes/'`.
|
||||||
|
const myTheme = {
|
||||||
|
'root': TextStyle(
|
||||||
|
backgroundColor: Color(0xff2E3152),
|
||||||
|
color: Color(0xffdddddd),
|
||||||
|
),
|
||||||
|
'keyword': TextStyle(color: keywordColor),
|
||||||
|
'params': TextStyle(color: Color(0xffde935f)),
|
||||||
|
'selector-tag': TextStyle(color: attrColor),
|
||||||
|
'selector-id': TextStyle(color: idColor),
|
||||||
|
'selector-class': TextStyle(color: classColor),
|
||||||
|
'regexp': TextStyle(color: Color(0xffcc6666)),
|
||||||
|
'literal': TextStyle(color: Colors.white),
|
||||||
|
'section': TextStyle(color: Colors.white),
|
||||||
|
'link': TextStyle(color: Colors.white),
|
||||||
|
'subst': TextStyle(color: Color(0xffdddddd)),
|
||||||
|
'string': TextStyle(color: quoteColor),
|
||||||
|
'title': TextStyle(color: titlesColor),
|
||||||
|
'name': TextStyle(color: tagColor),
|
||||||
|
'type': TextStyle(color: tagColor),
|
||||||
|
'attribute': TextStyle(color: propertyColor),
|
||||||
|
'symbol': TextStyle(color: tagColor),
|
||||||
|
'bullet': TextStyle(color: tagColor),
|
||||||
|
'built_in': TextStyle(color: methodsColor),
|
||||||
|
'addition': TextStyle(color: tagColor),
|
||||||
|
'variable': TextStyle(color: tagColor),
|
||||||
|
'template-tag': TextStyle(color: tagColor),
|
||||||
|
'template-variable': TextStyle(color: tagColor),
|
||||||
|
'comment': TextStyle(color: Color(0xff777777)),
|
||||||
|
'quote': TextStyle(color: Color(0xff777777)),
|
||||||
|
'deletion': TextStyle(color: Color(0xff777777)),
|
||||||
|
'meta': TextStyle(color: Color(0xff777777)),
|
||||||
|
'emphasis': TextStyle(fontStyle: FontStyle.italic),
|
||||||
|
};
|
||||||
14
lib/base/code_editor/ToolButton.dart
Normal file
14
lib/base/code_editor/ToolButton.dart
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// A button under the navigation bar to help the user write code.
|
||||||
|
class ToolButton {
|
||||||
|
Function press;
|
||||||
|
IconData? icon;
|
||||||
|
String? symbol;
|
||||||
|
|
||||||
|
ToolButton({
|
||||||
|
required this.press,
|
||||||
|
this.icon,
|
||||||
|
this.symbol,
|
||||||
|
});
|
||||||
|
}
|
||||||
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(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import 'package:qinglong_app/base/http/token_interceptor.dart';
|
|||||||
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
|
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
|
||||||
|
|
||||||
import '../../json.jc.dart';
|
import '../../json.jc.dart';
|
||||||
|
import '../../main.dart';
|
||||||
|
|
||||||
class Http {
|
class Http {
|
||||||
static const int NOT_LOGIN = 1000;
|
static const int NOT_LOGIN = 1000;
|
||||||
@@ -31,7 +32,7 @@ class Http {
|
|||||||
|
|
||||||
static void _init() {
|
static void _init() {
|
||||||
if (_dio == null) {
|
if (_dio == null) {
|
||||||
initDioConfig(UserInfoViewModel.getInstance().host!);
|
initDioConfig(getIt<UserInfoViewModel>().host!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:qinglong_app/main.dart';
|
import 'package:qinglong_app/main.dart';
|
||||||
|
|
||||||
|
import '../userinfo_viewmodel.dart';
|
||||||
|
|
||||||
class TokenInterceptor extends Interceptor {
|
class TokenInterceptor extends Interceptor {
|
||||||
@override
|
@override
|
||||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||||
if (userInfoViewModel.token != null) {
|
if (getIt<UserInfoViewModel>().token != null) {
|
||||||
options.headers["Authorization"] = "Bearer " + userInfoViewModel.token!;
|
options.headers["Authorization"] = "Bearer " + getIt<UserInfoViewModel>().token!;
|
||||||
}
|
}
|
||||||
options.queryParameters["t"] = (DateTime.now().millisecondsSinceEpoch~/1000).toString();
|
options.queryParameters["t"] = (DateTime.now().millisecondsSinceEpoch~/1000).toString();
|
||||||
return handler.next(options);
|
return handler.next(options);
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class QlAppBar extends StatelessWidget with PreferredSizeWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return AppBar(
|
return AppBar(
|
||||||
|
elevation: 0,
|
||||||
leading: canBack ? back : null,
|
leading: canBack ? back : null,
|
||||||
automaticallyImplyLeading: canBack,
|
automaticallyImplyLeading: canBack,
|
||||||
title: Text(title),
|
title: Text(title),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:qinglong_app/utils/qinglong_theme.dart';
|
import 'package:qinglong_app/utils/codeeditor_theme.dart';
|
||||||
|
|
||||||
var themeProvider = ChangeNotifierProvider((ref) => ThemeViewModel());
|
var themeProvider = ChangeNotifierProvider((ref) => ThemeViewModel());
|
||||||
const Color _primaryColor = Color(0xFF299343);
|
const Color _primaryColor = Color(0xFF299343);
|
||||||
@@ -25,6 +25,25 @@ class ThemeViewModel extends ChangeNotifier {
|
|||||||
|
|
||||||
ThemeData darkTheme = ThemeData.dark().copyWith(
|
ThemeData darkTheme = ThemeData.dark().copyWith(
|
||||||
primaryColor: const Color(0xffffffff),
|
primaryColor: const Color(0xffffffff),
|
||||||
|
inputDecorationTheme: const InputDecorationTheme(
|
||||||
|
labelStyle: TextStyle(color: _primaryColor),
|
||||||
|
focusedBorder: UnderlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: _primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
tabBarTheme: const TabBarTheme(
|
||||||
|
labelStyle: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
unselectedLabelStyle: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
labelColor: Color(0xffffffff),
|
||||||
|
unselectedLabelColor: Color(0xff999999),
|
||||||
|
|
||||||
|
),
|
||||||
);
|
);
|
||||||
ThemeData lightTheme = ThemeData.light().copyWith(
|
ThemeData lightTheme = ThemeData.light().copyWith(
|
||||||
primaryColor: _primaryColor,
|
primaryColor: _primaryColor,
|
||||||
@@ -51,10 +70,10 @@ ThemeData lightTheme = ThemeData.light().copyWith(
|
|||||||
),
|
),
|
||||||
tabBarTheme: const TabBarTheme(
|
tabBarTheme: const TabBarTheme(
|
||||||
labelStyle: TextStyle(
|
labelStyle: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
unselectedLabelStyle: TextStyle(
|
unselectedLabelStyle: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
labelColor: _primaryColor,
|
labelColor: _primaryColor,
|
||||||
unselectedLabelColor: Color(0xff999999),
|
unselectedLabelColor: Color(0xff999999),
|
||||||
@@ -66,6 +85,7 @@ ThemeData lightTheme = ThemeData.light().copyWith(
|
|||||||
|
|
||||||
abstract class ThemeColors {
|
abstract class ThemeColors {
|
||||||
Color taskTitleColor();
|
Color taskTitleColor();
|
||||||
|
Color descColor();
|
||||||
|
|
||||||
Color searchBarBg();
|
Color searchBarBg();
|
||||||
|
|
||||||
@@ -94,6 +114,11 @@ class LightartThemeColors extends ThemeColors {
|
|||||||
Map<String, TextStyle> codeEditorTheme() {
|
Map<String, TextStyle> codeEditorTheme() {
|
||||||
return qinglongLightTheme;
|
return qinglongLightTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color descColor() {
|
||||||
|
return Color(0xff999999);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DartThemeColors extends ThemeColors {
|
class DartThemeColors extends ThemeColors {
|
||||||
@@ -116,4 +141,9 @@ class DartThemeColors extends ThemeColors {
|
|||||||
Map<String, TextStyle> codeEditorTheme() {
|
Map<String, TextStyle> codeEditorTheme() {
|
||||||
return qinglongDarkTheme;
|
return qinglongDarkTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color descColor() {
|
||||||
|
return Color(0xff999999);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
26
lib/base/ui/empty_widget.dart
Normal file
26
lib/base/ui/empty_widget.dart
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:qinglong_app/base/theme.dart';
|
||||||
|
|
||||||
|
class EmptyWidget extends ConsumerWidget {
|
||||||
|
const EmptyWidget({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context,WidgetRef ref) {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"暂无数据",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color:ref.watch(themeProvider).themeColor.descColor(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,14 +6,7 @@ class UserInfoViewModel {
|
|||||||
String? _token;
|
String? _token;
|
||||||
String? _host = "";
|
String? _host = "";
|
||||||
|
|
||||||
static UserInfoViewModel? _userInfoViewModel;
|
UserInfoViewModel() {
|
||||||
|
|
||||||
static UserInfoViewModel getInstance() {
|
|
||||||
_userInfoViewModel ??= UserInfoViewModel._();
|
|
||||||
return _userInfoViewModel!;
|
|
||||||
}
|
|
||||||
|
|
||||||
UserInfoViewModel._() {
|
|
||||||
String userInfoJson = SpUtil.getString(sp_UserINfo);
|
String userInfoJson = SpUtil.getString(sp_UserINfo);
|
||||||
_host = SpUtil.getString(sp_Host,defValue: "http://49.234.59.95:5700");
|
_host = SpUtil.getString(sp_Host,defValue: "http://49.234.59.95:5700");
|
||||||
if (userInfoJson.isNotEmpty) {
|
if (userInfoJson.isNotEmpty) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:dio_log/overlay_draggable_button.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
import 'package:qinglong_app/base/theme.dart';
|
import 'package:qinglong_app/base/theme.dart';
|
||||||
import 'package:qinglong_app/module/login/login_page.dart';
|
import 'package:qinglong_app/module/login/login_page.dart';
|
||||||
@@ -13,14 +14,16 @@ import 'base/routes.dart';
|
|||||||
import 'base/userinfo_viewmodel.dart';
|
import 'base/userinfo_viewmodel.dart';
|
||||||
import 'module/home/home_page.dart';
|
import 'module/home/home_page.dart';
|
||||||
|
|
||||||
late UserInfoViewModel userInfoViewModel;
|
final getIt = GetIt.instance;
|
||||||
|
var navigatorState = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
var logger = Logger();
|
var logger = Logger();
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await SpUtil.getInstance();
|
await SpUtil.getInstance();
|
||||||
userInfoViewModel = UserInfoViewModel.getInstance();
|
getIt.registerSingleton<UserInfoViewModel>(UserInfoViewModel());
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
ProviderScope(
|
ProviderScope(
|
||||||
overrides: [
|
overrides: [
|
||||||
@@ -36,6 +39,7 @@ void main() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends ConsumerWidget {
|
class MyApp extends ConsumerWidget {
|
||||||
|
|
||||||
const MyApp({Key? key}) : super(key: key);
|
const MyApp({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -46,6 +50,7 @@ class MyApp extends ConsumerWidget {
|
|||||||
FocusScope.of(context).requestFocus(FocusNode());
|
FocusScope.of(context).requestFocus(FocusNode());
|
||||||
},
|
},
|
||||||
child: MaterialApp(
|
child: MaterialApp(
|
||||||
|
navigatorKey: navigatorState,
|
||||||
theme: ref.watch<ThemeViewModel>(themeProvider).currentTheme,
|
theme: ref.watch<ThemeViewModel>(themeProvider).currentTheme,
|
||||||
onGenerateRoute: (setting) {
|
onGenerateRoute: (setting) {
|
||||||
return Routes.generateRoute(setting);
|
return Routes.generateRoute(setting);
|
||||||
@@ -53,7 +58,7 @@ class MyApp extends ConsumerWidget {
|
|||||||
home: Builder(
|
home: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
showDebugBtn(context, btnColor: Colors.blue);
|
showDebugBtn(context, btnColor: Colors.blue);
|
||||||
return userInfoViewModel.isLogined() ? const HomePage() : LoginPage();
|
return getIt<UserInfoViewModel>().isLogined() ? const HomePage() : LoginPage();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// home: LoginPage(),
|
// home: LoginPage(),
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import 'package:code_editor/code_editor.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:qinglong_app/base/common_dialog.dart';
|
import 'package:qinglong_app/base/common_dialog.dart';
|
||||||
import 'package:qinglong_app/base/http/api.dart';
|
import 'package:qinglong_app/base/http/api.dart';
|
||||||
import 'package:qinglong_app/base/http/http.dart';
|
import 'package:qinglong_app/base/http/http.dart';
|
||||||
import 'package:qinglong_app/base/ql_app_bar.dart';
|
import 'package:qinglong_app/base/ql_app_bar.dart';
|
||||||
|
import 'package:qinglong_app/base/theme.dart';
|
||||||
import 'package:qinglong_app/module/config/config_viewmodel.dart';
|
import 'package:qinglong_app/module/config/config_viewmodel.dart';
|
||||||
import 'package:qinglong_app/utils/qinglong_theme.dart';
|
|
||||||
|
|
||||||
class ConfigEditPage extends ConsumerStatefulWidget {
|
class ConfigEditPage extends ConsumerStatefulWidget {
|
||||||
final String content;
|
final String content;
|
||||||
@@ -21,34 +20,23 @@ class ConfigEditPage extends ConsumerStatefulWidget {
|
|||||||
class _ConfigEditPageState extends ConsumerState<ConfigEditPage> {
|
class _ConfigEditPageState extends ConsumerState<ConfigEditPage> {
|
||||||
String? value;
|
String? value;
|
||||||
|
|
||||||
|
late TextEditingController _controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_controller = TextEditingController(text: widget.content);
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
List<FileEditor> files = [
|
|
||||||
FileEditor(
|
|
||||||
name: widget.title,
|
|
||||||
language: "sh",
|
|
||||||
code: widget.content, // [code] needs a string
|
|
||||||
),
|
|
||||||
];
|
|
||||||
EditorModel editMode = EditorModel(
|
|
||||||
files: files,
|
|
||||||
styleOptions: EditorModelStyleOptions(
|
|
||||||
fontSize: 13,
|
|
||||||
heightOfContainer: MediaQuery.of(context).size.height - kToolbarHeight - kBottomNavigationBarHeight - 150,
|
|
||||||
editorBorderColor: Theme.of(context).scaffoldBackgroundColor,
|
|
||||||
editButtonBackgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
|
||||||
editorColor: Theme.of(context).scaffoldBackgroundColor,
|
|
||||||
theme: qinglongLightTheme,
|
|
||||||
),
|
|
||||||
|
|
||||||
);
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: QlAppBar(
|
appBar: QlAppBar(
|
||||||
canBack: true,
|
canBack: true,
|
||||||
backCall: () {
|
backCall: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
title: '编辑文件',
|
title: '编辑${widget.title}',
|
||||||
actions: [
|
actions: [
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
@@ -56,7 +44,7 @@ class _ConfigEditPageState extends ConsumerState<ConfigEditPage> {
|
|||||||
failDialog(context, "请先点击保存");
|
failDialog(context, "请先点击保存");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
HttpResponse<NullResponse> response = await Api.saveFile(widget.title, value!);
|
HttpResponse<NullResponse> response = await Api.saveFile(widget.title, _controller.text);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
ref.read(configProvider).loadContent(widget.title);
|
ref.read(configProvider).loadContent(widget.title);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
@@ -76,13 +64,21 @@ class _ConfigEditPageState extends ConsumerState<ConfigEditPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Container(
|
body: Container(
|
||||||
child: CodeEditor(
|
padding: const EdgeInsets.only(
|
||||||
model: editMode,
|
left: 15,
|
||||||
edit: true,
|
right: 15,
|
||||||
onSubmit: (title, v) {
|
),
|
||||||
value = v;
|
child: SingleChildScrollView(
|
||||||
},
|
child: TextField(
|
||||||
disableNavigationbar: false,
|
focusNode: FocusNode(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: ref.read(themeProvider).themeColor.descColor(),
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
controller: _controller,
|
||||||
|
minLines: 1,
|
||||||
|
maxLines: 100,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:qinglong_app/base/base_state_widget.dart';
|
import 'package:qinglong_app/base/base_state_widget.dart';
|
||||||
import 'package:code_editor/code_editor.dart';
|
import 'package:qinglong_app/base/code_editor/code_editor.dart';
|
||||||
|
import 'package:qinglong_app/base/code_editor/EditorModel.dart';
|
||||||
|
import 'package:qinglong_app/base/code_editor/EditorModelStyleOptions.dart';
|
||||||
|
import 'package:qinglong_app/base/code_editor/FileEditor.dart';
|
||||||
|
import 'package:qinglong_app/base/http/url.dart';
|
||||||
|
import 'package:qinglong_app/base/routes.dart';
|
||||||
import 'package:qinglong_app/base/theme.dart';
|
import 'package:qinglong_app/base/theme.dart';
|
||||||
import 'package:qinglong_app/utils/qinglong_theme.dart';
|
import 'package:qinglong_app/main.dart';
|
||||||
|
|
||||||
import 'config_viewmodel.dart';
|
import 'config_viewmodel.dart';
|
||||||
|
|
||||||
@@ -10,10 +16,12 @@ class ConfigPage extends StatefulWidget {
|
|||||||
const ConfigPage({Key? key}) : super(key: key);
|
const ConfigPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ConfigPageState createState() => _ConfigPageState();
|
ConfigPageState createState() => ConfigPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ConfigPageState extends State<ConfigPage> {
|
class ConfigPageState extends State<ConfigPage> {
|
||||||
|
GlobalKey<CodeEditorState> globalKey = GlobalKey();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BaseStateWidget<ConfigViewModel>(
|
return BaseStateWidget<ConfigViewModel>(
|
||||||
@@ -31,6 +39,7 @@ class _ConfigPageState extends State<ConfigPage> {
|
|||||||
files: files,
|
files: files,
|
||||||
styleOptions: EditorModelStyleOptions(
|
styleOptions: EditorModelStyleOptions(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
|
editorNormalFilenameColor: ref.read(themeProvider).themeColor.descColor(),
|
||||||
heightOfContainer: MediaQuery.of(context).size.height - kToolbarHeight - kBottomNavigationBarHeight - 90,
|
heightOfContainer: MediaQuery.of(context).size.height - kToolbarHeight - kBottomNavigationBarHeight - 90,
|
||||||
editorBorderColor: Theme.of(context).scaffoldBackgroundColor,
|
editorBorderColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
editButtonBackgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
editButtonBackgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
@@ -40,6 +49,7 @@ class _ConfigPageState extends State<ConfigPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
return CodeEditor(
|
return CodeEditor(
|
||||||
|
key: globalKey,
|
||||||
model: editMode,
|
model: editMode,
|
||||||
edit: false,
|
edit: false,
|
||||||
disableNavigationbar: false,
|
disableNavigationbar: false,
|
||||||
@@ -51,4 +61,11 @@ class _ConfigPageState extends State<ConfigPage> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void editMe(WidgetRef ref) {
|
||||||
|
int index = globalKey.currentState?.getCurrentIndex() ?? 0;
|
||||||
|
|
||||||
|
navigatorState.currentState?.pushNamed(Routes.route_ConfigEdit,
|
||||||
|
arguments: {"title": ref.read(configProvider).list[index].title, "content": ref.read(configProvider).content[ref.read(configProvider).list[index].title]});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,19 +9,21 @@ import 'package:qinglong_app/module/env/env_page.dart';
|
|||||||
import 'package:qinglong_app/module/others/other_page.dart';
|
import 'package:qinglong_app/module/others/other_page.dart';
|
||||||
import 'package:qinglong_app/module/task/task_page.dart';
|
import 'package:qinglong_app/module/task/task_page.dart';
|
||||||
|
|
||||||
class HomePage extends StatefulWidget {
|
class HomePage extends ConsumerStatefulWidget {
|
||||||
const HomePage({Key? key}) : super(key: key);
|
const HomePage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_HomePageState createState() => _HomePageState();
|
_HomePageState createState() => _HomePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomePageState extends State<HomePage> {
|
class _HomePageState extends ConsumerState<HomePage> {
|
||||||
int _index = 0;
|
int _index = 0;
|
||||||
String _title = "";
|
String _title = "";
|
||||||
|
|
||||||
List<IndexBean> titles = [];
|
List<IndexBean> titles = [];
|
||||||
|
|
||||||
|
GlobalKey<ConfigPageState> configKey = GlobalKey();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
initTitles();
|
initTitles();
|
||||||
@@ -52,6 +54,20 @@ class _HomePageState extends State<HomePage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
} else if (_index == 2) {
|
||||||
|
actions.add(InkWell(
|
||||||
|
onTap: () {
|
||||||
|
configKey.currentState?.editMe(ref);
|
||||||
|
},
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text("编辑"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
actions.add(
|
actions.add(
|
||||||
@@ -72,11 +88,13 @@ class _HomePageState extends State<HomePage> {
|
|||||||
),
|
),
|
||||||
body: IndexedStack(
|
body: IndexedStack(
|
||||||
index: _index,
|
index: _index,
|
||||||
children: const [
|
children: [
|
||||||
TaskPage(),
|
const TaskPage(),
|
||||||
EnvPage(),
|
const EnvPage(),
|
||||||
ConfigPage(),
|
ConfigPage(
|
||||||
OtherPage(),
|
key: configKey,
|
||||||
|
),
|
||||||
|
const OtherPage(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
bottomNavigationBar: BottomNavigationBar(
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:qinglong_app/base/common_dialog.dart';
|
import 'package:qinglong_app/base/common_dialog.dart';
|
||||||
import 'package:qinglong_app/base/routes.dart';
|
import 'package:qinglong_app/base/routes.dart';
|
||||||
|
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
|
||||||
import 'package:qinglong_app/main.dart';
|
import 'package:qinglong_app/main.dart';
|
||||||
import 'package:qinglong_app/module/login/login_viewmodel.dart';
|
import 'package:qinglong_app/module/login/login_viewmodel.dart';
|
||||||
import 'package:qinglong_app/utils/utils.dart';
|
import 'package:qinglong_app/utils/utils.dart';
|
||||||
@@ -16,7 +17,7 @@ class LoginPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _LoginPageState extends State<LoginPage> {
|
class _LoginPageState extends State<LoginPage> {
|
||||||
final TextEditingController _hostController = TextEditingController(text: userInfoViewModel.host);
|
final TextEditingController _hostController = TextEditingController(text: getIt<UserInfoViewModel>().host);
|
||||||
final TextEditingController _userNameController = TextEditingController();
|
final TextEditingController _userNameController = TextEditingController();
|
||||||
final TextEditingController _passwordController = TextEditingController();
|
final TextEditingController _passwordController = TextEditingController();
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: MediaQuery.of(context).size.height / 6,
|
height: MediaQuery.of(context).size.height / 10,
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 50,
|
height: 50,
|
||||||
@@ -72,7 +73,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
children: [
|
children: [
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 40,
|
left: 0,
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
"assets/images/login_tip.png",
|
"assets/images/login_tip.png",
|
||||||
height: 45,
|
height: 45,
|
||||||
@@ -80,7 +81,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
),
|
),
|
||||||
const Positioned(
|
const Positioned(
|
||||||
top: 10,
|
top: 10,
|
||||||
left: 40,
|
left: 0,
|
||||||
child: Text(
|
child: Text(
|
||||||
"登录",
|
"登录",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -91,7 +92,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 5,
|
top: 5,
|
||||||
right: 40,
|
right: 0,
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
"assets/images/ql.png",
|
"assets/images/ql.png",
|
||||||
height: 45,
|
height: 45,
|
||||||
@@ -197,7 +198,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Utils.hideKeyBoard(context);
|
Utils.hideKeyBoard(context);
|
||||||
userInfoViewModel.updateHost(_hostController.text);
|
getIt<UserInfoViewModel>().updateHost(_hostController.text);
|
||||||
model.login(_userNameController.text, _passwordController.text);
|
model.login(_userNameController.text, _passwordController.text);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:qinglong_app/base/base_viewmodel.dart';
|
|||||||
import 'package:qinglong_app/base/http/api.dart';
|
import 'package:qinglong_app/base/http/api.dart';
|
||||||
import 'package:qinglong_app/base/http/http.dart';
|
import 'package:qinglong_app/base/http/http.dart';
|
||||||
import 'package:qinglong_app/base/http/url.dart';
|
import 'package:qinglong_app/base/http/url.dart';
|
||||||
|
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
|
||||||
import 'package:qinglong_app/main.dart';
|
import 'package:qinglong_app/main.dart';
|
||||||
|
|
||||||
import 'login_bean.dart';
|
import 'login_bean.dart';
|
||||||
@@ -23,7 +24,7 @@ class LoginViewModel extends ViewModel {
|
|||||||
HttpResponse<LoginBean> response = await Api.login(userName, password);
|
HttpResponse<LoginBean> response = await Api.login(userName, password);
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
userInfoViewModel.updateToken(response.bean?.token ?? "");
|
getIt<UserInfoViewModel>().updateToken(response.bean?.token ?? "");
|
||||||
loginSuccess = true;
|
loginSuccess = true;
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:qinglong_app/base/base_state_widget.dart';
|
|||||||
import 'package:qinglong_app/base/routes.dart';
|
import 'package:qinglong_app/base/routes.dart';
|
||||||
import 'package:qinglong_app/base/theme.dart';
|
import 'package:qinglong_app/base/theme.dart';
|
||||||
import 'package:qinglong_app/base/ui/abs_underline_tabindicator.dart';
|
import 'package:qinglong_app/base/ui/abs_underline_tabindicator.dart';
|
||||||
|
import 'package:qinglong_app/base/ui/empty_widget.dart';
|
||||||
import 'package:qinglong_app/base/ui/menu.dart';
|
import 'package:qinglong_app/base/ui/menu.dart';
|
||||||
import 'package:qinglong_app/module/task/intime_log/intime_log_page.dart';
|
import 'package:qinglong_app/module/task/intime_log/intime_log_page.dart';
|
||||||
import 'package:qinglong_app/module/task/task_bean.dart';
|
import 'package:qinglong_app/module/task/task_bean.dart';
|
||||||
@@ -83,23 +84,21 @@ class _TaskPageState extends State<TaskPage> {
|
|||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
return model.loadData(false);
|
return model.loadData(false);
|
||||||
},
|
},
|
||||||
child: ListView.builder(
|
child: list.isEmpty
|
||||||
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
? const EmptyWidget()
|
||||||
itemBuilder: (context, index) {
|
: ListView.builder(
|
||||||
// if (index == 0) {
|
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
||||||
// return searchCell(ref);
|
itemBuilder: (context, index) {
|
||||||
// }
|
TaskBean item = list[index];
|
||||||
|
|
||||||
TaskBean item = list[index];
|
if ((item.name == null || item.name!.contains(_searchKey ?? "")) || (item.command == null || item.command!.contains(_searchKey ?? ""))) {
|
||||||
|
return TaskItemCell(item, ref);
|
||||||
if ((item.name == null || item.name!.contains(_searchKey ?? "")) || (item.command == null || item.command!.contains(_searchKey ?? ""))) {
|
} else {
|
||||||
return TaskItemCell(item, ref);
|
return const SizedBox.shrink();
|
||||||
} else {
|
}
|
||||||
return const SizedBox.shrink();
|
},
|
||||||
}
|
itemCount: list.length,
|
||||||
},
|
),
|
||||||
itemCount: list.length,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,13 +238,13 @@ class TaskItemCell extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Material(
|
Material(
|
||||||
color:Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: Text(
|
child: Text(
|
||||||
bean.name ?? "",
|
bean.name ?? "",
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
color: bean.isDisabled == 1 ? Color(0xffF85152) : ref.watch(themeProvider).themeColor.taskTitleColor(),
|
color: bean.isDisabled == 1 ? const Color(0xffF85152) : ref.watch(themeProvider).themeColor.taskTitleColor(),
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -264,13 +263,13 @@ class TaskItemCell extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Material(
|
Material(
|
||||||
color:Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: Text(
|
child: Text(
|
||||||
(bean.lastExecutionTime == null || bean.lastExecutionTime == 0) ? "-" : Utils.formatMessageTime(bean.lastExecutionTime!),
|
(bean.lastExecutionTime == null || bean.lastExecutionTime == 0) ? "-" : Utils.formatMessageTime(bean.lastExecutionTime!),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
color: Color(0xff999999),
|
color: ref.watch(themeProvider).themeColor.descColor(),
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -281,13 +280,13 @@ class TaskItemCell extends StatelessWidget {
|
|||||||
height: 8,
|
height: 8,
|
||||||
),
|
),
|
||||||
Material(
|
Material(
|
||||||
color:Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: Text(
|
child: Text(
|
||||||
bean.schedule ?? "",
|
bean.schedule ?? "",
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
color: Color(0xff999999),
|
color: ref.watch(themeProvider).themeColor.descColor(),
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ class TaskViewModel extends BaseViewModel {
|
|||||||
list.sort((a, b) {
|
list.sort((a, b) {
|
||||||
return b.created!.compareTo(a.created!);
|
return b.created!.compareTo(a.created!);
|
||||||
});
|
});
|
||||||
|
list.sort((a, b) {
|
||||||
|
return b.isPinned!.compareTo(a.isPinned!);
|
||||||
|
});
|
||||||
running.clear();
|
running.clear();
|
||||||
running.addAll(list.where((element) => element.status == 0));
|
running.addAll(list.where((element) => element.status == 0));
|
||||||
disabled.clear();
|
disabled.clear();
|
||||||
|
|||||||
23
pubspec.lock
23
pubspec.lock
@@ -155,13 +155,6 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.0"
|
version: "4.1.0"
|
||||||
code_editor:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: code_editor
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "1.3.1"
|
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -252,7 +245,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.1"
|
version: "0.1.1"
|
||||||
flutter_highlight:
|
flutter_highlight:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_highlight
|
name: flutter_highlight
|
||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
@@ -303,13 +296,6 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
font_awesome_flutter:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: font_awesome_flutter
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "9.2.0"
|
|
||||||
frontend_server_client:
|
frontend_server_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -317,6 +303,13 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
|
get_it:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: get_it
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "7.2.0"
|
||||||
glob:
|
glob:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ dependencies:
|
|||||||
json_conversion_annotation: ^0.0.4
|
json_conversion_annotation: ^0.0.4
|
||||||
logger: ^1.1.0
|
logger: ^1.1.0
|
||||||
intl: ^0.17.0
|
intl: ^0.17.0
|
||||||
code_editor: ^1.3.1
|
get_it: ^7.2.0
|
||||||
|
flutter_highlight: ^0.7.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_native_splash: ^1.3.3
|
flutter_native_splash: ^1.3.3
|
||||||
|
|||||||
Reference in New Issue
Block a user