更新数据库版本

This commit is contained in:
DelLevin-Home
2026-07-03 00:13:35 +08:00
parent f2bb3f72db
commit 48c1b05410
7 changed files with 223 additions and 12 deletions

View File

@@ -93,6 +93,7 @@ class _BookDetailPageState extends State<BookDetailPage> {
if (book.isbn != null && book.isbn!.isNotEmpty) _buildIsbnSection(book),
if (book.publisher != null && book.publisher!.isNotEmpty) _buildPublisherSection(book),
if (book.publishDate != null) _buildPublishDateSection(book),
if (book.startDate != null || book.finishDate != null) _buildReadingDatesSection(book),
Divider(height: 0.5, thickness: 0.5, color: colors.outline),
if (book.summary != null && book.summary!.isNotEmpty) _buildSummarySection(book),
Divider(height: 0.5, thickness: 0.5, color: colors.outline),
@@ -192,6 +193,7 @@ class _BookDetailPageState extends State<BookDetailPage> {
if (book.isbn != null && book.isbn!.isNotEmpty) _buildIsbnSection(book),
if (book.publisher != null && book.publisher!.isNotEmpty) _buildPublisherSection(book),
if (book.publishDate != null) _buildPublishDateSection(book),
if (book.startDate != null || book.finishDate != null) _buildReadingDatesSection(book),
// 类型标签毛玻璃
if (book.genres.isNotEmpty) _buildGenresSection(book),
// 简介:内部已有毛玻璃卡片
@@ -855,6 +857,59 @@ class _BookDetailPageState extends State<BookDetailPage> {
);
}
Widget _buildReadingDatesSection(Book book) {
final isOverlay = _detailStyle == 1;
final colors = Theme.of(context).colorScheme;
return Padding(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: isOverlay ? 5 : 16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 64,
child: Text(
'阅读日期',
style: TextStyle(
fontSize: 13,
color: isOverlay ? const Color(0x66FFFFFF) : colors.onSurface.withValues(alpha: 0.4),
),
),
),
Expanded(
child: Wrap(
spacing: 12,
runSpacing: 8,
children: [
if (book.startDate != null)
_buildDateChip('开始', book.startDate!, isOverlay),
if (book.finishDate != null)
_buildDateChip('读完', book.finishDate!, isOverlay),
],
),
),
],
),
);
}
Widget _buildDateChip(String label, DateTime date, bool isOverlay) {
final colors = Theme.of(context).colorScheme;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: isOverlay ? Colors.white.withValues(alpha: 0.12) : colors.surfaceContainerHighest,
borderRadius: BorderRadius.circular(6),
),
child: Text(
'$label ${_formatDate(date)}',
style: TextStyle(
fontSize: 13,
color: isOverlay ? Colors.white.withValues(alpha: 0.85) : colors.onSurface.withValues(alpha: 0.7),
),
),
);
}
Widget _buildGenresSection(Book book) {
final isOverlay = _detailStyle == 1;
return Padding(

View File

@@ -48,6 +48,8 @@ class _BookFormPageState extends State<BookFormPage> {
String? _coverPath;
String _status = 'want_to_read';
DateTime? _publishDate;
DateTime? _startDate;
DateTime? _finishDate;
bool _isDownloading = false;
@override
@@ -77,6 +79,8 @@ class _BookFormPageState extends State<BookFormPage> {
_coverPath = book.coverPath;
_status = book.status;
_publishDate = book.publishDate;
_startDate = book.startDate;
_finishDate = book.finishDate;
} else if (widget.initialStatus != null) {
_status = widget.initialStatus!;
}
@@ -208,6 +212,14 @@ class _BookFormPageState extends State<BookFormPage> {
),
),
// 开始阅读日期 + 读完日期(同一行)
_halfCard('开始阅读', _startDate != null ? '${_startDate!.year}.${_startDate!.month.toString().padLeft(2, '0')}.${_startDate!.day.toString().padLeft(2, '0')}' : '', Icons.play_circle_outlined,
onTap: () => _selectStartDate(),
),
_halfCard('读完日期', _finishDate != null ? '${_finishDate!.year}.${_finishDate!.month.toString().padLeft(2, '0')}.${_finishDate!.day.toString().padLeft(2, '0')}' : '', Icons.check_circle_outlined,
onTap: () => _selectFinishDate(),
),
// 书籍简介
SizedBox(
width: double.infinity,
@@ -536,6 +548,16 @@ class _BookFormPageState extends State<BookFormPage> {
if (picked != null) setState(() => _publishDate = picked);
}
Future<void> _selectStartDate() async {
final picked = await showDatePicker(context: context, initialDate: _startDate ?? DateTime.now(), firstDate: DateTime(1900), lastDate: DateTime.now().add(const Duration(days: 365 * 5)));
if (picked != null) setState(() => _startDate = picked);
}
Future<void> _selectFinishDate() async {
final picked = await showDatePicker(context: context, initialDate: _finishDate ?? DateTime.now(), firstDate: DateTime(1900), lastDate: DateTime.now().add(const Duration(days: 365 * 5)));
if (picked != null) setState(() => _finishDate = picked);
}
Future<void> _editSummary() async {
final result = await Navigator.push<String>(context, MaterialPageRoute(builder: (_) => _SummaryEditorPage(initialText: _summaryController.text)));
if (result != null) setState(() => _summaryController.text = result);
@@ -551,6 +573,8 @@ class _BookFormPageState extends State<BookFormPage> {
if (_coverPath != null) return true;
if (_authors.isNotEmpty || _alternateTitles.isNotEmpty || _genres.isNotEmpty) return true;
if (_publishDate != null) return true;
if (_startDate != null) return true;
if (_finishDate != null) return true;
return false;
}
@@ -594,7 +618,7 @@ class _BookFormPageState extends State<BookFormPage> {
authors: _authors, alternateTitles: _alternateTitles, publisher: _publisherController.text.trim(),
genres: _genres, summary: _summaryController.text.trim(), rating: rating, status: _status,
isbn: _isbnController.text.trim().isNotEmpty ? _isbnController.text.trim() : null,
publishDate: _publishDate, createdAt: now, updatedAt: now,
publishDate: _publishDate, startDate: _startDate, finishDate: _finishDate, createdAt: now, updatedAt: now,
);
await context.read<AppProvider>().addBook(newBook);
} else {
@@ -603,7 +627,7 @@ class _BookFormPageState extends State<BookFormPage> {
authors: _authors, alternateTitles: _alternateTitles, publisher: _publisherController.text.trim(),
genres: _genres, summary: _summaryController.text.trim(), rating: rating, status: _status,
isbn: _isbnController.text.trim().isNotEmpty ? _isbnController.text.trim() : null,
publishDate: _publishDate, updatedAt: now,
publishDate: _publishDate, startDate: _startDate, finishDate: _finishDate, updatedAt: now,
);
await context.read<AppProvider>().updateBook(updatedBook);
}

View File

@@ -44,12 +44,14 @@ class _EpubDetailPageState extends State<EpubDetailPage> {
bool _descriptionExpanded = false;
Future<List<BookExcerpt>>? _excerptsFuture;
Future<List<Map<String, dynamic>>>? _highlightsFuture;
String? _linkedBookCoverPath;
@override
void initState() {
super.initState();
_book = widget.book;
_loadBookInfo();
_loadLinkedBookCover();
final linkedBookId = _book['book_id'] as String? ?? '';
if (linkedBookId.isNotEmpty) {
_excerptsFuture = BookExcerptDao().getExcerptsByBookId(linkedBookId);
@@ -64,12 +66,22 @@ class _EpubDetailPageState extends State<EpubDetailPage> {
if (mounted && info != null) setState(() => _bookInfo = info);
}
Future<void> _loadLinkedBookCover() async {
final linkedBookId = _book['book_id'] as String? ?? '';
if (linkedBookId.isEmpty) return;
final book = await _bookDao.getBookById(linkedBookId);
if (mounted && book != null && book.coverPath != null && book.coverPath!.isNotEmpty) {
setState(() => _linkedBookCoverPath = book.coverPath);
}
}
Future<void> _refreshBook() async {
final updated = await _dao.getReaderBookById(widget.bookId);
if (mounted && updated != null) {
final linkedBookId = updated['book_id'] as String? ?? '';
setState(() {
_book = updated;
_linkedBookCoverPath = null;
_highlightsFuture = _dao.getHighlightsByBookId(widget.bookId);
if (linkedBookId.isNotEmpty) {
_excerptsFuture = BookExcerptDao().getExcerptsByBookId(linkedBookId);
@@ -77,10 +89,12 @@ class _EpubDetailPageState extends State<EpubDetailPage> {
_excerptsFuture = null;
}
});
_loadLinkedBookCover();
}
}
void _navigateToReader() {
final coverPath = _linkedBookCoverPath ?? _book['cover_path'] as String?;
Navigator.push(
context,
MaterialPageRoute(
@@ -88,7 +102,7 @@ class _EpubDetailPageState extends State<EpubDetailPage> {
bookId: _book['id'] as String,
filePath: _book['file_path'] as String,
title: _book['title'] as String? ?? '',
coverPath: _book['cover_path'] as String?,
coverPath: coverPath,
bookData: _book,
),
),
@@ -113,7 +127,7 @@ class _EpubDetailPageState extends State<EpubDetailPage> {
final progress = (_book['reading_percentage'] as num?)?.toDouble() ?? 0.0;
final title = _book['title'] as String? ?? '';
final author = _book['author'] as String? ?? '';
final coverPath = _book['cover_path'] as String?;
final coverPath = _linkedBookCoverPath ?? _book['cover_path'] as String?;
return Scaffold(
backgroundColor: colors.surface,
@@ -755,6 +769,7 @@ class _EpubDetailPageState extends State<EpubDetailPage> {
final chapter = int.tryParse(highlight['chapter'] as String? ?? '') ?? 0;
final xpath = _extractStartXPath(highlight['cfi'] as String? ?? '');
final text = highlight['content'] as String? ?? '';
final coverPath = _linkedBookCoverPath ?? _book['cover_path'] as String?;
Navigator.push(
context,
MaterialPageRoute(
@@ -762,7 +777,7 @@ class _EpubDetailPageState extends State<EpubDetailPage> {
bookId: _book['id'] as String,
filePath: _book['file_path'] as String,
title: _book['title'] as String? ?? '',
coverPath: _book['cover_path'] as String?,
coverPath: coverPath,
bookData: _book,
initialSpineIndex: chapter,
scrollToXPath: xpath,

View File

@@ -844,6 +844,62 @@ class _ProfilePageState extends State<ProfilePage> with RouteAware {
),
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: colors.surfaceContainerHighest.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Icon(Icons.group_outlined,
size: 20, color: colors.primary.withValues(alpha: 0.8)),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('QQ 群',
style: TextStyle(
fontSize: 12,
color: colors.onSurface.withValues(alpha: 0.5))),
const SizedBox(height: 2),
Text('1087203310',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: colors.onSurface)),
],
),
),
GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: '1087203310'));
ToastUtil.show(context, '已复制到剪贴板');
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: colors.primary.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.copy, size: 14, color: colors.primary),
const SizedBox(width: 4),
Text('复制', style: TextStyle(fontSize: 12, color: colors.primary, fontWeight: FontWeight.w600)),
],
),
),
),
],
),
),
),
const SizedBox(height: 16),
]),
),
);