Files
MookNote/lib/models/data_models.dart
DelLevin-Home 622e534b84 代码优化
2026-06-30 17:51:39 +08:00

700 lines
20 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:io';
import 'dart:convert';
/// 用于区分 copyWith 中"未传参数"和"传了 null"的标记
class _CopyWithNullSentinel {
const _CopyWithNullSentinel();
}
const _copyWithNull = _CopyWithNullSentinel();
/// 安全解析日期字符串,失败时返回 fallback
DateTime? _safeParseDate(String? str, {DateTime? fallback}) {
if (str == null || str.isEmpty) return fallback;
return DateTime.tryParse(str)?.toLocal() ?? fallback;
}
/// 安全将动态值转为 double失败时返回 fallback
double? _safeParseDouble(dynamic value, {double? fallback}) {
if (value == null) return fallback;
if (value is double) return value;
if (value is int) return value.toDouble();
if (value is num) return value.toDouble();
if (value is String) return double.tryParse(value) ?? fallback;
return fallback;
}
/// 解析字符串列表(通用工具函数,不限于 Movie
List<String> parseStringListGeneric(dynamic data) {
if (data == null) return [];
if (data is List) {
return data.map((e) => e.toString()).toList();
}
if (data is String) {
try {
final decoded = jsonDecode(data);
if (decoded is List) {
return decoded.map((e) => e.toString()).toList();
}
} catch (e) {
return data.split(',').map((s) => s.trim()).where((s) => s.isNotEmpty).toList();
}
}
return [];
}
/// 影视条目模型
class Movie {
final String id;
final String title; // 影视名称
final String? posterPath; // 本地海报路径
final DateTime? releaseDate; // 上映时间
final List<String> directors; // 导演列表
final List<String> writers; // 编剧列表
final List<String> actors; // 主演列表
final List<String> genres; // 类型
final List<String> alternateTitles; // 别名
final String? summary; // 剧情简介
final double? rating; // 评分 1-10
final String status; // watched/want_to_watch/watching
final String category; // 影视分类: movie/tv/anime/variety/documentary/short
final DateTime? watchDate; // 观看日期
final DateTime createdAt;
final DateTime updatedAt;
final bool isDeleted;
final double coverOffset; // 封面偏移量
Movie({
required this.id,
required this.title,
this.posterPath,
this.releaseDate,
this.directors = const [],
this.writers = const [],
this.actors = const [],
this.genres = const [],
this.alternateTitles = const [],
this.summary,
this.rating,
required this.status,
this.category = 'movie',
this.watchDate,
required this.createdAt,
required this.updatedAt,
this.isDeleted = false,
this.coverOffset = 0.0,
});
factory Movie.fromJson(Map<String, dynamic> json) {
return Movie(
id: json['id'] ?? '',
title: json['title'] ?? '',
posterPath: json['poster_path'],
releaseDate: _safeParseDate(json['release_date']),
directors: _parseStringList(json['directors']),
writers: _parseStringList(json['writers']),
actors: _parseStringList(json['actors']),
genres: _parseStringList(json['genres']),
alternateTitles: _parseStringList(json['alternate_titles']),
summary: json['summary'],
rating: _safeParseDouble(json['rating']),
status: json['status'] ?? 'want_to_watch',
category: json['category'] ?? 'movie',
watchDate: _safeParseDate(json['watch_date']),
createdAt: _safeParseDate(json['created_at'], fallback: DateTime.now())!,
updatedAt: _safeParseDate(json['updated_at'], fallback: DateTime.now())!,
isDeleted: json['is_deleted'] == 1 || json['is_deleted'] == true,
coverOffset: _safeParseDouble(json['cover_offset'], fallback: 0.0)!,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'poster_path': posterPath,
'release_date': releaseDate?.toUtc().toIso8601String(),
'directors': jsonEncode(directors),
'writers': jsonEncode(writers),
'actors': jsonEncode(actors),
'genres': jsonEncode(genres),
'alternate_titles': jsonEncode(alternateTitles),
'summary': summary,
'rating': rating,
'status': status,
'category': category,
'watch_date': watchDate?.toUtc().toIso8601String(),
'created_at': createdAt.toUtc().toIso8601String(),
'updated_at': updatedAt.toUtc().toIso8601String(),
'is_deleted': isDeleted ? 1 : 0,
'cover_offset': coverOffset,
};
}
/// 获取封面文件
File? get posterFile {
if (posterPath == null || posterPath!.isEmpty) return null;
return File(posterPath!);
}
/// 解析字符串列表(公共静态方法,供外部使用)
static List<String> parseStringList(dynamic data) => parseStringListGeneric(data);
/// 解析字符串列表(私有别名,保持兼容性)
static List<String> _parseStringList(dynamic data) => parseStringListGeneric(data);
/// 复制并修改
Movie copyWith({
String? id,
String? title,
Object? posterPath = _copyWithNull,
DateTime? releaseDate,
List<String>? directors,
List<String>? writers,
List<String>? actors,
List<String>? genres,
List<String>? alternateTitles,
Object? summary = _copyWithNull,
Object? rating = _copyWithNull,
String? status,
String? category,
DateTime? watchDate,
DateTime? createdAt,
DateTime? updatedAt,
bool? isDeleted,
double? coverOffset,
}) {
return Movie(
id: id ?? this.id,
title: title ?? this.title,
posterPath: posterPath is _CopyWithNullSentinel ? this.posterPath : (posterPath as String?),
releaseDate: releaseDate ?? this.releaseDate,
directors: directors ?? this.directors,
writers: writers ?? this.writers,
actors: actors ?? this.actors,
genres: genres ?? this.genres,
alternateTitles: alternateTitles ?? this.alternateTitles,
summary: summary is _CopyWithNullSentinel ? this.summary : (summary as String?),
rating: rating is _CopyWithNullSentinel ? this.rating : (rating as double?),
status: status ?? this.status,
category: category ?? this.category,
watchDate: watchDate ?? this.watchDate,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
isDeleted: isDeleted ?? this.isDeleted,
coverOffset: coverOffset ?? this.coverOffset,
);
}
}
/// 书籍条目模型
class Book {
final String id;
final String title; // 书籍名称
final String? coverPath; // 本地封面路径
final List<String> authors; // 作者列表
final List<String> alternateTitles; // 别名
final String? publisher; // 出版社
final List<String> genres; // 类型
final String? summary; // 书籍简介
final double? rating; // 评分 1-10
final String status; // read/reading/want_to_read
final String? isbn; // ISBN编号
final DateTime? publishDate; // 出版时间
final DateTime createdAt;
final DateTime updatedAt;
final bool isDeleted;
final double coverOffset; // 封面偏移量
Book({
required this.id,
required this.title,
this.coverPath,
this.authors = const [],
this.alternateTitles = const [],
this.publisher,
this.genres = const [],
this.summary,
this.rating,
required this.status,
this.isbn,
this.publishDate,
required this.createdAt,
required this.updatedAt,
this.isDeleted = false,
this.coverOffset = 0.0,
});
factory Book.fromJson(Map<String, dynamic> json) {
return Book(
id: json['id'] ?? '',
title: json['title'] ?? '',
coverPath: json['cover_path'],
authors: Movie.parseStringList(json['authors']),
alternateTitles: Movie.parseStringList(json['alternate_titles']),
publisher: json['publisher'],
genres: Movie.parseStringList(json['genres']),
summary: json['summary'],
rating: _safeParseDouble(json['rating']),
status: json['status'] ?? 'want_to_read',
isbn: json['isbn'],
publishDate: _safeParseDate(json['publish_date']),
createdAt: _safeParseDate(json['created_at'], fallback: DateTime.now())!,
updatedAt: _safeParseDate(json['updated_at'], fallback: DateTime.now())!,
isDeleted: json['is_deleted'] == 1 || json['is_deleted'] == true,
coverOffset: _safeParseDouble(json['cover_offset'], fallback: 0.0)!,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'cover_path': coverPath,
'authors': jsonEncode(authors),
'alternate_titles': jsonEncode(alternateTitles),
'publisher': publisher,
'genres': jsonEncode(genres),
'summary': summary,
'rating': rating,
'status': status,
'isbn': isbn,
'publish_date': publishDate?.toUtc().toIso8601String(),
'created_at': createdAt.toUtc().toIso8601String(),
'updated_at': updatedAt.toUtc().toIso8601String(),
'is_deleted': isDeleted ? 1 : 0,
'cover_offset': coverOffset,
};
}
/// 获取封面文件
File? get coverFile {
if (coverPath == null || coverPath!.isEmpty) return null;
return File(coverPath!);
}
/// 复制并修改
Book copyWith({
String? id,
String? title,
Object? coverPath = _copyWithNull,
List<String>? authors,
List<String>? alternateTitles,
String? publisher,
List<String>? genres,
Object? summary = _copyWithNull,
Object? rating = _copyWithNull,
String? status,
Object? isbn = _copyWithNull,
DateTime? publishDate,
DateTime? createdAt,
DateTime? updatedAt,
bool? isDeleted,
double? coverOffset,
}) {
return Book(
id: id ?? this.id,
title: title ?? this.title,
coverPath: coverPath is _CopyWithNullSentinel ? this.coverPath : (coverPath as String?),
authors: authors ?? this.authors,
alternateTitles: alternateTitles ?? this.alternateTitles,
publisher: publisher ?? this.publisher,
genres: genres ?? this.genres,
summary: summary is _CopyWithNullSentinel ? this.summary : (summary as String?),
rating: rating is _CopyWithNullSentinel ? this.rating : (rating as double?),
status: status ?? this.status,
isbn: isbn is _CopyWithNullSentinel ? this.isbn : (isbn as String?),
publishDate: publishDate ?? this.publishDate,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
isDeleted: isDeleted ?? this.isDeleted,
coverOffset: coverOffset ?? this.coverOffset,
);
}
}
/// 笔记模型
class Note {
final String id;
final String title;
final String content;
final String contentType; // 内容类型markdown
final List<String> tags;
final List<String> images; // 图片路径列表
final DateTime createdAt;
final DateTime updatedAt;
final bool isDeleted;
final bool isPinned;
Note({
required this.id,
required this.title,
required this.content,
this.contentType = 'markdown',
this.tags = const [],
this.images = const [],
required this.createdAt,
required this.updatedAt,
this.isDeleted = false,
this.isPinned = false,
});
factory Note.fromJson(Map<String, dynamic> json) {
return Note(
id: json['id']?.toString() ?? '',
title: json['title'] ?? '',
content: json['content'] ?? '',
contentType: json['content_type'] ?? 'markdown',
tags: Movie.parseStringList(json['tags']),
images: Movie.parseStringList(json['images']),
createdAt: _safeParseDate(json['created_at'], fallback: DateTime.now())!,
updatedAt: _safeParseDate(json['updated_at'], fallback: DateTime.now())!,
isDeleted: json['is_deleted'] == 1 || json['is_deleted'] == true,
isPinned: json['is_pinned'] == 1 || json['is_pinned'] == true,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'content': content,
'content_type': contentType,
'tags': jsonEncode(tags),
'images': jsonEncode(images),
'created_at': createdAt.toUtc().toIso8601String(),
'updated_at': updatedAt.toUtc().toIso8601String(),
'is_deleted': isDeleted ? 1 : 0,
'is_pinned': isPinned ? 1 : 0,
};
}
/// 复制并修改
Note copyWith({
String? id,
String? title,
String? content,
String? contentType,
List<String>? tags,
List<String>? images,
DateTime? createdAt,
DateTime? updatedAt,
bool? isDeleted,
bool? isPinned,
}) {
return Note(
id: id ?? this.id,
title: title ?? this.title,
content: content ?? this.content,
contentType: contentType ?? this.contentType,
tags: tags ?? this.tags,
images: images ?? this.images,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
isDeleted: isDeleted ?? this.isDeleted,
isPinned: isPinned ?? this.isPinned,
);
}
/// 获取内容摘要前100字
String get summary {
if (content.length <= 100) return content;
return '${content.substring(0, 100)}...';
}
}
/// 影评模型
class MovieReview {
final String id;
final String movieId;
final String content;
final String reviewer;
final String source;
final int reviewType; // 1: 短评, 2: 长评
final bool isDeleted;
final DateTime createdAt;
final DateTime updatedAt;
MovieReview({
required this.id,
required this.movieId,
required this.content,
this.reviewer = '',
this.source = '',
this.reviewType = 1,
this.isDeleted = false,
required this.createdAt,
required this.updatedAt,
});
factory MovieReview.fromJson(Map<String, dynamic> json) {
return MovieReview(
id: json['id']?.toString() ?? '',
movieId: json['movie_id']?.toString() ?? '',
content: json['content'] ?? '',
reviewer: json['reviewer'] ?? '',
source: json['source'] ?? '',
reviewType: json['review_type'] ?? 1,
isDeleted: json['is_deleted'] == 1 || json['is_deleted'] == true,
createdAt: _safeParseDate(json['created_at'], fallback: DateTime.now())!,
updatedAt: _safeParseDate(json['updated_at'], fallback: DateTime.now())!,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'movie_id': movieId,
'content': content,
'reviewer': reviewer,
'source': source,
'review_type': reviewType,
'is_deleted': isDeleted ? 1 : 0,
'created_at': createdAt.toUtc().toIso8601String(),
'updated_at': updatedAt.toUtc().toIso8601String(),
};
}
/// 复制并修改
MovieReview copyWith({
String? id,
String? movieId,
String? content,
String? reviewer,
String? source,
int? reviewType,
bool? isDeleted,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return MovieReview(
id: id ?? this.id,
movieId: movieId ?? this.movieId,
content: content ?? this.content,
reviewer: reviewer ?? this.reviewer,
source: source ?? this.source,
reviewType: reviewType ?? this.reviewType,
isDeleted: isDeleted ?? this.isDeleted,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
/// 获取评论摘要
String get summary {
if (content.length <= 50) return content;
return '${content.substring(0, 50)}...';
}
/// 评论类型文本
String get typeText => reviewType == 1 ? '短评' : '长评';
}
/// 影视海报墙模型
class MoviePoster {
final String id;
final String movieId;
final String posterPath;
final bool isDeleted;
final DateTime createdAt;
MoviePoster({
required this.id,
required this.movieId,
required this.posterPath,
this.isDeleted = false,
required this.createdAt,
});
factory MoviePoster.fromJson(Map<String, dynamic> json) {
return MoviePoster(
id: json['id']?.toString() ?? '',
movieId: json['movie_id']?.toString() ?? '',
posterPath: json['poster_path'] ?? '',
isDeleted: json['is_deleted'] == 1 || json['is_deleted'] == true,
createdAt: _safeParseDate(json['created_at'], fallback: DateTime.now())!,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'movie_id': movieId,
'poster_path': posterPath,
'is_deleted': isDeleted ? 1 : 0,
'created_at': createdAt.toUtc().toIso8601String(),
};
}
/// 获取海报文件
File? get posterFile {
if (posterPath.isEmpty) return null;
return File(posterPath);
}
}
/// 书评模型
class BookReview {
final String id;
final String bookId;
final String content;
final String reviewer;
final String source;
final int reviewType; // 1: 短评, 2: 长评
final bool isDeleted;
final DateTime createdAt;
final DateTime updatedAt;
BookReview({
required this.id,
required this.bookId,
required this.content,
this.reviewer = '',
this.source = '',
this.reviewType = 1,
this.isDeleted = false,
required this.createdAt,
required this.updatedAt,
});
factory BookReview.fromJson(Map<String, dynamic> json) {
return BookReview(
id: json['id']?.toString() ?? '',
bookId: json['book_id']?.toString() ?? '',
content: json['content'] ?? '',
reviewer: json['reviewer'] ?? '',
source: json['source'] ?? '',
reviewType: json['review_type'] ?? 1,
isDeleted: json['is_deleted'] == 1 || json['is_deleted'] == true,
createdAt: _safeParseDate(json['created_at'], fallback: DateTime.now())!,
updatedAt: _safeParseDate(json['updated_at'], fallback: DateTime.now())!,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'book_id': bookId,
'content': content,
'reviewer': reviewer,
'source': source,
'review_type': reviewType,
'is_deleted': isDeleted ? 1 : 0,
'created_at': createdAt.toUtc().toIso8601String(),
'updated_at': updatedAt.toUtc().toIso8601String(),
};
}
/// 复制并修改
BookReview copyWith({
String? id,
String? bookId,
String? content,
String? reviewer,
String? source,
int? reviewType,
bool? isDeleted,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return BookReview(
id: id ?? this.id,
bookId: bookId ?? this.bookId,
content: content ?? this.content,
reviewer: reviewer ?? this.reviewer,
source: source ?? this.source,
reviewType: reviewType ?? this.reviewType,
isDeleted: isDeleted ?? this.isDeleted,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
/// 获取评论摘要
String get summary {
if (content.length <= 50) return content;
return '${content.substring(0, 50)}...';
}
/// 评论类型文本
String get typeText => reviewType == 1 ? '短评' : '长评';
}
/// 书籍摘抄模型
class BookExcerpt {
final String id;
final String bookId;
final String chapter; // 章节
final String content; // 摘抄内容
final String comment; // 摘抄的评论/感悟
final bool isDeleted;
final DateTime createdAt;
final DateTime updatedAt;
BookExcerpt({
required this.id,
required this.bookId,
this.chapter = '',
required this.content,
this.comment = '',
this.isDeleted = false,
required this.createdAt,
required this.updatedAt,
});
factory BookExcerpt.fromJson(Map<String, dynamic> json) {
return BookExcerpt(
id: json['id']?.toString() ?? '',
bookId: json['book_id']?.toString() ?? '',
chapter: json['chapter'] ?? '',
content: json['content'] ?? '',
comment: json['comment'] ?? '',
isDeleted: json['is_deleted'] == 1 || json['is_deleted'] == true,
createdAt: _safeParseDate(json['created_at'], fallback: DateTime.now())!,
updatedAt: _safeParseDate(json['updated_at'], fallback: DateTime.now())!,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'book_id': bookId,
'chapter': chapter,
'content': content,
'comment': comment,
'is_deleted': isDeleted ? 1 : 0,
'created_at': createdAt.toUtc().toIso8601String(),
'updated_at': updatedAt.toUtc().toIso8601String(),
};
}
/// 复制并修改
BookExcerpt copyWith({
String? id,
String? bookId,
String? chapter,
String? content,
String? comment,
bool? isDeleted,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return BookExcerpt(
id: id ?? this.id,
bookId: bookId ?? this.bookId,
chapter: chapter ?? this.chapter,
content: content ?? this.content,
comment: comment ?? this.comment,
isDeleted: isDeleted ?? this.isDeleted,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
/// 获取摘抄摘要
String get summary {
if (content.length <= 50) return content;
return '${content.substring(0, 50)}...';
}
}