342 lines
8.5 KiB
Markdown
342 lines
8.5 KiB
Markdown
|
|
# 日期时间选择器优化实现计划
|
||
|
|
|
||
|
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||
|
|
|
||
|
|
**Goal:** 将摇卦页面的日期时间选择器改为 iOS 滚轮样式,并实现 locale-aware 格式化
|
||
|
|
|
||
|
|
**Architecture:** 创建共享的 `DateTimePickerBottomSheet` 组件,封装 `CupertinoDatePicker` 和底部弹层交互,替换现有的 `showDatePicker` + `showTimePicker` 调用
|
||
|
|
|
||
|
|
**Tech Stack:** Flutter, Cupertino widgets, intl package
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Task 1: 创建 DateTimePickerBottomSheet 组件
|
||
|
|
|
||
|
|
**Files:**
|
||
|
|
- Create: `apps/lib/shared/widgets/date_time_picker/date_time_picker_bottom_sheet.dart`
|
||
|
|
|
||
|
|
**Step 1: 创建文件结构和基础代码**
|
||
|
|
|
||
|
|
```dart
|
||
|
|
import 'package:flutter/cupertino.dart';
|
||
|
|
import 'package:flutter/material.dart';
|
||
|
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||
|
|
import 'package:intl/intl.dart';
|
||
|
|
|
||
|
|
class DateTimePickerBottomSheet extends StatefulWidget {
|
||
|
|
const DateTimePickerBottomSheet({
|
||
|
|
super.key,
|
||
|
|
required this.initialDateTime,
|
||
|
|
this.minDateTime,
|
||
|
|
this.maxDateTime,
|
||
|
|
});
|
||
|
|
|
||
|
|
final DateTime initialDateTime;
|
||
|
|
final DateTime? minDateTime;
|
||
|
|
final DateTime? maxDateTime;
|
||
|
|
|
||
|
|
@override
|
||
|
|
State<DateTimePickerBottomSheet> createState() => _DateTimePickerBottomSheetState();
|
||
|
|
}
|
||
|
|
|
||
|
|
class _DateTimePickerBottomSheetState extends State<DateTimePickerBottomSheet> {
|
||
|
|
late DateTime _selectedDateTime;
|
||
|
|
int _selectedTab = 0; // 0=日期, 1=时间
|
||
|
|
|
||
|
|
@override
|
||
|
|
void initState() {
|
||
|
|
super.initState();
|
||
|
|
_selectedDateTime = widget.initialDateTime;
|
||
|
|
}
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
final l10n = AppLocalizations.of(context)!;
|
||
|
|
final locale = Localizations.localeOf(context);
|
||
|
|
|
||
|
|
return Container(
|
||
|
|
height: 400,
|
||
|
|
decoration: BoxDecoration(
|
||
|
|
color: Theme.of(context).colorScheme.surface,
|
||
|
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
||
|
|
),
|
||
|
|
child: Column(
|
||
|
|
children: [
|
||
|
|
// 顶部栏
|
||
|
|
Padding(
|
||
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||
|
|
child: Row(
|
||
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
|
|
children: [
|
||
|
|
TextButton(
|
||
|
|
onPressed: () => Navigator.pop(context),
|
||
|
|
child: Text(l10n.cancel),
|
||
|
|
),
|
||
|
|
Text(
|
||
|
|
l10n.autoSelectTime,
|
||
|
|
style: Theme.of(context).textTheme.titleMedium,
|
||
|
|
),
|
||
|
|
TextButton(
|
||
|
|
onPressed: () => Navigator.pop(context, _selectedDateTime),
|
||
|
|
child: Text(l10n.confirm),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
// SegmentedControl 切换日期/时间
|
||
|
|
Padding(
|
||
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||
|
|
child: CupertinoSlidingSegmentedControl<int>(
|
||
|
|
groupValue: _selectedTab,
|
||
|
|
children: {
|
||
|
|
0: Text(l10n.dateTab),
|
||
|
|
1: Text(l10n.timeTab),
|
||
|
|
},
|
||
|
|
onValueChanged: (value) => setState(() => _selectedTab = value ?? 0),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
const SizedBox(height: 16),
|
||
|
|
// CupertinoDatePicker
|
||
|
|
Expanded(
|
||
|
|
child: CupertinoDatePicker(
|
||
|
|
mode: _selectedTab == 0
|
||
|
|
? CupertinoDatePickerMode.date
|
||
|
|
: CupertinoDatePickerMode.time,
|
||
|
|
initialDateTime: _selectedDateTime,
|
||
|
|
minimumDate: widget.minDateTime,
|
||
|
|
maximumDate: widget.maxDateTime,
|
||
|
|
onDateTimeChanged: (DateTime newDateTime) {
|
||
|
|
setState(() => _selectedDateTime = newDateTime);
|
||
|
|
},
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<DateTime?> showDateTimePickerBottomSheet({
|
||
|
|
required BuildContext context,
|
||
|
|
required DateTime initialDateTime,
|
||
|
|
DateTime? minDateTime,
|
||
|
|
DateTime? maxDateTime,
|
||
|
|
}) {
|
||
|
|
return showModalBottomSheet<DateTime>(
|
||
|
|
context: context,
|
||
|
|
isScrollControlled: true,
|
||
|
|
backgroundColor: Colors.transparent,
|
||
|
|
builder: (context) => DateTimePickerBottomSheet(
|
||
|
|
initialDateTime: initialDateTime,
|
||
|
|
minDateTime: minDateTime,
|
||
|
|
maxDateTime: maxDateTime,
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Step 2: 添加 l10n 键值**
|
||
|
|
|
||
|
|
在 `apps/lib/l10n/app_zh.arb` 添加:
|
||
|
|
```json
|
||
|
|
"dateTab": "日期",
|
||
|
|
"timeTab": "时间",
|
||
|
|
"confirm": "确认",
|
||
|
|
"cancel": "取消"
|
||
|
|
```
|
||
|
|
|
||
|
|
在 `apps/lib/l10n/app_en.arb` 添加:
|
||
|
|
```json
|
||
|
|
"dateTab": "Date",
|
||
|
|
"timeTab": "Time",
|
||
|
|
"confirm": "Confirm",
|
||
|
|
"cancel": "Cancel"
|
||
|
|
```
|
||
|
|
|
||
|
|
运行 `flutter gen-l10n` 生成代码
|
||
|
|
|
||
|
|
**Step 3: Commit**
|
||
|
|
|
||
|
|
```bash
|
||
|
|
git add apps/lib/shared/widgets/date_time_picker/ apps/lib/l10n/
|
||
|
|
git commit -m "feat(divination): add DateTimePickerBottomSheet with iOS wheel style"
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Task 2: 修改 auto_divination_screen.dart 使用新选择器
|
||
|
|
|
||
|
|
**Files:**
|
||
|
|
- Modify: `apps/lib/features/divination/presentation/screens/auto_divination_screen.dart:208-230`
|
||
|
|
- Modify: `apps/lib/features/divination/presentation/screens/auto_divination_screen.dart:353`
|
||
|
|
|
||
|
|
**Step 1: 添加 import**
|
||
|
|
|
||
|
|
在文件顶部添加:
|
||
|
|
```dart
|
||
|
|
import 'package:shared/widgets/date_time_picker/date_time_picker_bottom_sheet.dart';
|
||
|
|
```
|
||
|
|
|
||
|
|
**Step 2: 修改 _pickTime 方法**
|
||
|
|
|
||
|
|
将:
|
||
|
|
```dart
|
||
|
|
Future<void> _pickTime() async {
|
||
|
|
final date = await showDatePicker(
|
||
|
|
context: context,
|
||
|
|
initialDate: _selectedTime,
|
||
|
|
firstDate: DateTime(2000),
|
||
|
|
lastDate: DateTime(2100),
|
||
|
|
);
|
||
|
|
if (date == null || !mounted) return;
|
||
|
|
final time = await showTimePicker(
|
||
|
|
context: context,
|
||
|
|
initialTime: TimeOfDay.fromDateTime(_selectedTime),
|
||
|
|
);
|
||
|
|
if (time == null || !mounted) return;
|
||
|
|
setState(() {
|
||
|
|
_selectedTime = DateTime(
|
||
|
|
date.year,
|
||
|
|
date.month,
|
||
|
|
date.day,
|
||
|
|
time.hour,
|
||
|
|
time.minute,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
替换为:
|
||
|
|
```dart
|
||
|
|
Future<void> _pickTime() async {
|
||
|
|
final result = await showDateTimePickerBottomSheet(
|
||
|
|
context: context,
|
||
|
|
initialDateTime: _selectedTime,
|
||
|
|
minDateTime: DateTime(2000),
|
||
|
|
maxDateTime: DateTime(2100),
|
||
|
|
);
|
||
|
|
if (result == null || !mounted) return;
|
||
|
|
setState(() {
|
||
|
|
_selectedTime = result;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Step 3: 修改日期显示格式**
|
||
|
|
|
||
|
|
将:
|
||
|
|
```dart
|
||
|
|
DateFormat('yyyy年MM月dd日 HH:mm').format(selectedTime)
|
||
|
|
```
|
||
|
|
|
||
|
|
替换为:
|
||
|
|
```dart
|
||
|
|
DateFormat.yMd(Localizations.localeOf(context).toString()).add_Hm().format(selectedTime)
|
||
|
|
```
|
||
|
|
|
||
|
|
需要添加 import:
|
||
|
|
```dart
|
||
|
|
import 'package:intl/intl.dart';
|
||
|
|
```
|
||
|
|
|
||
|
|
**Step 4: Commit**
|
||
|
|
|
||
|
|
```bash
|
||
|
|
git add apps/lib/features/divination/presentation/screens/auto_divination_screen.dart
|
||
|
|
git commit -m "feat(divination): use DateTimePickerBottomSheet in auto_divination_screen"
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Task 3: 修改 manual_divination_screen.dart 使用新选择器
|
||
|
|
|
||
|
|
**Files:**
|
||
|
|
- Modify: `apps/lib/features/divination/presentation/screens/manual_divination_screen.dart:142-168`
|
||
|
|
- Modify: `apps/lib/features/divination/presentation/screens/manual_divination_screen.dart:271`
|
||
|
|
|
||
|
|
**Step 1: 添加 import**
|
||
|
|
|
||
|
|
```dart
|
||
|
|
import 'package:shared/widgets/date_time_picker/date_time_picker_bottom_sheet.dart';
|
||
|
|
import 'package:intl/intl.dart';
|
||
|
|
```
|
||
|
|
|
||
|
|
**Step 2: 修改 _pickTime 方法和日期显示格式**
|
||
|
|
|
||
|
|
同 Task 2 的修改方式
|
||
|
|
|
||
|
|
**Step 3: Commit**
|
||
|
|
|
||
|
|
```bash
|
||
|
|
git add apps/lib/features/divination/presentation/screens/manual_divination_screen.dart
|
||
|
|
git commit -m "feat(divination): use DateTimePickerBottomSheet in manual_divination_screen"
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Task 4: 修改 divination_result_screen.dart 的日期格式
|
||
|
|
|
||
|
|
**Files:**
|
||
|
|
- Modify: `apps/lib/features/divination/presentation/screens/divination_result_screen.dart:455-457`
|
||
|
|
|
||
|
|
**Step 1: 添加 import**
|
||
|
|
|
||
|
|
```dart
|
||
|
|
import 'package:intl/intl.dart';
|
||
|
|
```
|
||
|
|
|
||
|
|
**Step 2: 修改日期格式**
|
||
|
|
|
||
|
|
将:
|
||
|
|
```dart
|
||
|
|
DateFormat(
|
||
|
|
'yyyy年MM月dd日 HH:mm',
|
||
|
|
).format(data.params.divinationTime),
|
||
|
|
```
|
||
|
|
|
||
|
|
替换为:
|
||
|
|
```dart
|
||
|
|
DateFormat.yMd(Localizations.localeOf(context).toString()).add_Hm().format(data.params.divinationTime),
|
||
|
|
```
|
||
|
|
|
||
|
|
**Step 3: Commit**
|
||
|
|
|
||
|
|
```bash
|
||
|
|
git add apps/lib/features/divination/presentation/screens/divination_result_screen.dart
|
||
|
|
git commit -m "refactor(divination): use locale-aware date format in divination_result_screen"
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Task 5: 运行验证
|
||
|
|
|
||
|
|
**Step 1: 生成 l10n**
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cd apps && flutter gen-l10n
|
||
|
|
```
|
||
|
|
|
||
|
|
**Step 2: 运行静态分析**
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cd apps && flutter analyze
|
||
|
|
```
|
||
|
|
|
||
|
|
预期: 无错误
|
||
|
|
|
||
|
|
**Step 3: 运行相关测试**
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cd apps && flutter test test/features/divination/
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Plan complete.** Two execution options:
|
||
|
|
|
||
|
|
**1. Subagent-Driven (this session)** - I dispatch fresh subagent per task, review between tasks, fast iteration
|
||
|
|
|
||
|
|
**2. Parallel Session (separate)** - Open new session with executing-plans, batch execution with checkpoints
|
||
|
|
|
||
|
|
**Which approach?**
|