add function

This commit is contained in:
jyuesong
2022-01-17 14:21:41 +08:00
parent 96b9d720a5
commit c7b98a04db
23 changed files with 807 additions and 105 deletions

View 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;
}

View 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;
}
}

View 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 ?? "";
}
}

View 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),
};

View 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,
});
}

View 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(),
],
);
}
}

View File

@@ -8,6 +8,7 @@ import 'package:qinglong_app/base/http/token_interceptor.dart';
import 'package:qinglong_app/base/userinfo_viewmodel.dart';
import '../../json.jc.dart';
import '../../main.dart';
class Http {
static const int NOT_LOGIN = 1000;
@@ -31,7 +32,7 @@ class Http {
static void _init() {
if (_dio == null) {
initDioConfig(UserInfoViewModel.getInstance().host!);
initDioConfig(getIt<UserInfoViewModel>().host!);
}
}

View File

@@ -1,11 +1,13 @@
import 'package:dio/dio.dart';
import 'package:qinglong_app/main.dart';
import '../userinfo_viewmodel.dart';
class TokenInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
if (userInfoViewModel.token != null) {
options.headers["Authorization"] = "Bearer " + userInfoViewModel.token!;
if (getIt<UserInfoViewModel>().token != null) {
options.headers["Authorization"] = "Bearer " + getIt<UserInfoViewModel>().token!;
}
options.queryParameters["t"] = (DateTime.now().millisecondsSinceEpoch~/1000).toString();
return handler.next(options);

View File

@@ -35,6 +35,7 @@ class QlAppBar extends StatelessWidget with PreferredSizeWidget {
);
return AppBar(
elevation: 0,
leading: canBack ? back : null,
automaticallyImplyLeading: canBack,
title: Text(title),

View File

@@ -1,7 +1,7 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.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());
const Color _primaryColor = Color(0xFF299343);
@@ -25,6 +25,25 @@ class ThemeViewModel extends ChangeNotifier {
ThemeData darkTheme = ThemeData.dark().copyWith(
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(
primaryColor: _primaryColor,
@@ -51,10 +70,10 @@ ThemeData lightTheme = ThemeData.light().copyWith(
),
tabBarTheme: const TabBarTheme(
labelStyle: TextStyle(
fontSize: 16,
fontSize: 14,
),
unselectedLabelStyle: TextStyle(
fontSize: 16,
fontSize: 14,
),
labelColor: _primaryColor,
unselectedLabelColor: Color(0xff999999),
@@ -66,6 +85,7 @@ ThemeData lightTheme = ThemeData.light().copyWith(
abstract class ThemeColors {
Color taskTitleColor();
Color descColor();
Color searchBarBg();
@@ -94,6 +114,11 @@ class LightartThemeColors extends ThemeColors {
Map<String, TextStyle> codeEditorTheme() {
return qinglongLightTheme;
}
@override
Color descColor() {
return Color(0xff999999);
}
}
class DartThemeColors extends ThemeColors {
@@ -116,4 +141,9 @@ class DartThemeColors extends ThemeColors {
Map<String, TextStyle> codeEditorTheme() {
return qinglongDarkTheme;
}
@override
Color descColor() {
return Color(0xff999999);
}
}

View 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(),
),
),
],
),
);
}
}

View File

@@ -6,14 +6,7 @@ class UserInfoViewModel {
String? _token;
String? _host = "";
static UserInfoViewModel? _userInfoViewModel;
static UserInfoViewModel getInstance() {
_userInfoViewModel ??= UserInfoViewModel._();
return _userInfoViewModel!;
}
UserInfoViewModel._() {
UserInfoViewModel() {
String userInfoJson = SpUtil.getString(sp_UserINfo);
_host = SpUtil.getString(sp_Host,defValue: "http://49.234.59.95:5700");
if (userInfoJson.isNotEmpty) {