- 清理大量废弃的 barrel 导出文件,移除冗余的中间导出层 - 修复所有相对路径导入错误,统一调整为扁平化模块引用 - 更新多平台 pubspec 版本号与依赖库版本 - 补充后端功能问题管理后台与脚本工具 - 调整部分页面的快捷方式文案适配新功能 - 更新部分翻译覆盖率与API文档
298 lines
10 KiB
Dart
298 lines
10 KiB
Dart
// ============================================================
|
||
// 闲言APP — 画布样式模型
|
||
// 创建时间: 2026-05-05
|
||
// 更新时间: 2026-06-12
|
||
// 作用: 画布边缘样式数据模型 — 圆角+边框+阴影+叠层+外边距+内阴影
|
||
// 上次更新: 新增四角独立圆角/内阴影/常用叠层位置
|
||
// ============================================================
|
||
|
||
import 'dart:convert';
|
||
|
||
import 'package:flutter/material.dart';
|
||
|
||
// ============================================================
|
||
// 边框样式枚举
|
||
// ============================================================
|
||
|
||
enum CanvasBorderStyle {
|
||
none('无边框'),
|
||
solid('实线'),
|
||
dashed('虚线'),
|
||
dotted('点线');
|
||
|
||
final String label;
|
||
const CanvasBorderStyle(this.label);
|
||
}
|
||
|
||
// ============================================================
|
||
// 叠层位置枚举
|
||
// ============================================================
|
||
|
||
enum CanvasStackPosition {
|
||
topLeft('左上', AlignmentDirectional.topStart),
|
||
topCenter('上中', AlignmentDirectional.topCenter),
|
||
topRight('右上', AlignmentDirectional.topEnd),
|
||
centerLeft('左中', AlignmentDirectional.centerStart),
|
||
center('居中', AlignmentDirectional.center),
|
||
centerRight('右中', AlignmentDirectional.centerEnd),
|
||
bottomLeft('左下', AlignmentDirectional.bottomStart),
|
||
bottomCenter('下中', AlignmentDirectional.bottomCenter),
|
||
bottomRight('右下', AlignmentDirectional.bottomEnd),
|
||
left('左', AlignmentDirectional.centerStart),
|
||
right('右', AlignmentDirectional.centerEnd),
|
||
top('上', AlignmentDirectional.topCenter),
|
||
bottom('下', AlignmentDirectional.bottomCenter);
|
||
|
||
final String label;
|
||
final AlignmentDirectional alignment;
|
||
const CanvasStackPosition(this.label, this.alignment);
|
||
|
||
/// 常用叠层位置 — 面板UI展示的精简列表(6个最常用位置)
|
||
static const List<CanvasStackPosition> commonPositions = [
|
||
bottomRight,
|
||
bottomLeft,
|
||
topRight,
|
||
topLeft,
|
||
center,
|
||
right,
|
||
];
|
||
}
|
||
|
||
// ============================================================
|
||
// 画布样式模型
|
||
// ============================================================
|
||
|
||
class CanvasStyleModel {
|
||
// ─── 圆角 ───
|
||
final double borderRadius;
|
||
final bool useIndependentRadius;
|
||
final double borderRadiusTL;
|
||
final double borderRadiusTR;
|
||
final double borderRadiusBL;
|
||
final double borderRadiusBR;
|
||
|
||
// ─── 边框 ───
|
||
final CanvasBorderStyle borderStyle;
|
||
final double borderWidth;
|
||
final Color borderColor;
|
||
|
||
// ─── 外阴影 ───
|
||
final double shadowBlur;
|
||
final double shadowOpacity;
|
||
final Color shadowColor;
|
||
final double shadowSpread;
|
||
final double shadowOffsetX;
|
||
final double shadowOffsetY;
|
||
|
||
// ─── 内阴影 ───
|
||
final double innerShadowBlur;
|
||
final double innerShadowOpacity;
|
||
final double innerShadowOffsetX;
|
||
final double innerShadowOffsetY;
|
||
|
||
// ─── 叠层 ───
|
||
final int stackLayers;
|
||
final double stackDistance;
|
||
final CanvasStackPosition stackPosition;
|
||
|
||
// ─── 外边距 ───
|
||
final double outerMargin;
|
||
|
||
// ─── 计算属性 ───
|
||
|
||
bool get hasShadow => shadowBlur > 0 && shadowOpacity > 0;
|
||
bool get hasInnerShadow => innerShadowBlur > 0 && innerShadowOpacity > 0;
|
||
bool get hasStack => stackLayers > 1;
|
||
bool get hasOuterMargin => outerMargin > 0;
|
||
bool get hasBorder => borderStyle != CanvasBorderStyle.none;
|
||
|
||
/// 获取有效圆角 — 独立模式返回TL值,统一模式返回borderRadius
|
||
double get effectiveBorderRadius =>
|
||
useIndependentRadius ? borderRadiusTL : borderRadius;
|
||
|
||
static const CanvasStyleModel defaults = CanvasStyleModel._();
|
||
|
||
const CanvasStyleModel._({
|
||
this.borderRadius = 0,
|
||
this.useIndependentRadius = false,
|
||
this.borderRadiusTL = 0,
|
||
this.borderRadiusTR = 0,
|
||
this.borderRadiusBL = 0,
|
||
this.borderRadiusBR = 0,
|
||
this.borderStyle = CanvasBorderStyle.none,
|
||
this.borderWidth = 1.0,
|
||
this.borderColor = const Color(0xFFFFFFFF),
|
||
this.shadowBlur = 0,
|
||
this.shadowOpacity = 0,
|
||
this.shadowColor = const Color(0xFF000000),
|
||
this.shadowSpread = 0,
|
||
this.shadowOffsetX = 0,
|
||
this.shadowOffsetY = 4,
|
||
this.innerShadowBlur = 0,
|
||
this.innerShadowOpacity = 0,
|
||
this.innerShadowOffsetX = 0,
|
||
this.innerShadowOffsetY = 2,
|
||
this.stackLayers = 1,
|
||
this.stackDistance = 8,
|
||
this.stackPosition = CanvasStackPosition.bottomRight,
|
||
this.outerMargin = 0,
|
||
});
|
||
|
||
factory CanvasStyleModel.fromJson(Map<String, dynamic> json) {
|
||
return CanvasStyleModel._(
|
||
borderRadius: (json['borderRadius'] as num?)?.toDouble() ?? 0,
|
||
useIndependentRadius:
|
||
(json['useIndependentRadius'] as bool?) ?? false,
|
||
borderRadiusTL:
|
||
(json['borderRadiusTL'] as num?)?.toDouble() ??
|
||
(json['borderRadius'] as num?)?.toDouble() ??
|
||
0,
|
||
borderRadiusTR:
|
||
(json['borderRadiusTR'] as num?)?.toDouble() ??
|
||
(json['borderRadius'] as num?)?.toDouble() ??
|
||
0,
|
||
borderRadiusBL:
|
||
(json['borderRadiusBL'] as num?)?.toDouble() ??
|
||
(json['borderRadius'] as num?)?.toDouble() ??
|
||
0,
|
||
borderRadiusBR:
|
||
(json['borderRadiusBR'] as num?)?.toDouble() ??
|
||
(json['borderRadius'] as num?)?.toDouble() ??
|
||
0,
|
||
borderStyle: CanvasBorderStyle.values.firstWhere(
|
||
(e) => e.name == json['borderStyle'],
|
||
orElse: () => CanvasBorderStyle.none,
|
||
),
|
||
borderWidth: (json['borderWidth'] as num?)?.toDouble() ?? 1.0,
|
||
borderColor: _colorFromJson(json['borderColor']),
|
||
shadowBlur: (json['shadowBlur'] as num?)?.toDouble() ?? 0,
|
||
shadowOpacity: (json['shadowOpacity'] as num?)?.toDouble() ?? 0,
|
||
shadowColor: json.containsKey('shadowColor')
|
||
? _colorFromJson(json['shadowColor'])
|
||
: const Color(0xFF000000),
|
||
shadowSpread: (json['shadowSpread'] as num?)?.toDouble() ?? 0,
|
||
shadowOffsetX: (json['shadowOffsetX'] as num?)?.toDouble() ?? 0,
|
||
shadowOffsetY: (json['shadowOffsetY'] as num?)?.toDouble() ?? 4,
|
||
innerShadowBlur:
|
||
(json['innerShadowBlur'] as num?)?.toDouble() ?? 0,
|
||
innerShadowOpacity:
|
||
(json['innerShadowOpacity'] as num?)?.toDouble() ?? 0,
|
||
innerShadowOffsetX:
|
||
(json['innerShadowOffsetX'] as num?)?.toDouble() ?? 0,
|
||
innerShadowOffsetY:
|
||
(json['innerShadowOffsetY'] as num?)?.toDouble() ?? 2,
|
||
stackLayers: (json['stackLayers'] as num?)?.toInt() ?? 1,
|
||
stackDistance: (json['stackDistance'] as num?)?.toDouble() ?? 8,
|
||
stackPosition: CanvasStackPosition.values.firstWhere(
|
||
(e) => e.name == json['stackPosition'],
|
||
orElse: () => CanvasStackPosition.bottomRight,
|
||
),
|
||
outerMargin: (json['outerMargin'] as num?)?.toDouble() ?? 0,
|
||
);
|
||
}
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'borderRadius': borderRadius,
|
||
'useIndependentRadius': useIndependentRadius,
|
||
'borderRadiusTL': borderRadiusTL,
|
||
'borderRadiusTR': borderRadiusTR,
|
||
'borderRadiusBL': borderRadiusBL,
|
||
'borderRadiusBR': borderRadiusBR,
|
||
'borderStyle': borderStyle.name,
|
||
'borderWidth': borderWidth,
|
||
'borderColor': borderColor.toARGB32(),
|
||
'shadowBlur': shadowBlur,
|
||
'shadowOpacity': shadowOpacity,
|
||
'shadowColor': shadowColor.toARGB32(),
|
||
'shadowSpread': shadowSpread,
|
||
'shadowOffsetX': shadowOffsetX,
|
||
'shadowOffsetY': shadowOffsetY,
|
||
'innerShadowBlur': innerShadowBlur,
|
||
'innerShadowOpacity': innerShadowOpacity,
|
||
'innerShadowOffsetX': innerShadowOffsetX,
|
||
'innerShadowOffsetY': innerShadowOffsetY,
|
||
'stackLayers': stackLayers,
|
||
'stackDistance': stackDistance,
|
||
'stackPosition': stackPosition.name,
|
||
'outerMargin': outerMargin,
|
||
};
|
||
|
||
CanvasStyleModel copyWith({
|
||
double? borderRadius,
|
||
bool? useIndependentRadius,
|
||
double? borderRadiusTL,
|
||
double? borderRadiusTR,
|
||
double? borderRadiusBL,
|
||
double? borderRadiusBR,
|
||
CanvasBorderStyle? borderStyle,
|
||
double? borderWidth,
|
||
Color? borderColor,
|
||
double? shadowBlur,
|
||
double? shadowOpacity,
|
||
Color? shadowColor,
|
||
double? shadowSpread,
|
||
double? shadowOffsetX,
|
||
double? shadowOffsetY,
|
||
double? innerShadowBlur,
|
||
double? innerShadowOpacity,
|
||
double? innerShadowOffsetX,
|
||
double? innerShadowOffsetY,
|
||
int? stackLayers,
|
||
double? stackDistance,
|
||
CanvasStackPosition? stackPosition,
|
||
double? outerMargin,
|
||
}) {
|
||
return CanvasStyleModel._(
|
||
borderRadius: borderRadius ?? this.borderRadius,
|
||
useIndependentRadius:
|
||
useIndependentRadius ?? this.useIndependentRadius,
|
||
borderRadiusTL: borderRadiusTL ?? this.borderRadiusTL,
|
||
borderRadiusTR: borderRadiusTR ?? this.borderRadiusTR,
|
||
borderRadiusBL: borderRadiusBL ?? this.borderRadiusBL,
|
||
borderRadiusBR: borderRadiusBR ?? this.borderRadiusBR,
|
||
borderStyle: borderStyle ?? this.borderStyle,
|
||
borderWidth: borderWidth ?? this.borderWidth,
|
||
borderColor: borderColor ?? this.borderColor,
|
||
shadowBlur: shadowBlur ?? this.shadowBlur,
|
||
shadowOpacity: shadowOpacity ?? this.shadowOpacity,
|
||
shadowColor: shadowColor ?? this.shadowColor,
|
||
shadowSpread: shadowSpread ?? this.shadowSpread,
|
||
shadowOffsetX: shadowOffsetX ?? this.shadowOffsetX,
|
||
shadowOffsetY: shadowOffsetY ?? this.shadowOffsetY,
|
||
innerShadowBlur: innerShadowBlur ?? this.innerShadowBlur,
|
||
innerShadowOpacity: innerShadowOpacity ?? this.innerShadowOpacity,
|
||
innerShadowOffsetX: innerShadowOffsetX ?? this.innerShadowOffsetX,
|
||
innerShadowOffsetY: innerShadowOffsetY ?? this.innerShadowOffsetY,
|
||
stackLayers: stackLayers ?? this.stackLayers,
|
||
stackDistance: stackDistance ?? this.stackDistance,
|
||
stackPosition: stackPosition ?? this.stackPosition,
|
||
outerMargin: outerMargin ?? this.outerMargin,
|
||
);
|
||
}
|
||
|
||
static Color _colorFromJson(dynamic value) {
|
||
if (value is int) return Color(value);
|
||
if (value is String) {
|
||
final hex = value.replaceFirst('#', '');
|
||
return Color(int.parse('FF$hex', radix: 16));
|
||
}
|
||
return const Color(0xFFFFFFFF);
|
||
}
|
||
|
||
/// 导出为JSON字符串 — 用于样式分享
|
||
String exportToJsonString() => const JsonEncoder.withIndent(' ').convert(toJson());
|
||
|
||
/// 从JSON字符串导入样式
|
||
static CanvasStyleModel? tryFromJsonString(String jsonStr) {
|
||
try {
|
||
final map = jsonDecode(jsonStr) as Map<String, dynamic>;
|
||
return CanvasStyleModel.fromJson(map);
|
||
} catch (_) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
@override
|
||
String toString() => 'CanvasStyleModel(${jsonEncode(toJson())})';
|
||
}
|