generated from dellevin/template
优化日历界面
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
import 'dart:io';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../../providers/app_provider.dart';
|
import '../../providers/app_provider.dart';
|
||||||
import '../../models/data_models.dart';
|
import '../../models/data_models.dart';
|
||||||
import '../../utils/user_prefs.dart';
|
import '../../utils/user_prefs.dart';
|
||||||
|
import '../../widgets/fade_in_local_image.dart';
|
||||||
import '../movies/movie_detail_page.dart';
|
import '../movies/movie_detail_page.dart';
|
||||||
import '../movies/movie_form_page.dart';
|
import '../movies/movie_form_page.dart';
|
||||||
import '../book/book_detail_page.dart';
|
import '../book/book_detail_page.dart';
|
||||||
@@ -120,33 +120,21 @@ class _MediaCalendarPageState extends State<MediaCalendarPage> {
|
|||||||
_buildDateModeToggle(colors),
|
_buildDateModeToggle(colors),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Column(
|
body: LayoutBuilder(
|
||||||
children: [
|
builder: (context, constraints) {
|
||||||
_buildMonthHeader(colors),
|
// 日历区域固定,详情区独立滚动
|
||||||
_buildWeekdayLabels(colors),
|
final calendar = _buildCalendarArea(colors, today, constraints.maxWidth);
|
||||||
Expanded(
|
return Column(
|
||||||
child: _selectedDay != null && (_dayItems[_selectedDay]?.isNotEmpty ?? false)
|
children: [
|
||||||
? Column(
|
_buildMonthHeader(colors),
|
||||||
children: [
|
_buildWeekdayLabels(colors),
|
||||||
SingleChildScrollView(
|
calendar,
|
||||||
child: _buildCalendarGrid(colors, today),
|
Expanded(
|
||||||
),
|
child: _buildSelectedDayDetail(colors),
|
||||||
Expanded(
|
),
|
||||||
child: _buildSelectedDayDetail(colors),
|
],
|
||||||
),
|
);
|
||||||
],
|
},
|
||||||
)
|
|
||||||
: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
_buildCalendarGrid(colors, today),
|
|
||||||
if (_selectedDay != null) _buildSelectedDayDetail(colors),
|
|
||||||
const SizedBox(height: 80),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
floatingActionButton: _selectedDay != null
|
floatingActionButton: _selectedDay != null
|
||||||
? FloatingActionButton(
|
? FloatingActionButton(
|
||||||
@@ -202,9 +190,28 @@ class _MediaCalendarPageState extends State<MediaCalendarPage> {
|
|||||||
|
|
||||||
// ─── 日历网格 ───
|
// ─── 日历网格 ───
|
||||||
|
|
||||||
static const double _cellHeight = 62;
|
/// 日历区域:横滑切月 + cell 自适应
|
||||||
|
Widget _buildCalendarArea(ColorScheme colors, DateTime today, double maxWidth) {
|
||||||
|
// 可用宽度去掉左右 padding(8+8),每行 7 格
|
||||||
|
final cellSize = ((maxWidth - 16) / 7).clamp(44.0, 76.0);
|
||||||
|
|
||||||
Widget _buildCalendarGrid(ColorScheme colors, DateTime today) {
|
// 横滑切月手势
|
||||||
|
return GestureDetector(
|
||||||
|
onHorizontalDragEnd: (details) {
|
||||||
|
final v = details.primaryVelocity;
|
||||||
|
if (v == null) return;
|
||||||
|
const threshold = 300.0;
|
||||||
|
if (v < -threshold) {
|
||||||
|
_nextMonth();
|
||||||
|
} else if (v > threshold) {
|
||||||
|
_prevMonth();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: _buildCalendarGrid(colors, today, cellSize),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCalendarGrid(ColorScheme colors, DateTime today, double cellSize) {
|
||||||
final firstDay = DateTime(_currentMonth.year, _currentMonth.month, 1);
|
final firstDay = DateTime(_currentMonth.year, _currentMonth.month, 1);
|
||||||
final lastDay = DateTime(_currentMonth.year, _currentMonth.month + 1, 0);
|
final lastDay = DateTime(_currentMonth.year, _currentMonth.month + 1, 0);
|
||||||
final startOffset = firstDay.weekday - 1;
|
final startOffset = firstDay.weekday - 1;
|
||||||
@@ -217,7 +224,7 @@ class _MediaCalendarPageState extends State<MediaCalendarPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: List.generate(rows, (row) {
|
children: List.generate(rows, (row) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: _cellHeight,
|
height: cellSize,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: List.generate(7, (col) {
|
children: List.generate(7, (col) {
|
||||||
final index = row * 7 + col;
|
final index = row * 7 + col;
|
||||||
@@ -251,25 +258,38 @@ class _MediaCalendarPageState extends State<MediaCalendarPage> {
|
|||||||
? colors.surfaceContainerHigh
|
? colors.surfaceContainerHigh
|
||||||
: null,
|
: null,
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
border: isToday
|
border: isSelected
|
||||||
? Border.all(color: colors.primary, width: 1.5)
|
? Border.all(color: colors.primary.withValues(alpha: 0.3), width: 1)
|
||||||
: isSelected
|
: null,
|
||||||
? Border.all(color: colors.primary.withValues(alpha: 0.3), width: 1)
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
child: hasItems
|
child: hasItems
|
||||||
? _buildImageCell(colors, day, items, isToday)
|
? _buildImageCell(colors, day, items, isToday)
|
||||||
: Center(
|
: Stack(
|
||||||
child: Text(
|
children: [
|
||||||
'$day',
|
Center(
|
||||||
style: TextStyle(
|
child: Text(
|
||||||
fontSize: 13,
|
'$day',
|
||||||
fontWeight: isToday ? FontWeight.w600 : FontWeight.normal,
|
style: TextStyle(
|
||||||
color: isToday
|
fontSize: 13,
|
||||||
? colors.primary
|
fontWeight: isToday ? FontWeight.w600 : FontWeight.normal,
|
||||||
: colors.onSurface.withValues(alpha: 0.35),
|
color: isToday
|
||||||
|
? colors.primary
|
||||||
|
: colors.onSurface.withValues(alpha: 0.35),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
if (isToday)
|
||||||
|
Positioned(
|
||||||
|
top: 4, left: 4,
|
||||||
|
child: Container(
|
||||||
|
width: 6, height: 6,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: colors.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -281,10 +301,11 @@ class _MediaCalendarPageState extends State<MediaCalendarPage> {
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
Image(
|
FadeInLocalImage(
|
||||||
image: FileImage(File(items.first.path)),
|
path: items.first.path,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
errorBuilder: (_, __, ___) => Container(
|
duration: const Duration(milliseconds: 250),
|
||||||
|
errorWidget: Container(
|
||||||
color: colors.surfaceContainerHighest,
|
color: colors.surfaceContainerHighest,
|
||||||
child: Center(child: Icon(Icons.image_outlined, size: 16, color: colors.onSurface.withValues(alpha: 0.2))),
|
child: Center(child: Icon(Icons.image_outlined, size: 16, color: colors.onSurface.withValues(alpha: 0.2))),
|
||||||
),
|
),
|
||||||
@@ -324,6 +345,18 @@ class _MediaCalendarPageState extends State<MediaCalendarPage> {
|
|||||||
child: Text('+${items.length - 1}', style: const TextStyle(fontSize: 9, fontWeight: FontWeight.w600, color: Colors.white)),
|
child: Text('+${items.length - 1}', style: const TextStyle(fontSize: 9, fontWeight: FontWeight.w600, color: Colors.white)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// 今日小圆点(左上角)
|
||||||
|
if (isToday)
|
||||||
|
Positioned(
|
||||||
|
top: 4, left: 4,
|
||||||
|
child: Container(
|
||||||
|
width: 6, height: 6,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: Color(0xFFFFD54F),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -332,6 +365,9 @@ class _MediaCalendarPageState extends State<MediaCalendarPage> {
|
|||||||
// ─── 选中日期的详情 ───
|
// ─── 选中日期的详情 ───
|
||||||
|
|
||||||
Widget _buildSelectedDayDetail(ColorScheme colors) {
|
Widget _buildSelectedDayDetail(ColorScheme colors) {
|
||||||
|
if (_selectedDay == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
final items = _dayItems[_selectedDay] ?? [];
|
final items = _dayItems[_selectedDay] ?? [];
|
||||||
if (items.isEmpty) {
|
if (items.isEmpty) {
|
||||||
return Container(
|
return Container(
|
||||||
@@ -379,10 +415,11 @@ class _MediaCalendarPageState extends State<MediaCalendarPage> {
|
|||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 40, height: 40,
|
width: 40, height: 40,
|
||||||
child: Image(
|
child: FadeInLocalImage(
|
||||||
image: FileImage(File(item.path)),
|
path: item.path,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
errorBuilder: (_, __, ___) => Container(
|
duration: const Duration(milliseconds: 250),
|
||||||
|
errorWidget: Container(
|
||||||
color: colors.surfaceContainerHighest,
|
color: colors.surfaceContainerHighest,
|
||||||
child: Icon(Icons.image_outlined, size: 16, color: colors.onSurface.withValues(alpha: 0.2)),
|
child: Icon(Icons.image_outlined, size: 16, color: colors.onSurface.withValues(alpha: 0.2)),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user