自定义线路与线路优化

This commit is contained in:
2026-06-04 17:52:16 +08:00
parent 33509de4e3
commit e6c60a9d6d
16 changed files with 1184 additions and 203 deletions
@@ -8,6 +8,7 @@ import 'package:highlight/highlight.dart';
import 'package:highlight/languages/all.dart';
import 'package:http/http.dart' as http;
import '../../../data/models/file_model.dart';
import '../../../services/custom_line_service.dart';
import '../../../services/file_service.dart';
import '../../../core/utils/file_type_utils.dart';
@@ -62,6 +63,7 @@ class _DocumentPreviewPageState extends State<DocumentPreviewPage> {
if (urls.isEmpty) throw Exception('获取URL为空');
final urlData = urls[0] as Map<String, dynamic>;
final url = urlData['url'] as String;
await CustomLineService.instance.activateSelectedNodeForUrl(url);
final responseContent = await http.get(Uri.parse(url));
if (responseContent.statusCode != 200) {
@@ -93,7 +95,8 @@ class _DocumentPreviewPageState extends State<DocumentPreviewPage> {
_codeController = CodeController(text: _content, language: _languageMode);
}
int _countLines(String text) => text.isEmpty ? 0 : '\n'.allMatches(text).length + 1;
int _countLines(String text) =>
text.isEmpty ? 0 : '\n'.allMatches(text).length + 1;
Mode _detectLanguageMode(String fileName) {
final ext = FileTypeUtils.getExtension(fileName);
@@ -109,7 +112,7 @@ class _DocumentPreviewPageState extends State<DocumentPreviewPage> {
@override
Widget build(BuildContext context) {
final double screenWidth = MediaQuery.of(context).size.width;
// 自动初始化布局逻辑
if (!_hasInitializedLayout && !_isLoading) {
_showLineNumbers = screenWidth >= 600;
@@ -122,7 +125,7 @@ class _DocumentPreviewPageState extends State<DocumentPreviewPage> {
// 计算行数的位数,并根据当前字体大小分配宽度
int digits = _lineCount.toString().length;
// 这里的 0.7 是字体宽高的约数比例,20 是左右边距预留
lineNumberWidth = (digits * (_fontSize * 0.7)) + 15;
lineNumberWidth = (digits * (_fontSize * 0.7)) + 15;
if (lineNumberWidth < 35) lineNumberWidth = 35;
}
@@ -131,16 +134,21 @@ class _DocumentPreviewPageState extends State<DocumentPreviewPage> {
appBar: AppBar(
backgroundColor: _backgroundColor,
elevation: 0,
iconTheme: const IconThemeData(
color: Colors.white,
),
iconTheme: const IconThemeData(color: Colors.white),
title: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(widget.file.name, style: const TextStyle(color: Colors.white, fontSize: 15), textAlign: TextAlign.center,),
if (!_isLoading)
Text('$_languageName · $_lineCount 行 · 字号: ${_fontSize.toInt()}',
style: TextStyle(color: Colors.grey.shade400, fontSize: 12), textAlign: TextAlign.center,),
Text(
widget.file.name,
style: const TextStyle(color: Colors.white, fontSize: 15),
textAlign: TextAlign.center,
),
if (!_isLoading)
Text(
'$_languageName · $_lineCount 行 · 字号: ${_fontSize.toInt()}',
style: TextStyle(color: Colors.grey.shade400, fontSize: 12),
textAlign: TextAlign.center,
),
],
),
actions: [
@@ -159,60 +167,64 @@ class _DocumentPreviewPageState extends State<DocumentPreviewPage> {
/// 现在是一次性渲染的, 所以代码行数太多会有性能问题, 渲染会耗时较长, 而且会卡顿
Widget _buildCodeEditor(double lineNumberWidth) {
// 核心改进:根据当前字号,动态计算一个更宽松的行号宽度
// 13号字大概每个数字占 8-9 像素,我们按 10 像素算并加上边距
int digits = _lineCount.toString().length;
// 这里的 12.0 是根据 13-15号字体的平均宽度预留,46 是基础间距 (调试火葬场)
double stableWidth = _showLineNumbers
? (digits * (_fontSize * 0.75)) + 46
: 0;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 2, vertical: 2),
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: _backgroundColor,
borderRadius: BorderRadius.circular(12),
// 核心改进:根据当前字号,动态计算一个更宽松的行号宽度
// 13号字大概每个数字占 8-9 像素,我们按 10 像素算并加上边距
int digits = _lineCount.toString().length;
// 这里的 12.0 是根据 13-15号字体的平均宽度预留,46 是基础间距 (调试火葬场)
double stableWidth = _showLineNumbers
? (digits * (_fontSize * 0.75)) + 46
: 0;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 2, vertical: 2),
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: _backgroundColor,
borderRadius: BorderRadius.circular(12),
),
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
dragDevices: {PointerDeviceKind.touch, PointerDeviceKind.mouse},
),
child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
dragDevices: {PointerDeviceKind.touch, PointerDeviceKind.mouse},
),
child: CodeTheme(
data: CodeThemeData(styles: {...atomOneDarkTheme}),
child: Scrollbar(
child: CodeTheme(
data: CodeThemeData(styles: {...atomOneDarkTheme}),
child: Scrollbar(
controller: _customCodeScrollController,
child: SingleChildScrollView(
controller: _customCodeScrollController,
child: SingleChildScrollView(
controller: _customCodeScrollController,
child: CodeField(
controller: _codeController!,
child: CodeField(
controller: _codeController!,
textStyle: TextStyle(
fontFamily: 'SourceCodePro',
fontSize: _fontSize,
height: 1.5,
),
enabled: false,
readOnly: true,
background: Colors.transparent,
// 关键点 1:调整行号样式
lineNumberStyle: LineNumberStyle(
width: stableWidth,
textAlign: TextAlign.right, // 回归右对齐,更符合代码习惯
margin: _showLineNumbers ? 12 : 0, // 增加间距防止数字贴边触发换行
textStyle: TextStyle(
fontFamily: 'SourceCodePro',
fontSize: _fontSize,
height: 1.5,
),
enabled: false,
readOnly: true,
background: Colors.transparent,
// 关键点 1:调整行号样式
lineNumberStyle: LineNumberStyle(
width: stableWidth,
textAlign: TextAlign.right, // 回归右对齐,更符合代码习惯
margin: _showLineNumbers ? 12 : 0, // 增加间距防止数字贴边触发换行
textStyle: TextStyle(
color: _showLineNumbers ? Colors.grey.shade600 : Colors.transparent,
fontSize: _fontSize * 0.8,
height: 1.5, // 必须和正文高度完全一致
// 核心修复:通过强制单词不换行来防止数字断裂
fontFeatures: const [FontFeature.tabularFigures()], // 使用等宽数字
),
color: _showLineNumbers
? Colors.grey.shade600
: Colors.transparent,
fontSize: _fontSize * 0.8,
height: 1.5, // 必须和正文高度完全一致
// 核心修复:通过强制单词不换行来防止数字断裂
fontFeatures: const [
FontFeature.tabularFigures(),
], // 使用等宽数字
),
),
),
),
),
),
);
}
),
);
}
// 构建功能组合按钮
Widget _buildExpandableFab() {
@@ -225,7 +237,9 @@ class _DocumentPreviewPageState extends State<DocumentPreviewPage> {
heroTag: 'font_up',
mini: true,
backgroundColor: Colors.grey.shade800,
onPressed: () => setState(() { if(_fontSize < 30) _fontSize++; }),
onPressed: () => setState(() {
if (_fontSize < 30) _fontSize++;
}),
child: const Icon(Icons.add, color: Colors.white, size: 20),
),
const SizedBox(height: 8),
@@ -234,7 +248,9 @@ class _DocumentPreviewPageState extends State<DocumentPreviewPage> {
heroTag: 'font_down',
mini: true,
backgroundColor: Colors.grey.shade800,
onPressed: () => setState(() { if(_fontSize > 8) _fontSize--; }),
onPressed: () => setState(() {
if (_fontSize > 8) _fontSize--;
}),
child: const Icon(Icons.remove, color: Colors.white, size: 20),
),
const SizedBox(height: 8),
@@ -242,7 +258,9 @@ class _DocumentPreviewPageState extends State<DocumentPreviewPage> {
FloatingActionButton(
heroTag: 'line_toggle',
mini: true,
backgroundColor: _showLineNumbers ? Colors.blue : Colors.grey.shade800,
backgroundColor: _showLineNumbers
? Colors.blue
: Colors.grey.shade800,
onPressed: () => setState(() => _showLineNumbers = !_showLineNumbers),
child: Icon(
_showLineNumbers ? Icons.format_list_numbered : Icons.short_text,
@@ -253,4 +271,4 @@ class _DocumentPreviewPageState extends State<DocumentPreviewPage> {
],
);
}
}
}