This commit is contained in:
DelLevin-Home
2026-06-20 05:01:59 +08:00
parent 3f186c7521
commit c7f74c61f5
15 changed files with 572 additions and 396 deletions

View File

@@ -28,13 +28,14 @@ class _BookDetailPageState extends State<BookDetailPage> {
late int _detailStyle;
final ValueNotifier<double> _coverOffset = ValueNotifier(0.0);
double _coverDragStartOffset = 0.0;
bool _draggingCover = false;
final ValueNotifier<bool> _draggingCover = ValueNotifier(false);
final GlobalKey _coverImageKey = GlobalKey();
double _coverImageHeight = 0.0;
@override
void dispose() {
_coverOffset.dispose();
_draggingCover.dispose();
super.dispose();
}
@@ -42,7 +43,7 @@ class _BookDetailPageState extends State<BookDetailPage> {
void initState() {
super.initState();
_detailStyle = UserPrefs().detailPageStyle;
_coverOffset.value = widget.book.coverOffset;
_coverOffset.value = UserPrefs().getCoverOffset(widget.book.id);
}
@override
@@ -351,7 +352,7 @@ class _BookDetailPageState extends State<BookDetailPage> {
final box = ctx.findRenderObject() as RenderBox?;
if (box != null) _coverImageHeight = box.size.height;
}
setState(() => _draggingCover = true);
_draggingCover.value = true;
_coverDragStartOffset = _coverOffset.value;
} : null,
onLongPressMoveUpdate: hasCover ? (d) {
@@ -361,8 +362,10 @@ class _BookDetailPageState extends State<BookDetailPage> {
_coverOffset.value = (raw.clamp(minOffset, 0.0) as double);
} : null,
onLongPressEnd: hasCover ? (_) {
setState(() => _draggingCover = false);
context.read<AppProvider>().updateBook(book.copyWith(coverOffset: _coverOffset.value));
_draggingCover.value = false;
final offset = _coverOffset.value;
UserPrefs().setCoverOffset(widget.book.id, offset);
context.read<AppProvider>().updateBookCoverOffset(widget.book.id, offset);
} : null,
child: ValueListenableBuilder<double>(
valueListenable: _coverOffset,
@@ -389,44 +392,54 @@ class _BookDetailPageState extends State<BookDetailPage> {
)
else
_buildCoverPlaceholder(),
if (!_draggingCover)
Positioned(
left: 0, right: 0, bottom: 0,
child: IgnorePointer(
child: Container(
height: 60,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
colors.surface.withValues(alpha: 0),
colors.surface,
],
// 底部渐变 + 拖动遮罩
ValueListenableBuilder<bool>(
valueListenable: _draggingCover,
builder: (context, dragging, _) {
return Stack(
children: [
if (!dragging)
Positioned(
left: 0, right: 0, bottom: 0,
child: IgnorePointer(
child: Container(
height: 60,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
colors.surface.withValues(alpha: 0),
colors.surface,
],
),
),
),
),
),
),
),
),
),
if (_draggingCover) ...[
Positioned.fill(
child: Container(color: Colors.black.withValues(alpha: 0.3)),
),
Positioned(
left: 0, right: 0, bottom: 20,
child: Center(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.6),
borderRadius: BorderRadius.circular(20),
),
child: const Text('上下滑动调整图片位置',
style: TextStyle(fontSize: 13, color: Colors.white70)),
),
),
),
],
if (dragging) ...[
Positioned.fill(
child: Container(color: Colors.black.withValues(alpha: 0.3)),
),
Positioned(
left: 0, right: 0, bottom: 20,
child: Center(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.6),
borderRadius: BorderRadius.circular(20),
),
child: const Text('上下滑动调整图片位置',
style: TextStyle(fontSize: 13, color: Colors.white70)),
),
),
),
],
],
);
},
),
],
);
},

View File

@@ -135,10 +135,17 @@ class _BookFormPageState extends State<BookFormPage> {
final colors = Theme.of(context).colorScheme;
final isEdit = widget.book != null;
return Scaffold(
backgroundColor: colors.surface,
appBar: AppBar(
title: Text(isEdit ? '编辑书籍' : '添加书籍'),
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) async {
if (didPop) return;
final shouldPop = await _confirmLeave();
if (shouldPop && context.mounted) Navigator.pop(context);
},
child: Scaffold(
backgroundColor: colors.surface,
appBar: AppBar(
title: Text(isEdit ? '编辑书籍' : '添加书籍'),
actions: [
// 保存按钮
_buildActionButton(
@@ -325,6 +332,7 @@ class _BookFormPageState extends State<BookFormPage> {
],
),
),
),
);
}
@@ -1264,6 +1272,56 @@ class _BookFormPageState extends State<BookFormPage> {
);
}
/// 检查表单是否有内容
bool _hasContent() {
if (widget.book != null) return true;
if (_titleController.text.trim().isNotEmpty) return true;
if (_summaryController.text.trim().isNotEmpty) return true;
if (_ratingController.text.trim().isNotEmpty) return true;
if (_isbnController.text.trim().isNotEmpty) return true;
if (_publisherController.text.trim().isNotEmpty) return true;
if (_coverPath != null) return true;
if (_authors.isNotEmpty || _alternateTitles.isNotEmpty || _genres.isNotEmpty) return true;
if (_publishDate != null) return true;
return false;
}
/// 离开确认
Future<bool> _confirmLeave() async {
if (!_hasContent()) return true;
final colors = Theme.of(context).colorScheme;
final result = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
backgroundColor: colors.surface,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
title: Text('未保存', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: colors.onSurface)),
content: Text('当前内容未保存,确定要离开吗?',
style: TextStyle(fontSize: 14, color: colors.onSurface.withValues(alpha: 0.6), height: 1.5)),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: Text('取消', style: TextStyle(color: colors.onSurface.withValues(alpha: 0.6))),
),
ElevatedButton(
onPressed: () => Navigator.pop(ctx, true),
style: ElevatedButton.styleFrom(
backgroundColor: colors.error,
foregroundColor: colors.onError,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
child: const Text('离开'),
),
],
actionsPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
),
);
return result ?? false;
}
/// 保存书籍
Future<void> _saveBook() async {
if (!_formKey.currentState!.validate()) {

View File

@@ -90,10 +90,10 @@ class _BookTabPageState extends State<BookTabPage> {
_lastStatusIndex = statusIdx;
_initialized = true;
final status = _statusMap[statusIdx] ?? 'read';
setState(() { _isLoading = true; _items.clear(); _offset = 0; _hasMore = true; });
setState(() { _isLoading = true; _offset = 0; _hasMore = true; });
final list = await provider.loadBooksPaged(status: status, offset: 0);
if (!mounted) return;
setState(() { _items.addAll(list); _offset = list.length; _hasMore = list.length >= 20; _isLoading = false; });
setState(() { _items.clear(); _items.addAll(list); _offset = list.length; _hasMore = list.length >= 20; _isLoading = false; });
}
Future<void> _loadMore() async {