基础功能

This commit is contained in:
DelLevin-Home
2026-03-05 10:06:32 +08:00
commit adad6372a3
55 changed files with 6036 additions and 0 deletions

View File

@@ -0,0 +1,290 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/app_provider.dart';
import '../models/data_models.dart';
/// 添加/编辑影视记录页面
class MovieFormPage extends StatefulWidget {
final Movie? movie; // 如果为 null则是添加模式否则是编辑模式
const MovieFormPage({super.key, this.movie});
@override
State<MovieFormPage> createState() => _MovieFormPageState();
}
class _MovieFormPageState extends State<MovieFormPage> {
final _formKey = GlobalKey<FormState>();
late TextEditingController _titleController;
late TextEditingController _yearController;
late TextEditingController _ratingController;
late TextEditingController _noteController;
late String _status;
DateTime? _watchDate;
@override
void initState() {
super.initState();
_titleController = TextEditingController(text: widget.movie?.title ?? '');
_yearController = TextEditingController(text: widget.movie?.year?.toString() ?? '');
_ratingController = TextEditingController(text: widget.movie?.rating?.toString() ?? '');
_noteController = TextEditingController(text: widget.movie?.note ?? '');
_status = widget.movie?.status ?? 'want_to_watch';
_watchDate = widget.movie?.watchDate;
}
@override
void dispose() {
_titleController.dispose();
_yearController.dispose();
_ratingController.dispose();
_noteController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final isEdit = widget.movie != null;
return Scaffold(
appBar: AppBar(
title: Text(isEdit ? '编辑影片' : '添加影片'),
actions: [
IconButton(
icon: const Icon(Icons.save),
onPressed: _saveMovie,
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题
TextFormField(
controller: _titleController,
decoration: const InputDecoration(
labelText: '影片名称 *',
hintText: '请输入影片名称',
prefixIcon: Icon(Icons.movie),
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return '请输入影片名称';
}
return null;
},
),
const SizedBox(height: 16),
// 年份和评分
Row(
children: [
Expanded(
child: TextFormField(
controller: _yearController,
decoration: const InputDecoration(
labelText: '年份',
hintText: '例如2024',
prefixIcon: Icon(Icons.calendar_today),
border: OutlineInputBorder(),
),
keyboardType: TextInputType.number,
),
),
const SizedBox(width: 16),
Expanded(
child: TextFormField(
controller: _ratingController,
decoration: const InputDecoration(
labelText: '评分',
hintText: '0-10',
prefixIcon: Icon(Icons.star),
border: OutlineInputBorder(),
),
keyboardType: const TextInputType.numberWithOptions(decimal: true),
validator: (value) {
if (value != null && value.isNotEmpty) {
final rating = double.tryParse(value);
if (rating == null || rating < 0 || rating > 10) {
return '评分必须在 0-10 之间';
}
}
return null;
},
),
),
],
),
const SizedBox(height: 16),
// 状态选择
DropdownButtonFormField<String>(
value: _status,
decoration: const InputDecoration(
labelText: '状态',
prefixIcon: Icon(Icons.check_circle_outline),
border: OutlineInputBorder(),
),
items: const [
DropdownMenuItem(value: 'watched', child: Text('已看')),
DropdownMenuItem(value: 'want_to_watch', child: Text('想看')),
DropdownMenuItem(value: 'watching', child: Text('在看')),
],
onChanged: (value) {
setState(() {
_status = value!;
});
},
),
const SizedBox(height: 16),
// 观看日期选择
InkWell(
onTap: _selectWatchDate,
child: InputDecorator(
decoration: const InputDecoration(
labelText: '观看日期',
prefixIcon: Icon(Icons.event),
border: OutlineInputBorder(),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_watchDate != null
? '${_watchDate!.year}-${_watchDate!.month.toString().padLeft(2, '0')}-${_watchDate!.day.toString().padLeft(2, '0')}'
: '选择日期',
style: TextStyle(
color: _watchDate != null
? Theme.of(context).colorScheme.onSurface
: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
if (_watchDate != null)
IconButton(
icon: const Icon(Icons.clear, size: 20),
onPressed: () {
setState(() {
_watchDate = null;
});
},
),
],
),
),
),
const SizedBox(height: 16),
// 笔记
TextFormField(
controller: _noteController,
decoration: const InputDecoration(
labelText: '笔记',
hintText: '写下你的观后感...',
prefixIcon: Icon(Icons.edit_note),
border: OutlineInputBorder(),
alignLabelWithHint: true,
),
maxLines: 5,
),
const SizedBox(height: 32),
// 保存按钮
SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton.icon(
onPressed: _saveMovie,
icon: const Icon(Icons.save),
label: Text(isEdit ? '保存修改' : '添加记录'),
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
),
],
),
),
),
);
}
/// 选择观看日期
Future<void> _selectWatchDate() async {
final picked = await showDatePicker(
context: context,
initialDate: _watchDate ?? DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime.now(),
);
if (picked != null) {
setState(() {
_watchDate = picked;
});
}
}
/// 保存影视记录
Future<void> _saveMovie() async {
if (!_formKey.currentState!.validate()) {
return;
}
final year = _yearController.text.isNotEmpty ? int.tryParse(_yearController.text) : null;
final rating = _ratingController.text.isNotEmpty ? double.tryParse(_ratingController.text) : null;
if (widget.movie == null) {
// 添加新模式
final newMovie = Movie(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: _titleController.text.trim(),
year: year,
rating: rating,
status: _status,
watchDate: _watchDate,
note: _noteController.text.trim(),
);
await context.read<AppProvider>().addMovie(newMovie);
} else {
// 编辑现有模式
final updatedMovie = Movie(
id: widget.movie!.id,
title: _titleController.text.trim(),
year: year,
rating: rating,
status: _status,
watchDate: _watchDate,
note: _noteController.text.trim(),
);
await context.read<AppProvider>().updateMovie(updatedMovie);
}
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(widget.movie == null ? '添加成功' : '更新成功'),
behavior: SnackBarBehavior.floating,
),
);
Navigator.pop(context);
}
}