diff --git a/lib/base/code_editor/EditorModel.dart b/lib/base/code_editor/EditorModel.dart deleted file mode 100644 index 1a1a663..0000000 --- a/lib/base/code_editor/EditorModel.dart +++ /dev/null @@ -1,101 +0,0 @@ - -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 _languages; - late List allFiles; - - /// Define the required parameters for the editor to work properly. - /// For that, you need to define [files] wich is a `List`. - /// - /// You can also define your own preferences with [styleOptions]. - EditorModel({required List 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 of the files' content that uses [language]. - List getCodeWithLanguage(String language) { - List 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 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; -} diff --git a/lib/base/code_editor/EditorModelStyleOptions.dart b/lib/base/code_editor/EditorModelStyleOptions.dart deleted file mode 100644 index b4ab02b..0000000 --- a/lib/base/code_editor/EditorModelStyleOptions.dart +++ /dev/null @@ -1,139 +0,0 @@ -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 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; - } -} diff --git a/lib/base/code_editor/FileEditor.dart b/lib/base/code_editor/FileEditor.dart deleted file mode 100644 index 83ba4ed..0000000 --- a/lib/base/code_editor/FileEditor.dart +++ /dev/null @@ -1,18 +0,0 @@ -/// 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 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 ?? ""; - } -} diff --git a/lib/base/code_editor/Theme.dart b/lib/base/code_editor/Theme.dart deleted file mode 100644 index 54c9d4a..0000000 --- a/lib/base/code_editor/Theme.dart +++ /dev/null @@ -1,59 +0,0 @@ - -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), -}; diff --git a/lib/base/code_editor/ToolButton.dart b/lib/base/code_editor/ToolButton.dart deleted file mode 100644 index 9347884..0000000 --- a/lib/base/code_editor/ToolButton.dart +++ /dev/null @@ -1,14 +0,0 @@ -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, - }); -} diff --git a/lib/base/code_editor/code_editor.dart b/lib/base/code_editor/code_editor.dart deleted file mode 100644 index 94618f0..0000000 --- a/lib/base/code_editor/code_editor.dart +++ /dev/null @@ -1,284 +0,0 @@ -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 { - /// 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 editableTextKey = GlobalKey(); - - @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: [ - 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: [ - 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: [ - disableNavigationbar ? SizedBox.shrink() : buildNavbar(), - buildContentEditor(), - ], - ); - } -} diff --git a/lib/module/config/config_page.dart b/lib/module/config/config_page.dart index 3271238..f81fec7 100644 --- a/lib/module/config/config_page.dart +++ b/lib/module/config/config_page.dart @@ -1,13 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:flutter_highlight/flutter_highlight.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:qinglong_app/base/base_state_widget.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/ui/abs_underline_tabindicator.dart'; import 'package:qinglong_app/main.dart'; import 'config_viewmodel.dart'; @@ -19,40 +16,56 @@ class ConfigPage extends StatefulWidget { ConfigPageState createState() => ConfigPageState(); } -class ConfigPageState extends State { - GlobalKey globalKey = GlobalKey(); +class ConfigPageState extends State with SingleTickerProviderStateMixin { + TabController? _tabController; + + @override + void initState() { + super.initState(); + } @override Widget build(BuildContext context) { return BaseStateWidget( builder: (ref, model, child) { - List files = model.list - .map( - (e) => FileEditor( - name: e.title, - language: "sh", - code: model.content[e.title], // [code] needs a string + _tabController ??= TabController(length: model.list.length, vsync: this); + + return Column( + children: [ + TabBar( + controller: _tabController, + tabs: model.list + .map((e) => Tab( + text: e.title, + )) + .toList(), + isScrollable: true, + indicator: AbsUnderlineTabIndicator( + wantWidth: 20, + borderSide: BorderSide( + color: Theme.of(context).primaryColor, + width: 2, + ), ), - ) - .toList(); - EditorModel editMode = EditorModel( - files: files, - styleOptions: EditorModelStyleOptions( - fontSize: 13, - editorNormalFilenameColor: ref.read(themeProvider).themeColor.descColor(), - heightOfContainer: MediaQuery.of(context).size.height - kToolbarHeight - kBottomNavigationBarHeight - 90, - editorBorderColor: Theme.of(context).scaffoldBackgroundColor, - editButtonBackgroundColor: Theme.of(context).scaffoldBackgroundColor, - editorColor: Theme.of(context).scaffoldBackgroundColor, - editorFilenameColor: Theme.of(context).primaryColor, - theme: ref.watch(themeProvider).themeColor.codeEditorTheme(), - ), - ); - return CodeEditor( - key: globalKey, - model: editMode, - edit: false, - disableNavigationbar: false, + ), + Expanded( + child: TabBarView( + controller: _tabController, + children: model.list + .map( + (e) => SingleChildScrollView( + child: HighlightView( + model.content[e.title] ?? "", + language: "sh", + theme: ref.watch(themeProvider).themeColor.codeEditorTheme(), + tabSize: 14, + ), + ), + ) + .toList(), + ), + ), + ], ); }, model: configProvider, @@ -63,9 +76,10 @@ class ConfigPageState extends State { } 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]}); + if (_tabController == null || _tabController!.length == 0) return; + navigatorState.currentState?.pushNamed(Routes.route_ConfigEdit, arguments: { + "title": ref.read(configProvider).list[_tabController?.index ?? 0].title, + "content": ref.read(configProvider).content[ref.read(configProvider).list[_tabController?.index ?? 0].title] + }); } }