新增代码文件支持行号显示

This commit is contained in:
jyuesong
2022-06-15 11:05:49 +08:00
parent 4e3f8a0df9
commit 2005083d2e
117 changed files with 4625 additions and 136 deletions

View File

@@ -1,12 +1,16 @@
import 'package:code_text_field/code_text_field.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:highlight/languages/powershell.dart';
import 'package:qinglong_app/base/http/api.dart';
import 'package:qinglong_app/base/http/http.dart';
import 'package:qinglong_app/base/ql_app_bar.dart';
import 'package:qinglong_app/base/sp_const.dart';
import 'package:qinglong_app/base/theme.dart';
import 'package:qinglong_app/utils/extension.dart';
import 'package:qinglong_app/utils/sp_utils.dart';
class ConfigEditPage extends ConsumerStatefulWidget {
final String content;
@@ -21,6 +25,8 @@ class ConfigEditPage extends ConsumerStatefulWidget {
class _ConfigEditPageState extends ConsumerState<ConfigEditPage> {
CodeController? _codeController;
late String result;
late String preResult;
List<String> operateList = [];
@override
void dispose() {
@@ -31,18 +37,23 @@ class _ConfigEditPageState extends ConsumerState<ConfigEditPage> {
@override
void initState() {
result = widget.content;
preResult = widget.content;
super.initState();
generateOperateList();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
focusNode.requestFocus();
checkClipBoard();
});
}
Future<void> notifyICloud(BuildContext context, String? title, String? content) async {
}
@override
Widget build(BuildContext context) {
_codeController ??= CodeController(
text: widget.content,
text: result,
language: powershell,
onChange: (value) {
result = value;
@@ -56,13 +67,78 @@ class _ConfigEditPageState extends ConsumerState<ConfigEditPage> {
appBar: QlAppBar(
canBack: true,
backCall: () {
Navigator.of(context).pop();
FocusManager.instance.primaryFocus?.unfocus();
if (preResult == result) {
Navigator.of(context).pop();
} else {
showCupertinoDialog(
context: context,
useRootNavigator: false,
builder: (childContext) => CupertinoAlertDialog(
title: const Text("温馨提示"),
content: const Text("你编辑的内容还没用提交,确定退出吗?"),
actions: [
CupertinoDialogAction(
child: const Text(
"取消",
style: TextStyle(
color: Color(0xff999999),
),
),
onPressed: () {
Navigator.of(childContext).pop();
},
),
CupertinoDialogAction(
child: Text(
"确定",
style: TextStyle(
color: ref.watch(themeProvider).primaryColor,
),
),
onPressed: () {
Navigator.of(childContext).pop();
Navigator.of(context).pop();
},
),
],
),
);
}
},
title: '编辑${widget.title}',
actions: [
const SizedBox(
width: 15,
),
Material(
color: Colors.transparent,
child: PopupMenuButton<String>(
onSelected: (String result) {
updateValueBykey(result);
},
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
...operateList
.map(
(e) => PopupMenuItem<String>(
child: Text(e),
value: e,
),
)
.toList(),
],
child: const Center(
child: Icon(
CupertinoIcons.arrow_up_right_diamond,
),
),
),
),
InkWell(
onTap: () async {
HttpResponse<NullResponse> response = await Api.saveFile(widget.title, result);
await notifyICloud(context, widget.title, result);
if (response.success) {
"提交成功".toast();
Navigator.of(context).pop(widget.title);
@@ -89,23 +165,176 @@ class _ConfigEditPageState extends ConsumerState<ConfigEditPage> {
),
body: SafeArea(
top: false,
child: CodeField(
controller: _codeController!,
expands: true,
wrap: true,
lineNumberStyle: const LineNumberStyle(
width: 0,
margin: 0,
textStyle: TextStyle(
color: Colors.transparent,
fontSize: 0,
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: SpUtil.getBool(spShowLine, defValue: false) ? 0 : 10,
),
child: CodeField(
controller: _codeController!,
expands: true,
background: Colors.white,
wrap: SpUtil.getBool(spShowLine, defValue: false) ? false : true,
hideColumn: !SpUtil.getBool(spShowLine, defValue: false),
lineNumberStyle: LineNumberStyle(
textStyle: TextStyle(
color: ref.watch(themeProvider).themeColor.descColor(),
fontSize: 12,
),
),
),
background: ref.watch(themeProvider).themeColor.settingBgColor(),
),
),
);
}
FocusNode focusNode = FocusNode();
void generateOperateList() {
operateList.clear();
List<String> array = result.split("\n");
for (String a in array) {
String t = a.replaceAll(" ", "");
if (t.trim().startsWith("export")) {
int i = t.indexOf("export") + 6;
int j = t.indexOf("=");
operateList.add(t.substring(i, j));
}
}
}
void updateValueBykey(String key) async {
String defaultValue = "";
try {
var clipBoard = await Clipboard.getData(Clipboard.kTextPlain);
if (clipBoard != null && clipBoard.text != null) {
String tempText = clipBoard.text!;
if (tempText.trim().contains("export")) {
int i = tempText.trim().indexOf("\"");
int j = tempText.trim().lastIndexOf("\"");
if (i == -1 || j == -1) {
i = tempText.trim().indexOf("'");
j = tempText.trim().lastIndexOf("'");
}
defaultValue = tempText.trim().substring(i, j);
} else {
defaultValue = tempText;
}
}
} catch (e) {}
TextEditingController controller = TextEditingController(text: defaultValue.replaceAll("\"", "").replaceAll("'", ""));
showCupertinoDialog(
useRootNavigator: false,
context: context,
builder: (context) => CupertinoAlertDialog(
title: Text("编辑$key:"),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(
vertical: 10,
),
child: TextField(
controller: controller,
decoration: const InputDecoration(
isDense: true,
contentPadding: EdgeInsets.all(4),
hintText: "请输入值",
hintStyle: TextStyle(
fontSize: 14,
),
),
autofocus: false,
),
),
],
),
actions: [
CupertinoDialogAction(
child: const Text(
"取消",
style: TextStyle(
color: Color(0xff999999),
),
),
onPressed: () {
Navigator.of(context).pop();
},
),
CupertinoDialogAction(
child: Text(
"确定",
style: TextStyle(
color: ref.watch(themeProvider).primaryColor,
),
),
onPressed: () async {
Navigator.of(context).pop();
updateValueByKey(key, controller.text);
},
),
],
),
);
}
void updateValueByKey(String key, String text) {
List<String> array = result.split("\n");
for (String a in array) {
String t = a.replaceAll(" ", "");
if (t.trim().startsWith("export")) {
int i = t.indexOf("export") + 6;
int j = t.indexOf("=");
String tempResult = t.substring(i, j);
if (tempResult == key) {
result = result.replaceAll(a, "\nexport $key = \"$text\" \n\n");
break;
}
}
}
_codeController = null;
setState(() {});
"已修改".toast();
}
void checkClipBoard() async {
try {
String key = "";
String value = "";
var clipBoard = await Clipboard.getData(Clipboard.kTextPlain);
if (clipBoard != null && clipBoard.text != null) {
String tempText = clipBoard.text!;
if (tempText.trim().contains("export")) {
int kI = tempText.trim().indexOf("export");
int kJ = tempText.trim().indexOf("=");
key = tempText.trim().substring(kI + 6, kJ);
int i = tempText.trim().indexOf("\"");
int j = tempText.trim().lastIndexOf("\"");
if (i == -1 || j == -1) {
i = tempText.trim().indexOf("'");
j = tempText.trim().lastIndexOf("'");
}
value = tempText.trim().substring(i, j);
if (key.isNotEmpty && result.contains(key) && value.isNotEmpty) {
WidgetsBinding.instance.endOfFrame.then((value) {
updateValueBykey(key);
});
}
}
}
} catch (e) {}
}
}

View File

@@ -1,13 +1,18 @@
import 'dart:ui';
import 'package:code_text_field/code_text_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:highlight/languages/powershell.dart';
import 'package:qinglong_app/base/base_state_widget.dart';
import 'package:qinglong_app/base/routes.dart';
import 'package:qinglong_app/base/sp_const.dart';
import 'package:qinglong_app/base/theme.dart';
import 'package:qinglong_app/base/ui/abs_underline_tabindicator.dart';
import 'package:qinglong_app/base/ui/empty_widget.dart';
import 'package:qinglong_app/main.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:qinglong_app/utils/sp_utils.dart';
import '../../base/ui/syntax_highlighter.dart';
import 'config_viewmodel.dart';
@@ -19,8 +24,7 @@ class ConfigPage extends StatefulWidget {
ConfigPageState createState() => ConfigPageState();
}
class ConfigPageState extends State<ConfigPage>
with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
class ConfigPageState extends State<ConfigPage> with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
int _initIndex = 0;
BuildContext? childContext;
@@ -85,14 +89,8 @@ class ConfigPageState extends State<ConfigPage>
void editMe(WidgetRef ref) {
if (childContext == null) return;
navigatorState.currentState?.pushNamed(Routes.routeConfigEdit, arguments: {
"title": ref
.read(configProvider)
.list[DefaultTabController.of(childContext!)?.index ?? 0]
.title,
"content": ref.read(configProvider).content[ref
.read(configProvider)
.list[DefaultTabController.of(childContext!)?.index ?? 0]
.title]
"title": ref.read(configProvider).list[DefaultTabController.of(childContext!)?.index ?? 0].title,
"content": ref.read(configProvider).content[ref.read(configProvider).list[DefaultTabController.of(childContext!)?.index ?? 0].title]
}).then((value) async {
if (value != null && (value as String).isNotEmpty) {
await ref.read(configProvider).loadContent(value);
@@ -105,7 +103,7 @@ class ConfigPageState extends State<ConfigPage>
bool get wantKeepAlive => true;
}
class CodeWidget extends StatefulWidget {
class CodeWidget extends ConsumerStatefulWidget {
final String content;
const CodeWidget({
@@ -114,30 +112,61 @@ class CodeWidget extends StatefulWidget {
}) : super(key: key);
@override
State<CodeWidget> createState() => _CodeWidgetState();
ConsumerState<CodeWidget> createState() => _CodeWidgetState();
}
class _CodeWidgetState extends State<CodeWidget>
with AutomaticKeepAliveClientMixin {
class _CodeWidgetState extends ConsumerState<CodeWidget> with AutomaticKeepAliveClientMixin {
CodeController? _codeController;
@override
void dispose() {
_codeController?.dispose();
super.dispose();
}
String result = "";
@override
void initState() {
result = widget.content;
super.initState();
}
@override
Widget build(BuildContext context) {
super.build(context);
return SelectableText.rich(
TextSpan(
style: GoogleFonts.droidSansMono(fontSize: 14).apply(
fontSizeFactor: 1,
_codeController ??= CodeController(
text: result,
language: powershell,
onChange: (value) {
result = value;
},
theme: ref.watch(themeProvider).themeColor.codeEditorTheme(),
stringMap: {
"export": const TextStyle(fontWeight: FontWeight.normal, color: Color(0xff6B2375)),
},
);
return SafeArea(
top: false,
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: SpUtil.getBool(spShowLine, defValue: false) ? 0 : 10,
),
children: <TextSpan>[
DartSyntaxHighlighter(SyntaxHighlighterStyle.lightThemeStyle())
.format(widget.content)
],
),
style: DefaultTextStyle.of(context).style.apply(
fontSizeFactor: 1,
child: CodeField(
controller: _codeController!,
expands: true,
readOnly: true,
background: Colors.white,
wrap: SpUtil.getBool(spShowLine, defValue: false) ? false : true,
hideColumn: !SpUtil.getBool(spShowLine, defValue: false),
lineNumberStyle: LineNumberStyle(
textStyle: TextStyle(
color: ref.watch(themeProvider).themeColor.descColor(),
fontSize: 12,
),
),
selectionWidthStyle: BoxWidthStyle.max,
selectionHeightStyle: BoxHeightStyle.max,
autofocus: true,
),
),
);
}