chore: 批量更新v6.5.21版本,整合多项功能修复与优化
主要变更: 1. 新增多风格音效资源与管理文档 2. 修复翻译服务空响应处理与Dio日志异常捕获 3. 完善Web端平台适配与路径获取Stub 4. 优化设备配对与文件传输功能 5. 新增角色命名常量与摇一摇检测器 6. 修复Riverpod dispose与鸿蒙导航路由 7. 新增每日通知服务与流体着色器 8. 优化备份服务与数据管理页面 9. 新增隐私设置附近设备发现选项 10. 重构诗词提供者支持历史记录 11. 完善桌面端构建配置与开发脚本 12. 清理旧版工具部署脚本
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
// ============================================================
|
||||
// 闲言APP — ProImageEditor 包装页
|
||||
// 创建时间: 2026-04-23
|
||||
// 更新时间: 2026-05-05
|
||||
// 更新时间: 2026-05-20
|
||||
// 作用: ProImageEditor 的闲言APP包装页,全新iOS 26风格
|
||||
// 上次更新: 修复描边坐标偏移(globalToLocal两步转换)+文本编辑器用selectedLayerNotifier回退取TextLayer
|
||||
// 上次更新: 画布背景改透明+ColoredBox提供背景色,配合CanvasStyleMiddleware裁剪修复
|
||||
// ============================================================
|
||||
|
||||
import 'dart:convert';
|
||||
@@ -15,6 +15,7 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:pro_image_editor/pro_image_editor.dart' as pro;
|
||||
|
||||
import 'package:xianyan/core/router/editor_router.dart';
|
||||
import 'package:xianyan/editor/services/core/canvas_style_middleware.dart';
|
||||
import 'package:xianyan/editor/services/core/pro_editor_bridge.dart';
|
||||
import 'package:xianyan/editor/services/core/editor_theme_notifier.dart';
|
||||
import 'package:xianyan/editor/widgets/controls/editor_system_ui.dart';
|
||||
@@ -365,6 +366,8 @@ class ProEditorPageState extends State<ProEditorPage>
|
||||
}
|
||||
}
|
||||
|
||||
String? latestDeltaJson;
|
||||
|
||||
showCupertinoModalPopup<void>(
|
||||
context: context,
|
||||
builder: (_) => SizedBox(
|
||||
@@ -373,8 +376,45 @@ class ProEditorPageState extends State<ProEditorPage>
|
||||
initialDeltaJson: initialDeltaJson,
|
||||
onDone: () {
|
||||
Navigator.pop(context);
|
||||
|
||||
final deltaJson = latestDeltaJson;
|
||||
if (deltaJson == null || deltaJson.isEmpty) return;
|
||||
|
||||
final editorState = _editorKey.currentState;
|
||||
if (editorState == null) return;
|
||||
|
||||
try {
|
||||
final deltaOps = jsonDecode(deltaJson) as List<dynamic>;
|
||||
final plainText = deltaOps
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.where((op) => op.containsKey('insert'))
|
||||
.map((op) {
|
||||
final v = op['insert'];
|
||||
return v is String ? v : '';
|
||||
})
|
||||
.join()
|
||||
.trim();
|
||||
if (plainText.isEmpty) return;
|
||||
|
||||
if (textLayer != null) {
|
||||
textLayer.text = plainText;
|
||||
editorState.setState(() {});
|
||||
} else {
|
||||
editorState.addLayer(
|
||||
pro.TextLayer(
|
||||
text: plainText,
|
||||
color: const Color(0xFFFFFFFF),
|
||||
background: Colors.transparent,
|
||||
align: TextAlign.center,
|
||||
offset: const Offset(0.5, 0.3),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (_) {}
|
||||
},
|
||||
onChanged: (deltaJson) {
|
||||
latestDeltaJson = deltaJson;
|
||||
|
||||
if (textLayer == null) return;
|
||||
final editorState = _editorKey.currentState;
|
||||
if (editorState == null) return;
|
||||
@@ -434,10 +474,10 @@ class ProEditorPageState extends State<ProEditorPage>
|
||||
final safeBottom = MediaQuery.of(context).viewPadding.bottom;
|
||||
final configs = ProEditorBridge.buildConfigs(
|
||||
context: context,
|
||||
canvasStyle: _canvasStyle,
|
||||
bodyItemsBuilder: (editor, stream) =>
|
||||
_buildMainBodyWidgets(editor, stream, safeTop, safeBottom),
|
||||
onCloseWarning: handleCloseWarning,
|
||||
canvasBackground: Colors.transparent,
|
||||
);
|
||||
|
||||
return MediaQuery.removeViewPadding(
|
||||
@@ -453,94 +493,103 @@ class ProEditorPageState extends State<ProEditorPage>
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: RepaintBoundary(
|
||||
key: _repaintKey,
|
||||
child: pro.ProImageEditor.memory(
|
||||
widget.imageBytes,
|
||||
key: _editorKey,
|
||||
configs: configs,
|
||||
callbacks: pro.ProImageEditorCallbacks(
|
||||
onImageEditingComplete: onEditingComplete,
|
||||
onCloseEditor: (mode) => Navigator.of(context).pop(),
|
||||
mainEditorCallbacks: pro.MainEditorCallbacks(
|
||||
onCreateTextLayer:
|
||||
widget.initialText != null &&
|
||||
widget.initialText!.isNotEmpty
|
||||
? () async => pro.TextLayer(
|
||||
text: widget.initialText!,
|
||||
color: const Color(0xFFFFFFFF),
|
||||
background: Colors.transparent,
|
||||
align: TextAlign.center,
|
||||
offset: const Offset(0.5, 0.3),
|
||||
)
|
||||
: null,
|
||||
onLayerTapUp: (layer) => _onLayerTapUp(layer),
|
||||
onScaleStart: (_) {
|
||||
if (!_isLayerDragging) {
|
||||
setState(() => _isLayerDragging = true);
|
||||
}
|
||||
},
|
||||
onScaleUpdate: (details) {
|
||||
final editorState = _editorKey.currentState;
|
||||
final layer = editorState?.selectedLayer;
|
||||
if (layer != null) {
|
||||
final layerBox =
|
||||
layer.key.currentContext?.findRenderObject()
|
||||
as RenderBox?;
|
||||
final overlayBox =
|
||||
_overlayStackKey.currentContext
|
||||
?.findRenderObject()
|
||||
as RenderBox?;
|
||||
if (layerBox != null &&
|
||||
layerBox.hasSize &&
|
||||
overlayBox != null) {
|
||||
final globalTopLeft = layerBox.localToGlobal(
|
||||
Offset.zero,
|
||||
);
|
||||
final globalBottomRight = layerBox.localToGlobal(
|
||||
Offset(
|
||||
layerBox.size.width,
|
||||
layerBox.size.height,
|
||||
),
|
||||
);
|
||||
final localTopLeft = overlayBox.globalToLocal(
|
||||
globalTopLeft,
|
||||
);
|
||||
final localBottomRight = overlayBox.globalToLocal(
|
||||
globalBottomRight,
|
||||
);
|
||||
final size = Size(
|
||||
(localBottomRight.dx - localTopLeft.dx).abs(),
|
||||
(localBottomRight.dy - localTopLeft.dy).abs(),
|
||||
);
|
||||
setState(() {
|
||||
_draggingLayerRect = localTopLeft & size;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
onScaleEnd: (_) {
|
||||
if (_isLayerDragging) {
|
||||
setState(() {
|
||||
_isLayerDragging = false;
|
||||
_draggingLayerRect = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
EditorThemeNotifier.instance.toggleDarkLight();
|
||||
},
|
||||
onEditorZoomScaleStart: (_) {
|
||||
if (!_isPinching) {
|
||||
setState(() => _isPinching = true);
|
||||
}
|
||||
},
|
||||
onEditorZoomScaleEnd: (_) {
|
||||
if (_isPinching) {
|
||||
setState(() => _isPinching = false);
|
||||
}
|
||||
},
|
||||
onEditorZoomMatrix4Change: (matrix) {
|
||||
_zoomScaleNotifier.value = matrix.getMaxScaleOnAxis();
|
||||
},
|
||||
child: CanvasStyleMiddleware(
|
||||
style: _canvasStyle,
|
||||
child: ColoredBox(
|
||||
color: p.bgCanvas,
|
||||
child: pro.ProImageEditor.memory(
|
||||
widget.imageBytes,
|
||||
key: _editorKey,
|
||||
configs: configs,
|
||||
callbacks: pro.ProImageEditorCallbacks(
|
||||
onImageEditingComplete: onEditingComplete,
|
||||
onCloseEditor: (mode) => Navigator.of(context).pop(),
|
||||
mainEditorCallbacks: pro.MainEditorCallbacks(
|
||||
onCreateTextLayer:
|
||||
widget.initialText != null &&
|
||||
widget.initialText!.isNotEmpty
|
||||
? () async => pro.TextLayer(
|
||||
text: widget.initialText!,
|
||||
color: const Color(0xFFFFFFFF),
|
||||
background: Colors.transparent,
|
||||
align: TextAlign.center,
|
||||
offset: const Offset(0.5, 0.3),
|
||||
)
|
||||
: null,
|
||||
onLayerTapUp: (layer) => _onLayerTapUp(layer),
|
||||
onScaleStart: (_) {
|
||||
if (!_isLayerDragging) {
|
||||
setState(() => _isLayerDragging = true);
|
||||
}
|
||||
},
|
||||
onScaleUpdate: (details) {
|
||||
final editorState = _editorKey.currentState;
|
||||
final layer = editorState?.selectedLayer;
|
||||
if (layer != null) {
|
||||
final layerBox =
|
||||
layer.key.currentContext?.findRenderObject()
|
||||
as RenderBox?;
|
||||
final overlayBox =
|
||||
_overlayStackKey.currentContext
|
||||
?.findRenderObject()
|
||||
as RenderBox?;
|
||||
if (layerBox != null &&
|
||||
layerBox.hasSize &&
|
||||
overlayBox != null) {
|
||||
final globalTopLeft = layerBox.localToGlobal(
|
||||
Offset.zero,
|
||||
);
|
||||
final globalBottomRight = layerBox
|
||||
.localToGlobal(
|
||||
Offset(
|
||||
layerBox.size.width,
|
||||
layerBox.size.height,
|
||||
),
|
||||
);
|
||||
final localTopLeft = overlayBox.globalToLocal(
|
||||
globalTopLeft,
|
||||
);
|
||||
final localBottomRight = overlayBox
|
||||
.globalToLocal(globalBottomRight);
|
||||
final size = Size(
|
||||
(localBottomRight.dx - localTopLeft.dx)
|
||||
.abs(),
|
||||
(localBottomRight.dy - localTopLeft.dy)
|
||||
.abs(),
|
||||
);
|
||||
setState(() {
|
||||
_draggingLayerRect = localTopLeft & size;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
onScaleEnd: (_) {
|
||||
if (_isLayerDragging) {
|
||||
setState(() {
|
||||
_isLayerDragging = false;
|
||||
_draggingLayerRect = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
EditorThemeNotifier.instance.toggleDarkLight();
|
||||
},
|
||||
onEditorZoomScaleStart: (_) {
|
||||
if (!_isPinching) {
|
||||
setState(() => _isPinching = true);
|
||||
}
|
||||
},
|
||||
onEditorZoomScaleEnd: (_) {
|
||||
if (_isPinching) {
|
||||
setState(() => _isPinching = false);
|
||||
}
|
||||
},
|
||||
onEditorZoomMatrix4Change: (matrix) {
|
||||
_zoomScaleNotifier.value = matrix
|
||||
.getMaxScaleOnAxis();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
200
lib/editor/services/core/canvas_style_middleware.dart
Normal file
200
lib/editor/services/core/canvas_style_middleware.dart
Normal file
@@ -0,0 +1,200 @@
|
||||
// ============================================================
|
||||
// 闲言APP — 画布样式中间件
|
||||
// 创建时间: 2026-05-20
|
||||
// 更新时间: 2026-05-20
|
||||
// 作用: 通过包裹ProImageEditor实现画布样式实时预览
|
||||
// 圆角/边框/阴影/叠层/外边距,无需魔改三方库
|
||||
// 上次更新: 重构渲染层级 — ClipRRect直接裁剪child,样式作用于画布本身
|
||||
// ============================================================
|
||||
//
|
||||
// 设计原理:
|
||||
// ProImageEditorState 没有 didUpdateWidget,
|
||||
// 导致 wrapBody 回调在 configs 变更后不会更新。
|
||||
// 因此本中间件在父级直接包裹 ProImageEditor,
|
||||
// 通过 setState 驱动重建,确保样式始终跟随 _canvasStyle。
|
||||
//
|
||||
// 渲染层级 (从内到外):
|
||||
// 1. child — ProImageEditor 编辑器本体 (背景已透明)
|
||||
// 2. clipRadius — ClipRRect 圆角裁剪 (直接裁剪child)
|
||||
// 3. border — 边框 (紧贴裁剪后的画布边缘)
|
||||
// 4. shadow — BoxShadow 阴影 (基于裁剪后画布生成)
|
||||
// 5. stackLayers — Stack 叠层卡片
|
||||
// 6. outerMargin — Padding 外边距
|
||||
//
|
||||
// 关键: ProImageEditor 的 Scaffold 背景需设为透明,
|
||||
// 画布背景色由外层 ColoredBox 提供,
|
||||
// 这样 ClipRRect 才能正确裁剪整个画布内容。
|
||||
//
|
||||
// 导出路径:
|
||||
// 编辑时预览 → 本中间件 (实时渲染)
|
||||
// 导出时后处理 → ExportService.applyCanvasStyle() (dart:ui 精确渲染)
|
||||
// ============================================================
|
||||
|
||||
import 'package:dotted_border/dotted_border.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pro_image_editor/core/models/canvas_style_model.dart';
|
||||
|
||||
class CanvasStyleMiddleware extends StatelessWidget {
|
||||
const CanvasStyleMiddleware({
|
||||
super.key,
|
||||
required this.style,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final CanvasStyleModel style;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final radius = style.borderRadius.clamp(0.0, 80.0);
|
||||
final borderRad = BorderRadius.circular(radius);
|
||||
|
||||
final hasAnyStyle =
|
||||
radius > 0 ||
|
||||
style.hasBorder ||
|
||||
style.hasShadow ||
|
||||
style.hasStack ||
|
||||
(style.hasOuterMargin && style.outerMargin > 0);
|
||||
|
||||
if (!hasAnyStyle) return child;
|
||||
|
||||
Widget result = child;
|
||||
|
||||
if (radius > 0) {
|
||||
result = ClipRRect(borderRadius: borderRad, child: result);
|
||||
}
|
||||
|
||||
if (style.hasBorder) {
|
||||
result = _buildBorder(result, borderRad);
|
||||
}
|
||||
|
||||
if (style.hasShadow) {
|
||||
result = Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: borderRad,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: style.shadowOpacity),
|
||||
blurRadius: style.shadowBlur,
|
||||
spreadRadius: style.shadowSpread,
|
||||
offset: Offset(style.shadowOffsetX, style.shadowOffsetY),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: result,
|
||||
);
|
||||
}
|
||||
|
||||
if (style.hasStack && style.stackLayers > 1) {
|
||||
result = _buildStackLayers(result, borderRad);
|
||||
}
|
||||
|
||||
if (style.hasOuterMargin && style.outerMargin > 0) {
|
||||
result = Padding(
|
||||
padding: EdgeInsets.all(style.outerMargin),
|
||||
child: result,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Widget _buildStackLayers(Widget content, BorderRadius borderRad) {
|
||||
final children = <Widget>[];
|
||||
|
||||
for (int i = style.stackLayers; i >= 1; i--) {
|
||||
final offset = style.stackDistance * i;
|
||||
final dx = _stackOffsetX(offset);
|
||||
final dy = _stackOffsetY(offset);
|
||||
|
||||
children.add(
|
||||
Transform.translate(
|
||||
offset: Offset(dx, dy),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.15),
|
||||
borderRadius: borderRad,
|
||||
border: style.hasBorder
|
||||
? Border.all(
|
||||
color: style.borderColor.withValues(alpha: 0.2),
|
||||
width: style.borderWidth * 0.5,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
children.add(content);
|
||||
|
||||
return Stack(
|
||||
alignment: style.stackPosition.alignment,
|
||||
clipBehavior: Clip.none,
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
|
||||
double _stackOffsetX(double offset) {
|
||||
final pos = style.stackPosition;
|
||||
if (pos == CanvasStackPosition.left ||
|
||||
pos == CanvasStackPosition.topLeft ||
|
||||
pos == CanvasStackPosition.centerLeft ||
|
||||
pos == CanvasStackPosition.bottomLeft) {
|
||||
return -offset;
|
||||
}
|
||||
if (pos == CanvasStackPosition.right ||
|
||||
pos == CanvasStackPosition.topRight ||
|
||||
pos == CanvasStackPosition.centerRight ||
|
||||
pos == CanvasStackPosition.bottomRight) {
|
||||
return offset;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double _stackOffsetY(double offset) {
|
||||
final pos = style.stackPosition;
|
||||
if (pos == CanvasStackPosition.top ||
|
||||
pos == CanvasStackPosition.topLeft ||
|
||||
pos == CanvasStackPosition.topCenter ||
|
||||
pos == CanvasStackPosition.topRight) {
|
||||
return -offset;
|
||||
}
|
||||
if (pos == CanvasStackPosition.bottom ||
|
||||
pos == CanvasStackPosition.bottomLeft ||
|
||||
pos == CanvasStackPosition.bottomCenter ||
|
||||
pos == CanvasStackPosition.bottomRight) {
|
||||
return offset;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
Widget _buildBorder(Widget content, BorderRadius borderRad) {
|
||||
if (style.borderStyle == CanvasBorderStyle.dashed ||
|
||||
style.borderStyle == CanvasBorderStyle.dotted) {
|
||||
final dashPattern = style.borderStyle == CanvasBorderStyle.dashed
|
||||
? const [6.0, 4.0]
|
||||
: const [2.0, 3.0];
|
||||
return DottedBorder(
|
||||
options: RoundedRectDottedBorderOptions(
|
||||
dashPattern: dashPattern,
|
||||
strokeWidth: style.borderWidth,
|
||||
color: style.borderColor,
|
||||
radius: Radius.circular(borderRad.topLeft.x),
|
||||
padding: EdgeInsets.zero,
|
||||
strokeCap: StrokeCap.round,
|
||||
),
|
||||
child: content,
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: borderRad,
|
||||
border: Border.all(color: style.borderColor, width: style.borderWidth),
|
||||
),
|
||||
child: content,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
// ============================================================
|
||||
// 闲言APP — ProImageEditor 桥接层
|
||||
// 创建时间: 2026-04-23
|
||||
// 更新时间: 2026-05-05
|
||||
// 上次更新: buildConfigs接受CanvasStyleModel(圆角+边框+阴影+叠层)
|
||||
// 更新时间: 2026-05-20
|
||||
// 上次更新: 移除wrapBody方案,画布样式改由CanvasStyleMiddleware中间件实现;
|
||||
// 新增canvasBackground参数支持透明画布背景
|
||||
// ============================================================
|
||||
|
||||
import 'dart:ui' as ui;
|
||||
@@ -17,7 +18,6 @@ import 'package:pro_image_editor/designs/frosted_glass/frosted_glass.dart';
|
||||
import 'package:xianyan/core/theme/app_radius.dart';
|
||||
import 'package:xianyan/core/theme/app_spacing.dart';
|
||||
import 'package:xianyan/editor/models/editor_models.dart';
|
||||
import 'package:pro_image_editor/core/models/canvas_style_model.dart';
|
||||
import 'package:xianyan/editor/services/core/editor_theme_notifier.dart';
|
||||
import 'package:xianyan/editor/services/core/sticker_config_builder.dart';
|
||||
import 'package:xianyan/editor/services/core/widget_layer_factory.dart';
|
||||
@@ -102,7 +102,6 @@ class ProEditorBridge {
|
||||
/// 构建 ProImageEditorConfigs — Cupertino + iOS风格 + 中文
|
||||
static pro.ProImageEditorConfigs buildConfigs({
|
||||
required BuildContext context,
|
||||
CanvasStyleModel? canvasStyle,
|
||||
List<TextStyle>? additionalFonts,
|
||||
List<pro.ReactiveWidget> Function(
|
||||
pro.ProImageEditorState editor,
|
||||
@@ -110,6 +109,7 @@ class ProEditorBridge {
|
||||
)?
|
||||
bodyItemsBuilder,
|
||||
Future<bool> Function(pro.ProImageEditorState editor)? onCloseWarning,
|
||||
Color? canvasBackground,
|
||||
}) {
|
||||
final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark;
|
||||
|
||||
@@ -140,9 +140,6 @@ class ProEditorBridge {
|
||||
builder: (_) => SizedBox(key: key, height: 0),
|
||||
),
|
||||
bodyItems: bodyItemsBuilder,
|
||||
wrapBody: (editor, rebuildStream, content) {
|
||||
return content;
|
||||
},
|
||||
closeWarningDialog:
|
||||
onCloseWarning ??
|
||||
(editor) async {
|
||||
@@ -169,7 +166,8 @@ class ProEditorBridge {
|
||||
},
|
||||
),
|
||||
style: pro.MainEditorStyle(
|
||||
background: EditorThemeNotifier.instance.palette.bgCanvas,
|
||||
background:
|
||||
canvasBackground ?? EditorThemeNotifier.instance.palette.bgCanvas,
|
||||
uiOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarColor: const Color(0x00000000),
|
||||
statusBarIconBrightness: EditorThemeNotifier.instance.isDark
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// ============================================================
|
||||
// 闲言APP — 导出服务
|
||||
// 创建时间: 2026-04-20
|
||||
// 更新时间: 2026-05-05
|
||||
// 更新时间: 2026-05-20
|
||||
// 作用: 截图导出 + 保存相册 + 系统分享 + WidgetLayer合成导出 + 压缩导出
|
||||
// 上次更新: 新增 applyCanvasStyle 画布样式后处理(边框+阴影+叠层+圆角)
|
||||
// 上次更新: 验证applyCanvasStyle渲染层级与CanvasStyleMiddleware一致
|
||||
// ============================================================
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// ============================================================
|
||||
// 闲言APP — 编辑器底部工具栏 v2
|
||||
// 创建时间: 2026-05-03
|
||||
// 更新时间: 2026-05-04
|
||||
// 更新时间: 2026-05-20
|
||||
// 作用: Tab分类+工具按钮+分割线+状态行+Home指示条
|
||||
// 上次更新: emoji→Icon全量替换,使用EditorIconData映射表
|
||||
// 上次更新: 图片格式信息→提示词按钮+新增onShowPromptPanel回调
|
||||
// ============================================================
|
||||
|
||||
import 'dart:typed_data';
|
||||
@@ -15,7 +15,6 @@ import 'package:pro_image_editor/pro_image_editor.dart' as pro;
|
||||
import 'package:xianyan/editor/services/core/color_tokens.dart';
|
||||
import 'package:xianyan/editor/services/core/editor_theme_notifier.dart';
|
||||
import 'package:xianyan/editor/widgets/controls/editor_icon.dart';
|
||||
import 'package:xianyan/editor/services/image/image_info_service.dart';
|
||||
|
||||
class _ToolDef {
|
||||
const _ToolDef({
|
||||
@@ -68,6 +67,7 @@ class EditorBottomToolbarV2 extends StatefulWidget {
|
||||
this.onShowLayerPanel,
|
||||
this.onShowEmojiIconGrid,
|
||||
this.onShowRichTextEditor,
|
||||
this.onShowPromptPanel,
|
||||
});
|
||||
|
||||
final pro.ProImageEditorState editor;
|
||||
@@ -94,6 +94,7 @@ class EditorBottomToolbarV2 extends StatefulWidget {
|
||||
final VoidCallback? onShowLayerPanel;
|
||||
final VoidCallback? onShowEmojiIconGrid;
|
||||
final VoidCallback? onShowRichTextEditor;
|
||||
final VoidCallback? onShowPromptPanel;
|
||||
|
||||
@override
|
||||
State<EditorBottomToolbarV2> createState() => _EditorBottomToolbarV2State();
|
||||
@@ -104,7 +105,6 @@ class _EditorBottomToolbarV2State extends State<EditorBottomToolbarV2>
|
||||
int _activeTab = 0;
|
||||
int _activeToolIndex = -1;
|
||||
bool _showDebugInfo = false;
|
||||
ImageInfoResult? _imageInfo;
|
||||
late PageController _pageController;
|
||||
|
||||
late List<_ToolTab> _tabs;
|
||||
@@ -112,7 +112,6 @@ class _EditorBottomToolbarV2State extends State<EditorBottomToolbarV2>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadImageInfo();
|
||||
_initTabs();
|
||||
_pageController = PageController(initialPage: _activeTab);
|
||||
}
|
||||
@@ -248,15 +247,9 @@ class _EditorBottomToolbarV2State extends State<EditorBottomToolbarV2>
|
||||
];
|
||||
}
|
||||
|
||||
void _loadImageInfo() {
|
||||
final info = ImageInfoService.getImageInfoFromBytes(widget.imageBytes);
|
||||
if (mounted) setState(() => _imageInfo = info);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant EditorBottomToolbarV2 oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.imageBytes != oldWidget.imageBytes) _loadImageInfo();
|
||||
_initTabs();
|
||||
}
|
||||
|
||||
@@ -464,17 +457,12 @@ class _EditorBottomToolbarV2State extends State<EditorBottomToolbarV2>
|
||||
}
|
||||
|
||||
Widget _buildImageInfo(EditorPalette p) {
|
||||
if (_imageInfo == null) return const SizedBox.shrink();
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(_imageInfo!.formatEmoji, style: const TextStyle(fontSize: 10)),
|
||||
const SizedBox(width: 3),
|
||||
Text(
|
||||
'${_imageInfo!.format} · ${_imageInfo!.sizeText}',
|
||||
style: TextStyle(color: p.textHint, fontSize: 10),
|
||||
),
|
||||
],
|
||||
return _StatusChip(
|
||||
iconName: 'textformat',
|
||||
label: '提示词',
|
||||
isActive: false,
|
||||
palette: p,
|
||||
onTap: () => widget.onShowPromptPanel?.call(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user