自定义线路与线路优化
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import '../../../data/models/file_model.dart';
|
||||
import '../../../services/custom_line_service.dart';
|
||||
import '../../../services/file_service.dart';
|
||||
|
||||
/// 音频预览页面
|
||||
@@ -43,7 +44,11 @@ class _AudioPreviewPageState extends State<AudioPreviewPage> {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
player.open(Media(url), play: true);
|
||||
await CustomLineService.instance.configureMediaPlayerProxy(
|
||||
player,
|
||||
url,
|
||||
);
|
||||
await player.open(Media(url), play: true);
|
||||
}
|
||||
} else {
|
||||
if (mounted) {
|
||||
@@ -104,11 +109,7 @@ class _AudioPreviewPageState extends State<AudioPreviewPage> {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.error_outline,
|
||||
size: 64,
|
||||
color: Colors.white54,
|
||||
),
|
||||
const Icon(Icons.error_outline, size: 64, color: Colors.white54),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
_errorMessage!,
|
||||
@@ -140,14 +141,17 @@ class AudioPlayerWidget extends StatefulWidget {
|
||||
final Player player;
|
||||
final String fileName;
|
||||
|
||||
const AudioPlayerWidget({super.key, required this.player, required this.fileName});
|
||||
const AudioPlayerWidget({
|
||||
super.key,
|
||||
required this.player,
|
||||
required this.fileName,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AudioPlayerWidget> createState() => _AudioPlayerWidgetState();
|
||||
}
|
||||
|
||||
class _AudioPlayerWidgetState extends State<AudioPlayerWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
@@ -155,10 +159,7 @@ class _AudioPlayerWidgetState extends State<AudioPlayerWidget> {
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
const Color(0xFF1A1A2E),
|
||||
const Color(0xFF16213E),
|
||||
],
|
||||
colors: [const Color(0xFF1A1A2E), const Color(0xFF16213E)],
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
@@ -246,12 +247,18 @@ class _AudioPlayerWidgetState extends State<AudioPlayerWidget> {
|
||||
return SliderTheme(
|
||||
data: SliderThemeData(
|
||||
trackHeight: 4,
|
||||
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 8),
|
||||
overlayShape: const RoundSliderOverlayShape(overlayRadius: 16),
|
||||
thumbShape: const RoundSliderThumbShape(
|
||||
enabledThumbRadius: 8,
|
||||
),
|
||||
overlayShape: const RoundSliderOverlayShape(
|
||||
overlayRadius: 16,
|
||||
),
|
||||
activeTrackColor: const Color(0xFFE94560),
|
||||
inactiveTrackColor: Colors.white.withValues(alpha: 0.1),
|
||||
thumbColor: const Color(0xFFE94560),
|
||||
overlayColor: const Color(0xFFE94560).withValues(alpha: 0.3),
|
||||
overlayColor: const Color(
|
||||
0xFFE94560,
|
||||
).withValues(alpha: 0.3),
|
||||
),
|
||||
child: Slider(
|
||||
value: position.inMilliseconds.toDouble(),
|
||||
@@ -259,9 +266,7 @@ class _AudioPlayerWidgetState extends State<AudioPlayerWidget> {
|
||||
? duration.inMilliseconds.toDouble()
|
||||
: 1.0,
|
||||
onChanged: (value) {
|
||||
widget.player.seek(
|
||||
Duration(milliseconds: value.toInt()),
|
||||
);
|
||||
widget.player.seek(Duration(milliseconds: value.toInt()));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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> {
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import '../../../data/models/file_model.dart';
|
||||
import '../../../services/custom_line_service.dart';
|
||||
import '../../../services/file_service.dart';
|
||||
import '../../../services/cache_manager_service.dart';
|
||||
import '../../widgets/toast_helper.dart';
|
||||
@@ -44,6 +45,7 @@ class _ImagePreviewPageState extends State<ImagePreviewPage> {
|
||||
if (urls.isNotEmpty) {
|
||||
final urlData = urls[0] as Map<String, dynamic>;
|
||||
final url = urlData['url'] as String;
|
||||
await CustomLineService.instance.activateSelectedNodeForUrl(url);
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
@@ -197,7 +199,7 @@ class _ImagePreviewPageState extends State<ImagePreviewPage> {
|
||||
// 2. 编写平滑缩放函数
|
||||
void _smoothScale(PhotoViewController photoController, double delta) {
|
||||
// 如果正在动画中,忽略新的滚轮脉冲,防止冲突
|
||||
if (_isAnimating) return;
|
||||
if (_isAnimating) return;
|
||||
_isAnimating = true;
|
||||
|
||||
// 计算目标缩放值
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:markdown_widget/widget/blocks/leaf/link.dart';
|
||||
import 'package:markdown_widget/widget/markdown.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../../../data/models/file_model.dart';
|
||||
import '../../../services/custom_line_service.dart';
|
||||
import '../../../services/file_service.dart';
|
||||
|
||||
class MarkdownPreviewPage extends StatefulWidget {
|
||||
@@ -30,7 +31,7 @@ class _MarkdownPreviewPageState extends State<MarkdownPreviewPage> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
final _tocController = TocController();
|
||||
bool _isDarkMode = false;
|
||||
|
||||
|
||||
// 控制目录是否显示
|
||||
bool _isTocVisible = false;
|
||||
// 标记是否已经根据屏幕宽度进行了初始化
|
||||
@@ -62,6 +63,7 @@ class _MarkdownPreviewPageState extends State<MarkdownPreviewPage> {
|
||||
|
||||
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) {
|
||||
@@ -104,7 +106,10 @@ class _MarkdownPreviewPageState extends State<MarkdownPreviewPage> {
|
||||
backgroundColor: bgColor,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back, color: isDark ? Colors.white : Colors.black87),
|
||||
icon: Icon(
|
||||
Icons.arrow_back,
|
||||
color: isDark ? Colors.white : Colors.black87,
|
||||
),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
title: Column(
|
||||
@@ -120,13 +125,19 @@ class _MarkdownPreviewPageState extends State<MarkdownPreviewPage> {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (!_isLoading && _error == null)
|
||||
Text('Markdown 预览', style: TextStyle(color: Colors.grey, fontSize: 12)),
|
||||
Text(
|
||||
'Markdown 预览',
|
||||
style: TextStyle(color: Colors.grey, fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
if (!_isLoading && _error == null)
|
||||
IconButton(
|
||||
icon: Icon(Icons.copy, color: isDark ? Colors.white : Colors.black87),
|
||||
icon: Icon(
|
||||
Icons.copy,
|
||||
color: isDark ? Colors.white : Colors.black87,
|
||||
),
|
||||
onPressed: () => Clipboard.setData(ClipboardData(text: _content)),
|
||||
),
|
||||
],
|
||||
@@ -134,8 +145,8 @@ class _MarkdownPreviewPageState extends State<MarkdownPreviewPage> {
|
||||
body: _isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _error != null
|
||||
? _buildErrorWidget()
|
||||
: _buildResponsiveBody(isWideScreen, screenWidth),
|
||||
? _buildErrorWidget()
|
||||
: _buildResponsiveBody(isWideScreen, screenWidth),
|
||||
floatingActionButton: _buildFAB(isDark),
|
||||
);
|
||||
}
|
||||
@@ -143,7 +154,7 @@ class _MarkdownPreviewPageState extends State<MarkdownPreviewPage> {
|
||||
// 构建响应式主体
|
||||
Widget _buildResponsiveBody(bool isWideScreen, double screenWidth) {
|
||||
final double tocWidth = isWideScreen ? 300 : screenWidth * 0.7;
|
||||
|
||||
|
||||
// 预定义暗色/亮色下的文字颜色
|
||||
final Color textColor = _isDarkMode ? Colors.white : Colors.black87;
|
||||
final Color subTextColor = _isDarkMode ? Colors.white70 : Colors.black54;
|
||||
@@ -159,10 +170,14 @@ class _MarkdownPreviewPageState extends State<MarkdownPreviewPage> {
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
right: BorderSide(
|
||||
color: _isDarkMode ? Colors.white10 : Colors.grey.withValues(alpha: 0.2)
|
||||
color: _isDarkMode
|
||||
? Colors.white10
|
||||
: Colors.grey.withValues(alpha: 0.2),
|
||||
),
|
||||
),
|
||||
color: _isDarkMode ? const Color(0xFF252525) : Colors.grey.shade50,
|
||||
color: _isDarkMode
|
||||
? const Color(0xFF252525)
|
||||
: Colors.grey.shade50,
|
||||
),
|
||||
child: ClipRect(
|
||||
child: Column(
|
||||
@@ -178,7 +193,10 @@ class _MarkdownPreviewPageState extends State<MarkdownPreviewPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
Divider(height: 1, color: _isDarkMode ? Colors.white10 : Colors.grey.shade300),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: _isDarkMode ? Colors.white10 : Colors.grey.shade300,
|
||||
),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
@@ -198,9 +216,7 @@ class _MarkdownPreviewPageState extends State<MarkdownPreviewPage> {
|
||||
bodySmall: TextStyle(color: subTextColor),
|
||||
),
|
||||
),
|
||||
child: TocWidget(
|
||||
controller: _tocController,
|
||||
),
|
||||
child: TocWidget(controller: _tocController),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -228,8 +244,11 @@ class _MarkdownPreviewPageState extends State<MarkdownPreviewPage> {
|
||||
}
|
||||
|
||||
Widget _buildMarkdownWidget(String content) {
|
||||
final config = _isDarkMode ? MarkdownConfig.darkConfig : MarkdownConfig.defaultConfig;
|
||||
CodeWrapperWidget codeWrapper(child, text, language) => CodeWrapperWidget(child, text, language);
|
||||
final config = _isDarkMode
|
||||
? MarkdownConfig.darkConfig
|
||||
: MarkdownConfig.defaultConfig;
|
||||
CodeWrapperWidget codeWrapper(child, text, language) =>
|
||||
CodeWrapperWidget(child, text, language);
|
||||
|
||||
return MarkdownWidget(
|
||||
data: content,
|
||||
@@ -237,7 +256,10 @@ class _MarkdownPreviewPageState extends State<MarkdownPreviewPage> {
|
||||
config: config.copy(
|
||||
configs: [
|
||||
_isDarkMode
|
||||
? PreConfig.darkConfig.copy(theme: a11yLightTheme, wrapper: codeWrapper)
|
||||
? PreConfig.darkConfig.copy(
|
||||
theme: a11yLightTheme,
|
||||
wrapper: codeWrapper,
|
||||
)
|
||||
: PreConfig(theme: atomOneDarkTheme).copy(wrapper: codeWrapper),
|
||||
LinkConfig(
|
||||
style: TextStyle(
|
||||
@@ -266,12 +288,14 @@ class _MarkdownPreviewPageState extends State<MarkdownPreviewPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFAB(bool isDark) {
|
||||
Widget _buildFAB(bool isDark) {
|
||||
if (_isLoading || _error != null) return const SizedBox.shrink();
|
||||
|
||||
|
||||
// 定义统一的按钮背景颜色,增加视觉一致性
|
||||
final Color activeColor = isDark ? Colors.blue.shade700 : Colors.blue;
|
||||
final Color inactiveColor = isDark ? Colors.grey.shade800 : Colors.grey.shade200;
|
||||
final Color inactiveColor = isDark
|
||||
? Colors.grey.shade800
|
||||
: Colors.grey.shade200;
|
||||
final Color iconColor = isDark ? Colors.white : Colors.black87;
|
||||
|
||||
return Column(
|
||||
@@ -285,7 +309,7 @@ Widget _buildFAB(bool isDark) {
|
||||
elevation: 2, // 稍微降低阴影,看起来更精致
|
||||
onPressed: () => setState(() => _isDarkMode = !_isDarkMode),
|
||||
child: Icon(
|
||||
isDark ? Icons.light_mode : Icons.dark_mode,
|
||||
isDark ? Icons.light_mode : Icons.dark_mode,
|
||||
color: iconColor,
|
||||
size: 20, // 微调图标大小
|
||||
),
|
||||
@@ -300,7 +324,7 @@ Widget _buildFAB(bool isDark) {
|
||||
elevation: 2,
|
||||
onPressed: () => setState(() => _isTocVisible = !_isTocVisible),
|
||||
child: Icon(
|
||||
Icons.format_list_bulleted,
|
||||
Icons.format_list_bulleted,
|
||||
color: _isTocVisible ? Colors.white : iconColor,
|
||||
size: 20,
|
||||
),
|
||||
@@ -308,4 +332,4 @@ Widget _buildFAB(bool isDark) {
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pdfrx/pdfrx.dart';
|
||||
import '../../../data/models/file_model.dart';
|
||||
import '../../../services/custom_line_service.dart';
|
||||
import '../../../services/file_service.dart';
|
||||
|
||||
/// PDF预览页面
|
||||
@@ -37,6 +38,7 @@ class _PdfPreviewPageState extends State<PdfPreviewPage> {
|
||||
if (urls.isNotEmpty) {
|
||||
final urlData = urls[0] as Map<String, dynamic>;
|
||||
final url = urlData['url'] as String;
|
||||
await CustomLineService.instance.activateSelectedNodeForUrl(url);
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:media_kit_video/media_kit_video.dart';
|
||||
import '../../../data/models/file_model.dart';
|
||||
import '../../../services/custom_line_service.dart';
|
||||
import '../../../services/file_service.dart';
|
||||
import 'widgets/video_controls_overlay.dart';
|
||||
|
||||
@@ -45,7 +46,11 @@ class _VideoPreviewPageState extends State<VideoPreviewPage> {
|
||||
|
||||
if (mounted) {
|
||||
setState(() => _isLoading = false);
|
||||
player.open(Media(url), play: true);
|
||||
await CustomLineService.instance.configureMediaPlayerProxy(
|
||||
player,
|
||||
url,
|
||||
);
|
||||
await player.open(Media(url), play: true);
|
||||
}
|
||||
} else {
|
||||
if (mounted) {
|
||||
@@ -78,37 +83,42 @@ class _VideoPreviewPageState extends State<VideoPreviewPage> {
|
||||
body: _isLoading
|
||||
? const Center(child: CircularProgressIndicator(color: Colors.white))
|
||||
: _errorMessage != null
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.error_outline, size: 64, color: Colors.white),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
_errorMessage!,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Colors.white70),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_errorMessage = null;
|
||||
});
|
||||
_loadVideoUrl();
|
||||
},
|
||||
child: const Text('重试'),
|
||||
),
|
||||
],
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.error_outline,
|
||||
size: 64,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
: ExcludeSemantics(
|
||||
child: Video(
|
||||
controller: controller,
|
||||
controls: (state) => VideoControlsOverlay(state: state, title: widget.file.name),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
_errorMessage!,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(color: Colors.white70),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_errorMessage = null;
|
||||
});
|
||||
_loadVideoUrl();
|
||||
},
|
||||
child: const Text('重试'),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: ExcludeSemantics(
|
||||
child: Video(
|
||||
controller: controller,
|
||||
controls: (state) =>
|
||||
VideoControlsOverlay(state: state, title: widget.file.name),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user