Files
MookNote/lib/models/data_models.dart
DelLevin-Home a84d4c03e8 界面完善
2026-06-20 21:32:23 +08:00

703 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();
/// 影视条目模型
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 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.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: json['release_date'] != null
? DateTime.parse(json['release_date'])
: null,
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: json['rating']?.toDouble(),
status: json['status'] ?? 'want_to_watch',
watchDate: json['watch_date'] != null
? DateTime.parse(json['watch_date'])
: null,
createdAt: json['created_at'] != null
? DateTime.parse(json['created_at'])
: DateTime.now(),
updatedAt: json['updated_at'] != null
? DateTime.parse(json['updated_at'])
: DateTime.now(),
isDeleted: json['is_deleted'] == 1 || json['is_deleted'] == true,
coverOffset: (json['cover_offset'] ?? 0.0).toDouble(),
);
}
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,
'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!);
}
/// 解析字符串列表公共静态方法供Book使用
static List<String> parseStringList(dynamic data) {
if (data == null) return [];
if (data is List) {
return data.map((e) => e.toString()).toList();
}
if (data is String) {
try {
// 尝试解析JSON字符串
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 [];
}
/// 解析字符串列表(私有别名,保持兼容性)
static List<String> _parseStringList(dynamic data) => parseStringList(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,
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,
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: json['rating']?.toDouble(),
status: json['status'] ?? 'want_to_read',
isbn: json['isbn'],
publishDate: json['publish_date'] != null
? DateTime.parse(json['publish_date'])
: null,
createdAt: json['created_at'] != null
? DateTime.parse(json['created_at'])
: DateTime.now(),
updatedAt: json['updated_at'] != null
? DateTime.parse(json['updated_at'])
: DateTime.now(),
isDeleted: json['is_deleted'] == 1 || json['is_deleted'] == true,
coverOffset: (json['cover_offset'] ?? 0.0).toDouble(),
);
}
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;
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,
});
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: json['created_at'] != null
? DateTime.parse(json['created_at'])
: DateTime.now(),
updatedAt: json['updated_at'] != null
? DateTime.parse(json['updated_at'])
: DateTime.now(),
isDeleted: json['is_deleted'] == 1 || json['is_deleted'] == 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,
};
}
/// 复制并修改
Note copyWith({
String? id,
String? title,
String? content,
String? contentType,
List<String>? tags,
List<String>? images,
DateTime? createdAt,
DateTime? updatedAt,
bool? isDeleted,
}) {
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,
);
}
/// 获取内容摘要前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: json['created_at'] != null
? DateTime.parse(json['created_at'])
: DateTime.now(),
updatedAt: json['updated_at'] != null
? DateTime.parse(json['updated_at'])
: 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: json['created_at'] != null
? DateTime.parse(json['created_at'])
: 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: json['created_at'] != null
? DateTime.parse(json['created_at'])
: DateTime.now(),
updatedAt: json['updated_at'] != null
? DateTime.parse(json['updated_at'])
: 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: json['created_at'] != null
? DateTime.parse(json['created_at'])
: DateTime.now(),
updatedAt: json['updated_at'] != null
? DateTime.parse(json['updated_at'])
: 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)}...';
}
}