generated from dellevin/template
完善
This commit is contained in:
@@ -32,7 +32,7 @@ class _MovieDetailPageState extends State<MovieDetailPage> {
|
||||
late int _detailStyle;
|
||||
final ValueNotifier<double> _posterOffset = ValueNotifier(0.0);
|
||||
double _posterDragStartOffset = 0.0;
|
||||
bool _draggingPoster = false;
|
||||
final ValueNotifier<bool> _draggingPoster = ValueNotifier(false);
|
||||
final GlobalKey _posterImageKey = GlobalKey();
|
||||
double _posterImageHeight = 0.0;
|
||||
|
||||
@@ -41,7 +41,14 @@ class _MovieDetailPageState extends State<MovieDetailPage> {
|
||||
super.initState();
|
||||
_showExactDate = UserPrefs().showExactReleaseDate;
|
||||
_detailStyle = UserPrefs().detailPageStyle;
|
||||
_posterOffset.value = widget.movie.coverOffset;
|
||||
_posterOffset.value = UserPrefs().getCoverOffset(widget.movie.id);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_posterOffset.dispose();
|
||||
_draggingPoster.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _toggleDateDisplay() {
|
||||
@@ -403,7 +410,7 @@ class _MovieDetailPageState extends State<MovieDetailPage> {
|
||||
final box = ctx.findRenderObject() as RenderBox?;
|
||||
if (box != null) _posterImageHeight = box.size.height;
|
||||
}
|
||||
setState(() => _draggingPoster = true);
|
||||
_draggingPoster.value = true;
|
||||
_posterDragStartOffset = _posterOffset.value;
|
||||
} : null,
|
||||
onLongPressMoveUpdate: hasPoster ? (d) {
|
||||
@@ -414,8 +421,10 @@ class _MovieDetailPageState extends State<MovieDetailPage> {
|
||||
_posterOffset.value = (raw.clamp(minOffset, 0.0) as double);
|
||||
} : null,
|
||||
onLongPressEnd: hasPoster ? (_) {
|
||||
setState(() => _draggingPoster = false);
|
||||
context.read<AppProvider>().updateMovie(movie.copyWith(coverOffset: _posterOffset.value));
|
||||
_draggingPoster.value = false;
|
||||
final offset = _posterOffset.value;
|
||||
UserPrefs().setCoverOffset(widget.movie.id, offset);
|
||||
context.read<AppProvider>().updateMovieCoverOffset(widget.movie.id, offset);
|
||||
} : null,
|
||||
child: ValueListenableBuilder<double>(
|
||||
valueListenable: _posterOffset,
|
||||
@@ -442,46 +451,54 @@ class _MovieDetailPageState extends State<MovieDetailPage> {
|
||||
)
|
||||
else
|
||||
_buildPosterPlaceholder(),
|
||||
// 底部白色渐变过渡(拖动时隐藏)
|
||||
if (!_draggingPoster)
|
||||
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: _draggingPoster,
|
||||
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 (_draggingPoster) ...[
|
||||
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)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
@@ -385,10 +385,17 @@ class _MovieFormPageState extends State<MovieFormPage> {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final isEdit = widget.movie != 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: [
|
||||
// 快捷添加按钮(仅添加模式显示)
|
||||
if (!isEdit)
|
||||
@@ -598,10 +605,9 @@ class _MovieFormPageState extends State<MovieFormPage> {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建信息卡片(用于网格布局)
|
||||
Widget _buildInfoCard({
|
||||
required String label,
|
||||
required String value,
|
||||
@@ -1800,6 +1806,55 @@ class _MovieFormPageState extends State<MovieFormPage> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查表单是否有内容
|
||||
bool _hasContent() {
|
||||
if (widget.movie != 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 (_posterPath != null) return true;
|
||||
if (_directors.isNotEmpty || _writers.isNotEmpty || _actors.isNotEmpty) return true;
|
||||
if (_genres.isNotEmpty || _alternateTitles.isNotEmpty) return true;
|
||||
if (_releaseDate != null || _watchDate != 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> _saveMovie() async {
|
||||
if (!_formKey.currentState!.validate()) {
|
||||
|
||||
@@ -92,10 +92,11 @@ class _MovieTabPageState extends State<MovieTabPage> {
|
||||
_lastStatusIndex = statusIdx;
|
||||
_initialized = true;
|
||||
final status = _statusMap[statusIdx] ?? 'watched';
|
||||
setState(() { _isLoading = true; _items.clear(); _offset = 0; _hasMore = true; });
|
||||
setState(() { _isLoading = true; _offset = 0; _hasMore = true; });
|
||||
final list = await provider.loadMoviesPaged(status: status, offset: 0);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_items.clear();
|
||||
_items.addAll(list);
|
||||
_offset = list.length;
|
||||
_hasMore = list.length >= 20;
|
||||
|
||||
Reference in New Issue
Block a user