// ============================================================ // 闲言APP — 统一图层模型 (freezed sealed class) // 创建时间: 2026-04-25 // 更新时间: 2026-04-25 // 作用: 统一 TextLayer/StickerLayer/GlassCardLayer/AnnotationLayer 为 LayerInfo // 上次更新: 移除重复枚举定义,复用 editor_models.dart 中的类型 // ============================================================ import 'package:flutter/material.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'editor_models.dart'; part 'layer_models.freezed.dart'; // ============================================================ // 图层类型枚举 // ============================================================ enum LayerType { text('📝', '文字'), sticker('🎭', '贴纸'), glass('🧊', '玻璃'), annotation('✏️', '标注'); const LayerType(this.emoji, this.label); final String emoji; final String label; } // ============================================================ // 统一图层模型 — freezed sealed class // ============================================================ @freezed sealed class LayerInfo with _$LayerInfo { const LayerInfo._(); // ─── 文字层 ─── const factory LayerInfo.text({ required String id, @Default('') String name, required String text, @Default(24.0) double fontSize, @Default(FontWeight.normal) FontWeight fontWeight, @Default('Inter') String fontFamily, @Default(Color(0xFF1A1A2E)) Color color, @Default(TextAlignMode.center) TextAlignMode textAlign, @Default(0.0) double offsetX, @Default(0.0) double offsetY, @Default(0.0) double rotation, @Default(1.0) double scale, @Default(0.0) double letterSpacing, @Default(1.5) double lineHeight, @Default(false) bool italic, @Default(false) bool underline, @Default(false) bool strikethrough, Color? strokeColor, @Default(0.0) double strokeWidth, Color? shadowColor, @Default(0.0) double shadowBlur, @Default(0.0) double shadowOffsetX, @Default(0.0) double shadowOffsetY, @Default(true) bool visible, @Default(false) bool locked, @Default(1.0) double opacity, @Default(0) int zIndex, }) = TextLayerInfo; // ─── 贴纸层 ─── const factory LayerInfo.sticker({ required String id, @Default('') String name, @Default(StickerType.emoji) StickerType stickerType, required String assetPath, @Default(80.0) double size, @Default(0.0) double offsetX, @Default(0.0) double offsetY, @Default(0.0) double rotation, @Default(1.0) double scale, @Default(1.0) double opacity, @Default(true) bool visible, @Default(false) bool locked, @Default(0) int zIndex, }) = StickerLayerInfo; // ─── 玻璃层 ─── const factory LayerInfo.glass({ required String id, @Default('') String name, @Default(GlassStyle.crystal) GlassStyle style, @Default(0.0) double offsetX, @Default(0.0) double offsetY, @Default(200.0) double width, @Default(150.0) double height, @Default(0.0) double rotation, @Default(1.0) double scale, @Default(20.0) double borderRadius, @Default(25.0) double blurIntensity, @Default(0.65) double opacity, @Default(0.15) double borderOpacity, @Default(0.4) double highlightStrength, @Default(true) bool showHighlight, @Default(true) bool showInnerShadow, @Default(Colors.white) Color tintColor, @Default(true) bool visible, @Default(false) bool locked, @Default(10) int zIndex, @Default([]) List containedTextIds, @Default([]) List containedStickerIds, }) = GlassLayerInfo; // ─── 标注层 ─── const factory LayerInfo.annotation({ required String id, @Default('') String name, required AnnotationType annotationType, required List points, @Default(3.0) double strokeWidth, @Default(Colors.red) Color color, @Default(1.0) double opacity, @Default(true) bool visible, @Default(false) bool locked, @Default(0) int zIndex, }) = AnnotationLayerInfo; // ─── 公共接口 ─── String get layerId => switch (this) { TextLayerInfo(:final id) => id, StickerLayerInfo(:final id) => id, GlassLayerInfo(:final id) => id, AnnotationLayerInfo(:final id) => id, }; String get layerName => switch (this) { TextLayerInfo(:final name) => name.isNotEmpty ? name : '文字层', StickerLayerInfo(:final name) => name.isNotEmpty ? name : '贴纸层', GlassLayerInfo(:final name) => name.isNotEmpty ? name : '玻璃层', AnnotationLayerInfo(:final name) => name.isNotEmpty ? name : '标注层', }; LayerType get layerType => switch (this) { TextLayerInfo() => LayerType.text, StickerLayerInfo() => LayerType.sticker, GlassLayerInfo() => LayerType.glass, AnnotationLayerInfo() => LayerType.annotation, }; bool get isVisible => switch (this) { TextLayerInfo(:final visible) => visible, StickerLayerInfo(:final visible) => visible, GlassLayerInfo(:final visible) => visible, AnnotationLayerInfo(:final visible) => visible, }; bool get isLocked => switch (this) { TextLayerInfo(:final locked) => locked, StickerLayerInfo(:final locked) => locked, GlassLayerInfo(:final locked) => locked, AnnotationLayerInfo(:final locked) => locked, }; double get layerOpacity => switch (this) { TextLayerInfo(:final opacity) => opacity, StickerLayerInfo(:final opacity) => opacity, GlassLayerInfo(:final opacity) => opacity, AnnotationLayerInfo(:final opacity) => opacity, }; double get layerOffsetX => switch (this) { TextLayerInfo(:final offsetX) => offsetX, StickerLayerInfo(:final offsetX) => offsetX, GlassLayerInfo(:final offsetX) => offsetX, AnnotationLayerInfo() => 0.0, }; double get layerOffsetY => switch (this) { TextLayerInfo(:final offsetY) => offsetY, StickerLayerInfo(:final offsetY) => offsetY, GlassLayerInfo(:final offsetY) => offsetY, AnnotationLayerInfo() => 0.0, }; double get layerRotation => switch (this) { TextLayerInfo(:final rotation) => rotation, StickerLayerInfo(:final rotation) => rotation, GlassLayerInfo(:final rotation) => rotation, AnnotationLayerInfo() => 0.0, }; double get layerScale => switch (this) { TextLayerInfo(:final scale) => scale, StickerLayerInfo(:final scale) => scale, GlassLayerInfo(:final scale) => scale, AnnotationLayerInfo() => 1.0, }; int get layerZIndex => switch (this) { TextLayerInfo(:final zIndex) => zIndex, StickerLayerInfo(:final zIndex) => zIndex, GlassLayerInfo(:final zIndex) => zIndex, AnnotationLayerInfo(:final zIndex) => zIndex, }; String get layerEmoji => layerType.emoji; LayerInfo withVisibility(bool visible) => switch (this) { TextLayerInfo() => (this as TextLayerInfo).copyWith(visible: visible), StickerLayerInfo() => (this as StickerLayerInfo).copyWith(visible: visible), GlassLayerInfo() => (this as GlassLayerInfo).copyWith(visible: visible), AnnotationLayerInfo() => (this as AnnotationLayerInfo).copyWith( visible: visible, ), }; LayerInfo withLocked(bool locked) => switch (this) { TextLayerInfo() => (this as TextLayerInfo).copyWith(locked: locked), StickerLayerInfo() => (this as StickerLayerInfo).copyWith(locked: locked), GlassLayerInfo() => (this as GlassLayerInfo).copyWith(locked: locked), AnnotationLayerInfo() => (this as AnnotationLayerInfo).copyWith( locked: locked, ), }; LayerInfo withOpacity(double opacity) => switch (this) { TextLayerInfo() => (this as TextLayerInfo).copyWith(opacity: opacity), StickerLayerInfo() => (this as StickerLayerInfo).copyWith(opacity: opacity), GlassLayerInfo() => (this as GlassLayerInfo).copyWith(opacity: opacity), AnnotationLayerInfo() => (this as AnnotationLayerInfo).copyWith( opacity: opacity, ), }; LayerInfo withName(String name) => switch (this) { TextLayerInfo() => (this as TextLayerInfo).copyWith(name: name), StickerLayerInfo() => (this as StickerLayerInfo).copyWith(name: name), GlassLayerInfo() => (this as GlassLayerInfo).copyWith(name: name), AnnotationLayerInfo() => (this as AnnotationLayerInfo).copyWith(name: name), }; LayerInfo withZIndex(int zIndex) => switch (this) { TextLayerInfo() => (this as TextLayerInfo).copyWith(zIndex: zIndex), StickerLayerInfo() => (this as StickerLayerInfo).copyWith(zIndex: zIndex), GlassLayerInfo() => (this as GlassLayerInfo).copyWith(zIndex: zIndex), AnnotationLayerInfo() => (this as AnnotationLayerInfo).copyWith( zIndex: zIndex, ), }; }