此提交包含多项变更: 1. 新增鸿蒙平台支持,完善设备检测与数据库适配 2. 替换旧版分享插件API为SharePlus 3. 批量迁移StateNotifier到Notifier以适配新版Riverpod 4. 修复zip编码判断、图表API参数等bug 5. 更新应用图标、启动页资源与多尺寸适配图标 6. 调整Android最小SDK版本与应用名称 7. 优化日志打印与正则表达式使用 8. 修正编辑器画布样式初始化与配置逻辑 9. 更新依赖与CI插件配置
627 lines
21 KiB
Dart
627 lines
21 KiB
Dart
// ============================================================
|
||
// 闲言APP — 草稿服务
|
||
// 创建时间: 2026-04-20
|
||
// 更新时间: 2026-05-16
|
||
// 作用: 基于 SharedPreferences 的本地草稿管理 + 缩略图生成
|
||
// 上次更新: 集成ImageCompressService缩略图生成
|
||
// ============================================================
|
||
|
||
import 'dart:convert';
|
||
import 'dart:typed_data';
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:pro_image_editor/pro_image_editor.dart' as pro;
|
||
import 'package:shared_preferences/shared_preferences.dart';
|
||
import 'package:xianyan/editor/models/editor_models.dart';
|
||
import 'package:xianyan/editor/services/export/image_compress_service.dart';
|
||
|
||
/// 草稿数据模型
|
||
class DraftItem {
|
||
final String id;
|
||
final String data;
|
||
final String hash;
|
||
final DateTime createdAt;
|
||
final Map<String, dynamic> preview;
|
||
final String? thumbnailBase64;
|
||
|
||
const DraftItem({
|
||
required this.id,
|
||
required this.data,
|
||
required this.hash,
|
||
required this.createdAt,
|
||
required this.preview,
|
||
this.thumbnailBase64,
|
||
});
|
||
|
||
factory DraftItem.fromJson(Map<String, dynamic> json) {
|
||
return DraftItem(
|
||
id: json['id'] as String,
|
||
data: json['data'] as String,
|
||
hash: json['hash'] as String,
|
||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||
preview: Map<String, dynamic>.from(json['preview'] as Map),
|
||
thumbnailBase64: json['thumbnailBase64'] as String?,
|
||
);
|
||
}
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'id': id,
|
||
'data': data,
|
||
'hash': hash,
|
||
'createdAt': createdAt.toIso8601String(),
|
||
'preview': preview,
|
||
if (thumbnailBase64 != null) 'thumbnailBase64': thumbnailBase64,
|
||
};
|
||
|
||
Uint8List? get thumbnailBytes {
|
||
if (thumbnailBase64 == null) return null;
|
||
try {
|
||
return base64Decode(thumbnailBase64!);
|
||
} catch (_) {
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 草稿服务 — 基于 SharedPreferences 的本地草稿管理
|
||
class DraftService {
|
||
static const _draftKey = 'editor_drafts';
|
||
static const _maxDrafts = 20;
|
||
|
||
/// 保存草稿(自动去重:相同内容不重复存储)
|
||
static Future<void> save(QuoteCanvasModel canvas) async {
|
||
try {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
final drafts = await getAll();
|
||
final canvasJson = _canvasToJson(canvas);
|
||
final hash = _hashCanvas(canvas);
|
||
|
||
drafts.removeWhere((d) => d.hash == hash);
|
||
drafts.insert(
|
||
0,
|
||
DraftItem(
|
||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||
data: canvasJson,
|
||
hash: hash,
|
||
createdAt: DateTime.now(),
|
||
preview: _buildPreview(canvas),
|
||
),
|
||
);
|
||
|
||
if (drafts.length > _maxDrafts) drafts.removeLast();
|
||
|
||
await prefs.setString(
|
||
_draftKey,
|
||
jsonEncode(drafts.map((d) => d.toJson()).toList()),
|
||
);
|
||
} catch (_) {}
|
||
}
|
||
|
||
/// 保存草稿并生成缩略图
|
||
static Future<void> saveWithThumbnail(
|
||
QuoteCanvasModel canvas,
|
||
Uint8List imageBytes,
|
||
) async {
|
||
try {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
final drafts = await getAll();
|
||
final canvasJson = _canvasToJson(canvas);
|
||
final hash = _hashCanvas(canvas);
|
||
|
||
String? thumbBase64;
|
||
final thumb = await ImageCompressService.compressThumbnail(imageBytes);
|
||
if (thumb != null) {
|
||
thumbBase64 = base64Encode(thumb);
|
||
}
|
||
|
||
drafts.removeWhere((d) => d.hash == hash);
|
||
drafts.insert(
|
||
0,
|
||
DraftItem(
|
||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||
data: canvasJson,
|
||
hash: hash,
|
||
createdAt: DateTime.now(),
|
||
preview: _buildPreview(canvas),
|
||
thumbnailBase64: thumbBase64,
|
||
),
|
||
);
|
||
|
||
if (drafts.length > _maxDrafts) drafts.removeLast();
|
||
|
||
await prefs.setString(
|
||
_draftKey,
|
||
jsonEncode(drafts.map((d) => d.toJson()).toList()),
|
||
);
|
||
} catch (_) {}
|
||
}
|
||
|
||
/// 获取所有草稿
|
||
static Future<List<DraftItem>> getAll() async {
|
||
try {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
final raw = prefs.getString(_draftKey);
|
||
if (raw == null || raw.isEmpty) return [];
|
||
|
||
final list = jsonDecode(raw) as List;
|
||
return list
|
||
.map((e) => DraftItem.fromJson(Map<String, dynamic>.from(e as Map)))
|
||
.toList();
|
||
} catch (_) {
|
||
return [];
|
||
}
|
||
}
|
||
|
||
/// 加载指定草稿为画布模型
|
||
static QuoteCanvasModel? loadCanvas(DraftItem draft) {
|
||
try {
|
||
final json = jsonDecode(draft.data) as Map<String, dynamic>;
|
||
return _jsonToCanvas(json);
|
||
} catch (_) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// 删除草稿
|
||
static Future<void> delete(String id) async {
|
||
try {
|
||
final drafts = await getAll();
|
||
drafts.removeWhere((d) => d.id == id);
|
||
final prefs = await SharedPreferences.getInstance();
|
||
if (drafts.isEmpty) {
|
||
await prefs.remove(_draftKey);
|
||
} else {
|
||
await prefs.setString(
|
||
_draftKey,
|
||
jsonEncode(drafts.map((d) => d.toJson()).toList()),
|
||
);
|
||
}
|
||
} catch (_) {}
|
||
}
|
||
|
||
/// 清空所有草稿
|
||
static Future<void> clearAll() async {
|
||
try {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
await prefs.remove(_draftKey);
|
||
} catch (_) {}
|
||
}
|
||
|
||
/// 获取草稿数量
|
||
static Future<int> count() async {
|
||
final drafts = await getAll();
|
||
return drafts.length;
|
||
}
|
||
|
||
static String _canvasToJson(QuoteCanvasModel c) {
|
||
return jsonEncode({
|
||
'aspectRatio': c.aspectRatio.name,
|
||
'showGrid': c.showGrid,
|
||
'gridSize': c.gridSize,
|
||
'background': {
|
||
'type': c.background.type.name,
|
||
'solidColor': c.background.solidColor.toARGB32(),
|
||
'gradientColors': c.background.gradientColors
|
||
.map((e) => e.toARGB32())
|
||
.toList(),
|
||
'gradientStops': c.background.gradientStops,
|
||
'gradientAngle': c.background.gradientAngle,
|
||
'imagePath': c.background.imagePath,
|
||
'blurSigma': c.background.blurSigma,
|
||
'imageOpacity': c.background.imageOpacity,
|
||
},
|
||
'textLayers': c.textLayers
|
||
.map(
|
||
(l) => {
|
||
'id': l.id,
|
||
'text': l.text,
|
||
'fontSize': l.fontSize,
|
||
'fontWeight': l.fontWeight.value,
|
||
'fontFamily': l.fontFamily,
|
||
'color': l.color.toARGB32(),
|
||
'textAlign': l.textAlign.name,
|
||
'offsetX': l.offsetX,
|
||
'offsetY': l.offsetY,
|
||
'rotation': l.rotation,
|
||
'scale': l.scale,
|
||
'letterSpacing': l.letterSpacing,
|
||
'lineHeight': l.lineHeight,
|
||
'italic': l.italic,
|
||
'underline': l.underline,
|
||
'strikethrough': l.strikethrough,
|
||
'strokeColor': l.strokeColor?.toARGB32(),
|
||
'strokeWidth': l.strokeWidth,
|
||
'shadowColor': l.shadowColor?.toARGB32(),
|
||
'shadowBlur': l.shadowBlur,
|
||
'shadowOffsetX': l.shadowOffsetX,
|
||
'shadowOffsetY': l.shadowOffsetY,
|
||
'visible': l.visible,
|
||
'locked': l.locked,
|
||
'zIndex': l.zIndex,
|
||
},
|
||
)
|
||
.toList(),
|
||
'watermarkStyle': c.watermarkStyle.name,
|
||
'watermarkText': c.watermarkText,
|
||
'glassCardLayers': c.glassCardLayers
|
||
.map(
|
||
(g) => {
|
||
'id': g.id,
|
||
'style': g.style.name,
|
||
'offsetX': g.offsetX,
|
||
'offsetY': g.offsetY,
|
||
'width': g.width,
|
||
'height': g.height,
|
||
'rotation': g.rotation,
|
||
'scale': g.scale,
|
||
'borderRadius': g.borderRadius,
|
||
'blurIntensity': g.blurIntensity,
|
||
'opacity': g.opacity,
|
||
'borderOpacity': g.borderOpacity,
|
||
'highlightStrength': g.highlightStrength,
|
||
'showHighlight': g.showHighlight,
|
||
'showInnerShadow': g.showInnerShadow,
|
||
'tintColor': g.tintColor.toARGB32(),
|
||
'visible': g.visible,
|
||
'locked': g.locked,
|
||
'zIndex': g.zIndex,
|
||
'containedTextIds': g.containedTextIds,
|
||
'containedStickerIds': g.containedStickerIds,
|
||
},
|
||
)
|
||
.toList(),
|
||
'stickerLayers': c.stickerLayers
|
||
.map(
|
||
(s) => {
|
||
'id': s.id,
|
||
'type': s.type.name,
|
||
'assetPath': s.assetPath,
|
||
'size': s.size,
|
||
'offsetX': s.offsetX,
|
||
'offsetY': s.offsetY,
|
||
'rotation': s.rotation,
|
||
'scale': s.scale,
|
||
'opacity': s.opacity,
|
||
'visible': s.visible,
|
||
'locked': s.locked,
|
||
'zIndex': s.zIndex,
|
||
},
|
||
)
|
||
.toList(),
|
||
'annotationLayers': c.annotationLayers
|
||
.map(
|
||
(a) => {
|
||
'id': a.id,
|
||
'type': a.type.name,
|
||
'points': a.points.map((p) => {'x': p.x, 'y': p.y}).toList(),
|
||
'strokeWidth': a.strokeWidth,
|
||
'color': a.color.toARGB32(),
|
||
'opacity': a.opacity,
|
||
'visible': a.visible,
|
||
},
|
||
)
|
||
.toList(),
|
||
'isLiquidGlassMode': c.isLiquidGlassMode,
|
||
});
|
||
}
|
||
|
||
static QuoteCanvasModel _jsonToCanvas(Map<String, dynamic> json) {
|
||
final ratioName = json['aspectRatio'] as String? ?? 'ratio9_16';
|
||
final ratio = CanvasAspectRatio.values.firstWhere(
|
||
(r) => r.name == ratioName,
|
||
orElse: () => CanvasAspectRatio.ratio9_16,
|
||
);
|
||
|
||
final bg = json['background'] as Map<String, dynamic>? ?? {};
|
||
final bgType = BackgroundType.values.firstWhere(
|
||
(t) => t.name == (bg['type'] as String? ?? 'solid'),
|
||
orElse: () => BackgroundType.solid,
|
||
);
|
||
|
||
final colorsRaw = bg['gradientColors'] as List? ?? [];
|
||
final gradientColors = colorsRaw.map((e) => Color(e as int)).toList();
|
||
if (gradientColors.length < 2) {
|
||
gradientColors.insert(0, const Color(0xFF6C63FF));
|
||
gradientColors.add(const Color(0xFF4ECDC4));
|
||
}
|
||
|
||
final stopsRaw = bg['gradientStops'] as List? ?? [0.0, 1.0];
|
||
final gradientStops = stopsRaw.map((e) => (e as num).toDouble()).toList();
|
||
|
||
final layersRaw = json['textLayers'] as List? ?? [];
|
||
final textLayers = layersRaw.map((l) {
|
||
final map = l as Map<String, dynamic>;
|
||
return TextLayer(
|
||
id: map['id'] as String? ?? '',
|
||
text: map['text'] as String? ?? '',
|
||
fontSize: (map['fontSize'] as num?)?.toDouble() ?? 24.0,
|
||
fontWeight: _parseFontWeight(map['fontWeight']),
|
||
fontFamily: map['fontFamily'] as String? ?? 'Inter',
|
||
color: Color(map['color'] as int? ?? 0xFF1A1A2E),
|
||
textAlign: _parseTextAlign(map['textAlign']),
|
||
offsetX: (map['offsetX'] as num?)?.toDouble() ?? 0.0,
|
||
offsetY: (map['offsetY'] as num?)?.toDouble() ?? 0.0,
|
||
rotation: (map['rotation'] as num?)?.toDouble() ?? 0.0,
|
||
scale: (map['scale'] as num?)?.toDouble() ?? 1.0,
|
||
letterSpacing: (map['letterSpacing'] as num?)?.toDouble() ?? 0.0,
|
||
lineHeight: (map['lineHeight'] as num?)?.toDouble() ?? 1.5,
|
||
italic: map['italic'] as bool? ?? false,
|
||
underline: map['underline'] as bool? ?? false,
|
||
strikethrough: map['strikethrough'] as bool? ?? false,
|
||
strokeColor: map['strokeColor'] != null
|
||
? Color(map['strokeColor'] as int)
|
||
: null,
|
||
strokeWidth: (map['strokeWidth'] as num?)?.toDouble() ?? 0.0,
|
||
shadowColor: map['shadowColor'] != null
|
||
? Color(map['shadowColor'] as int)
|
||
: null,
|
||
shadowBlur: (map['shadowBlur'] as num?)?.toDouble() ?? 0.0,
|
||
shadowOffsetX: (map['shadowOffsetX'] as num?)?.toDouble() ?? 0.0,
|
||
shadowOffsetY: (map['shadowOffsetY'] as num?)?.toDouble() ?? 0.0,
|
||
visible: map['visible'] as bool? ?? true,
|
||
locked: map['locked'] as bool? ?? false,
|
||
zIndex: map['zIndex'] as int? ?? 0,
|
||
);
|
||
}).toList();
|
||
|
||
return QuoteCanvasModel(
|
||
aspectRatio: ratio,
|
||
showGrid: json['showGrid'] as bool? ?? false,
|
||
gridSize: (json['gridSize'] as num?)?.toDouble() ?? 40.0,
|
||
background: BackgroundLayer(
|
||
type: bgType,
|
||
solidColor: Color(bg['solidColor'] as int? ?? 0xFF6C63FF),
|
||
gradientColors: gradientColors,
|
||
gradientStops: gradientStops,
|
||
gradientAngle: (bg['gradientAngle'] as num?)?.toDouble() ?? 135.0,
|
||
imagePath: bg['imagePath'] as String?,
|
||
blurSigma: (bg['blurSigma'] as num?)?.toDouble() ?? 0.0,
|
||
imageOpacity: (bg['imageOpacity'] as num?)?.toDouble() ?? 1.0,
|
||
),
|
||
textLayers: textLayers,
|
||
glassCardLayers: _jsonToGlassCards(json['glassCardLayers'] as List?),
|
||
stickerLayers: _jsonToStickers(json['stickerLayers'] as List?),
|
||
annotationLayers: _jsonToAnnotations(json['annotationLayers'] as List?),
|
||
watermarkStyle: WatermarkStyle.values.firstWhere(
|
||
(w) => w.name == (json['watermarkStyle'] as String? ?? 'none'),
|
||
orElse: () => WatermarkStyle.none,
|
||
),
|
||
watermarkText: json['watermarkText'] as String?,
|
||
isLiquidGlassMode: json['isLiquidGlassMode'] as bool? ?? false,
|
||
);
|
||
}
|
||
|
||
static String _hashCanvas(QuoteCanvasModel c) =>
|
||
'${c.background.type.name}_${c.background.solidColor.toARGB32()}_${c.textLayers.map((l) => "${l.id}_${l.text}").join("_")}';
|
||
|
||
static Map<String, dynamic> _buildPreview(QuoteCanvasModel c) => {
|
||
'text': c.textLayers.isNotEmpty ? c.textLayers.first.text : '',
|
||
'bgType': c.background.type.name,
|
||
'layerCount': c.textLayers.length,
|
||
};
|
||
|
||
static List<GlassCardLayer> _jsonToGlassCards(List<dynamic>? raw) {
|
||
if (raw == null) return [];
|
||
return raw.map((g) {
|
||
final map = g as Map<String, dynamic>;
|
||
return GlassCardLayer(
|
||
id: map['id'] as String? ?? '',
|
||
style: GlassStyle.values.firstWhere(
|
||
(s) => s.name == (map['style'] as String? ?? 'crystal'),
|
||
orElse: () => GlassStyle.crystal,
|
||
),
|
||
offsetX: (map['offsetX'] as num?)?.toDouble() ?? 0.0,
|
||
offsetY: (map['offsetY'] as num?)?.toDouble() ?? 0.0,
|
||
width: (map['width'] as num?)?.toDouble() ?? 200.0,
|
||
height: (map['height'] as num?)?.toDouble() ?? 150.0,
|
||
rotation: (map['rotation'] as num?)?.toDouble() ?? 0.0,
|
||
scale: (map['scale'] as num?)?.toDouble() ?? 1.0,
|
||
borderRadius: (map['borderRadius'] as num?)?.toDouble() ?? 20.0,
|
||
blurIntensity: (map['blurIntensity'] as num?)?.toDouble() ?? 25.0,
|
||
opacity: (map['opacity'] as num?)?.toDouble() ?? 0.65,
|
||
borderOpacity: (map['borderOpacity'] as num?)?.toDouble() ?? 0.15,
|
||
highlightStrength:
|
||
(map['highlightStrength'] as num?)?.toDouble() ?? 0.4,
|
||
showHighlight: map['showHighlight'] as bool? ?? true,
|
||
showInnerShadow: map['showInnerShadow'] as bool? ?? true,
|
||
tintColor: Color(map['tintColor'] as int? ?? 0xFFFFFFFF),
|
||
visible: map['visible'] as bool? ?? true,
|
||
locked: map['locked'] as bool? ?? false,
|
||
zIndex: map['zIndex'] as int? ?? 10,
|
||
containedTextIds: List<String>.from(
|
||
map['containedTextIds'] as List? ?? [],
|
||
),
|
||
containedStickerIds: List<String>.from(
|
||
map['containedStickerIds'] as List? ?? [],
|
||
),
|
||
);
|
||
}).toList();
|
||
}
|
||
|
||
static List<StickerLayer> _jsonToStickers(List<dynamic>? raw) {
|
||
if (raw == null) return [];
|
||
return raw.map((s) {
|
||
final map = s as Map<String, dynamic>;
|
||
return StickerLayer(
|
||
id: map['id'] as String? ?? '',
|
||
type: StickerType.values.firstWhere(
|
||
(t) => t.name == (map['type'] as String? ?? 'emoji'),
|
||
orElse: () => StickerType.emoji,
|
||
),
|
||
assetPath: map['assetPath'] as String? ?? '',
|
||
size: (map['size'] as num?)?.toDouble() ?? 80.0,
|
||
offsetX: (map['offsetX'] as num?)?.toDouble() ?? 0.0,
|
||
offsetY: (map['offsetY'] as num?)?.toDouble() ?? 0.0,
|
||
rotation: (map['rotation'] as num?)?.toDouble() ?? 0.0,
|
||
scale: (map['scale'] as num?)?.toDouble() ?? 1.0,
|
||
opacity: (map['opacity'] as num?)?.toDouble() ?? 1.0,
|
||
visible: map['visible'] as bool? ?? true,
|
||
locked: map['locked'] as bool? ?? false,
|
||
zIndex: map['zIndex'] as int? ?? 0,
|
||
);
|
||
}).toList();
|
||
}
|
||
|
||
static List<AnnotationLayer> _jsonToAnnotations(List<dynamic>? raw) {
|
||
if (raw == null) return [];
|
||
return raw.map((a) {
|
||
final map = a as Map<String, dynamic>;
|
||
final pointsRaw = map['points'] as List? ?? [];
|
||
final points = pointsRaw.map((p) {
|
||
final pm = p as Map<String, dynamic>;
|
||
return AnnotationPoint(
|
||
x: (pm['x'] as num?)?.toDouble() ?? 0.0,
|
||
y: (pm['y'] as num?)?.toDouble() ?? 0.0,
|
||
);
|
||
}).toList();
|
||
return AnnotationLayer(
|
||
id: map['id'] as String? ?? '',
|
||
type: AnnotationType.values.firstWhere(
|
||
(t) => t.name == (map['type'] as String? ?? 'line'),
|
||
orElse: () => AnnotationType.line,
|
||
),
|
||
points: points,
|
||
strokeWidth: (map['strokeWidth'] as num?)?.toDouble() ?? 3.0,
|
||
color: Color(map['color'] as int? ?? 0xFFFF0000),
|
||
opacity: (map['opacity'] as num?)?.toDouble() ?? 1.0,
|
||
visible: map['visible'] as bool? ?? true,
|
||
);
|
||
}).toList();
|
||
}
|
||
|
||
// ============================================================
|
||
// pro_image_editor 草稿支持
|
||
// ============================================================
|
||
|
||
static const _proDraftKey = 'editor_pro_drafts';
|
||
|
||
/// 保存 pro 编辑器草稿 (传入 JSON 字符串)
|
||
static Future<void> saveProDraft(
|
||
String stateHistoryJson, {
|
||
String? draftId,
|
||
}) async {
|
||
try {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
final id = draftId ?? DateTime.now().millisecondsSinceEpoch.toString();
|
||
|
||
final drafts = await getAllProDrafts();
|
||
drafts.removeWhere((d) => d.id == id);
|
||
drafts.insert(
|
||
0,
|
||
ProDraftItem(id: id, data: stateHistoryJson, createdAt: DateTime.now()),
|
||
);
|
||
|
||
if (drafts.length > _maxDrafts) drafts.removeLast();
|
||
|
||
await prefs.setString(
|
||
_proDraftKey,
|
||
jsonEncode(drafts.map((d) => d.toJson()).toList()),
|
||
);
|
||
} catch (e) {
|
||
debugPrint('saveProDraft error: $e');
|
||
}
|
||
}
|
||
|
||
/// 获取所有 pro 草稿
|
||
static Future<List<ProDraftItem>> getAllProDrafts() async {
|
||
try {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
final raw = prefs.getString(_proDraftKey);
|
||
if (raw == null || raw.isEmpty) return [];
|
||
|
||
final list = jsonDecode(raw) as List;
|
||
return list
|
||
.map(
|
||
(e) => ProDraftItem.fromJson(Map<String, dynamic>.from(e as Map)),
|
||
)
|
||
.toList();
|
||
} catch (e) {
|
||
return [];
|
||
}
|
||
}
|
||
|
||
/// 加载 pro 草稿为 ImportStateHistory
|
||
static pro.ImportStateHistory? loadProDraft(ProDraftItem draft) {
|
||
try {
|
||
return pro.ImportStateHistory.fromJson(draft.data);
|
||
} catch (e) {
|
||
debugPrint('loadProDraft error: $e');
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// 删除 pro 草稿
|
||
static Future<void> deleteProDraft(String id) async {
|
||
try {
|
||
final drafts = await getAllProDrafts();
|
||
drafts.removeWhere((d) => d.id == id);
|
||
final prefs = await SharedPreferences.getInstance();
|
||
if (drafts.isEmpty) {
|
||
await prefs.remove(_proDraftKey);
|
||
} else {
|
||
await prefs.setString(
|
||
_proDraftKey,
|
||
jsonEncode(drafts.map((d) => d.toJson()).toList()),
|
||
);
|
||
}
|
||
} catch (e) {
|
||
debugPrint('deleteProDraft error: $e');
|
||
}
|
||
}
|
||
|
||
/// 解析 FontWeight,兼容旧格式(index 0-8)和新格式(value 100-900)
|
||
static FontWeight _parseFontWeight(dynamic raw) {
|
||
if (raw == null) return FontWeight.normal;
|
||
if (raw is int) {
|
||
if (raw <= 8) return FontWeight.values[raw.clamp(0, 8)];
|
||
final idx = (raw ~/ 100) - 1;
|
||
return FontWeight.values[idx.clamp(0, 8)];
|
||
}
|
||
return FontWeight.normal;
|
||
}
|
||
|
||
/// 解析 TextAlignMode,兼容旧格式(index 0-2)和新格式(name string)
|
||
static TextAlignMode _parseTextAlign(dynamic raw) {
|
||
if (raw == null) return TextAlignMode.center;
|
||
if (raw is int)
|
||
return TextAlignMode.values[raw.clamp(
|
||
0,
|
||
TextAlignMode.values.length - 1,
|
||
)];
|
||
if (raw is String) {
|
||
return TextAlignMode.values.firstWhere(
|
||
(t) => t.name == raw,
|
||
orElse: () => TextAlignMode.center,
|
||
);
|
||
}
|
||
return TextAlignMode.center;
|
||
}
|
||
}
|
||
|
||
/// pro 编辑器草稿数据模型
|
||
class ProDraftItem {
|
||
final String id;
|
||
final String data;
|
||
final DateTime createdAt;
|
||
|
||
const ProDraftItem({
|
||
required this.id,
|
||
required this.data,
|
||
required this.createdAt,
|
||
});
|
||
|
||
factory ProDraftItem.fromJson(Map<String, dynamic> json) {
|
||
return ProDraftItem(
|
||
id: json['id'] as String,
|
||
data: json['data'] as String,
|
||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||
);
|
||
}
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'id': id,
|
||
'data': data,
|
||
'createdAt': createdAt.toIso8601String(),
|
||
};
|
||
}
|