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 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 directors; // 导演列表 final List writers; // 编剧列表 final List actors; // 主演列表 final List genres; // 类型 final List 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 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 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 parseStringList(dynamic data) => parseStringListGeneric(data); /// 解析字符串列表(私有别名,保持兼容性) static List _parseStringList(dynamic data) => parseStringListGeneric(data); /// 复制并修改 Movie copyWith({ String? id, String? title, Object? posterPath = _copyWithNull, DateTime? releaseDate, List? directors, List? writers, List? actors, List? genres, List? 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 authors; // 作者列表 final List alternateTitles; // 别名 final String? publisher; // 出版社 final List 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 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 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? authors, List? alternateTitles, String? publisher, List? 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 tags; final List 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 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 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? tags, List? 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 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 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 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 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 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 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 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 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)}...'; } }