Files
eryao/docs/plans/2026-04-03-datetime-picker-impl.md
T

8.5 KiB

日期时间选择器优化实现计划

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: 创建文件结构和基础代码

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 添加:

"dateTab": "日期",
"timeTab": "时间",
"confirm": "确认",
"cancel": "取消"

apps/lib/l10n/app_en.arb 添加:

"dateTab": "Date",
"timeTab": "Time",
"confirm": "Confirm",
"cancel": "Cancel"

运行 flutter gen-l10n 生成代码

Step 3: Commit

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

在文件顶部添加:

import 'package:shared/widgets/date_time_picker/date_time_picker_bottom_sheet.dart';

Step 2: 修改 _pickTime 方法

将:

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,
    );
  });
}

替换为:

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: 修改日期显示格式

将:

DateFormat('yyyy年MM月dd日 HH:mm').format(selectedTime)

替换为:

DateFormat.yMd(Localizations.localeOf(context).toString()).add_Hm().format(selectedTime)

需要添加 import:

import 'package:intl/intl.dart';

Step 4: Commit

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

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

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

import 'package:intl/intl.dart';

Step 2: 修改日期格式

将:

DateFormat(
  'yyyy年MM月dd日 HH:mm',
).format(data.params.divinationTime),

替换为:

DateFormat.yMd(Localizations.localeOf(context).toString()).add_Hm().format(data.params.divinationTime),

Step 3: Commit

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

cd apps && flutter gen-l10n

Step 2: 运行静态分析

cd apps && flutter analyze

预期: 无错误

Step 3: 运行相关测试

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?