一、应用概述

1.1 应用简介

番茄钟(Pomodoro Timer)是一款基于番茄工作法(Pomodoro Technique)的高效时间管理工具。番茄工作法由弗朗西斯科·西里洛(Francesco Cirillo)在20世纪80年代创立,核心理念是:将工作时间分割为25分钟的专注工作时段和5分钟的短暂休息时段,每完成4个番茄钟进行一次15-30分钟的长休息。该应用以HarmonyOS NEXT的ArkTS框架为基础,深入展示了定时器管理、番茄工作法实现、状态切换、数据统计、任务关联和通知提醒等关键技术。

1.2 核心功能

功能模块 功能描述 技术实现 设计考量
专注计时 25分钟专注工作倒计时 高精度定时器 精确到秒
休息计时 5分钟短暂休息 自动切换模式 无缝过渡
长休息 15-30分钟长休息 周期计数控制 每4个番茄钟
自定义设置 自定义专注/休息时长 配置管理 个性化适配
每日目标 番茄钟完成目标数 进度追踪 可视化管理
统计面板 完成统计与趋势 数据聚合 周/月/年视图
任务关联 每个番茄钟绑定任务 任务数据结构 时间追踪
白噪音 内置白噪音播放 背景音播放 提升专注力

1.3 应用架构

番茄钟应用采用分层架构:

  1. UI表现层:倒计时显示、进度环、状态标签、控制按钮、统计面板、任务列表。
  2. 业务逻辑层:番茄钟引擎、定时器管理器、统计计算器、任务管理器、白噪音管理器。
  3. 数据持久层:使用Preferences API保存统计数据、设置偏好和任务列表。

二、番茄钟引擎

2.1 核心状态机

enum PomodoroState {
  IDLE = 'idle',           // 空闲
  FOCUS = 'focus',         // 专注中
  SHORT_BREAK = 'short_break', // 短休息
  LONG_BREAK = 'long_break',   // 长休息
  PAUSED = 'paused'        // 暂停
}

interface PomodoroConfig {
  focusDuration: number;       // 专注时长(分钟)
  shortBreakDuration: number;  // 短休息时长(分钟)
  longBreakDuration: number;   // 长休息时长(分钟)
  sessionsBeforeLongBreak: number; // 长休息周期
  dailyGoal: number;           // 每日目标番茄数
  autoStartBreak: boolean;     // 自动开始休息
  autoStartFocus: boolean;     // 自动开始专注
  soundEnabled: boolean;       // 音效开关
}

class PomodoroEngine {
  @State currentState: PomodoroState = PomodoroState.IDLE;
  @State remainingSeconds: number = 25 * 60;
  @State currentSession: number = 0;     // 当前周期内的番茄数
  @State completedSessions: number = 0;  // 今日完成的番茄数
  @State isRunning: boolean = false;
  
  private config: PomodoroConfig = {
    focusDuration: 25,
    shortBreakDuration: 5,
    longBreakDuration: 15,
    sessionsBeforeLongBreak: 4,
    dailyGoal: 8,
    autoStartBreak: true,
    autoStartFocus: true,
    soundEnabled: true
  };
  
  private timerId: number | null = null;
  private onStateChange: ((state: PomodoroState) => void) | null = null;
  private onTick: ((remaining: number) => void) | null = null;
  private onComplete: ((session: number) => void) | null = null;
  
  // 开始专注
  startFocus(): void {
    this.currentState = PomodoroState.FOCUS;
    this.remainingSeconds = this.config.focusDuration * 60;
    this.isRunning = true;
    this.startTimer();
    this.onStateChange?.(PomodoroState.FOCUS);
  }
  
  // 开始休息
  startBreak(): void {
    const isLongBreak = this.currentSession > 0 && 
      this.currentSession % this.config.sessionsBeforeLongBreak === 0;
    
    this.currentState = isLongBreak ? PomodoroState.LONG_BREAK : PomodoroState.SHORT_BREAK;
    this.remainingSeconds = isLongBreak ? 
      this.config.longBreakDuration * 60 : 
      this.config.shortBreakDuration * 60;
    this.isRunning = true;
    this.startTimer();
    this.onStateChange?.(this.currentState);
  }
  
  private startTimer(): void {
    if (this.timerId !== null) {
      clearInterval(this.timerId);
    }
    
    this.timerId = setInterval(() => {
      this.remainingSeconds--;
      this.onTick?.(this.remainingSeconds);
      
      if (this.remainingSeconds <= 0) {
        this.handleTimerComplete();
      }
    }, 1000);
  }
  
  private handleTimerComplete(): void {
    this.stopTimer();
    
    if (this.currentState === PomodoroState.FOCUS) {
      this.currentSession++;
      this.completedSessions++;
      this.onComplete?.(this.currentSession);
      
      if (this.config.autoStartBreak) {
        this.startBreak();
      } else {
        this.currentState = PomodoroState.IDLE;
        this.isRunning = false;
        this.onStateChange?.(PomodoroState.IDLE);
      }
    } else {
      // 休息结束
      if (this.config.autoStartFocus) {
        this.startFocus();
      } else {
        this.currentState = PomodoroState.IDLE;
        this.isRunning = false;
        this.onStateChange?.(PomodoroState.IDLE);
      }
    }
  }
  
  pause(): void {
    if (this.isRunning) {
      this.stopTimer();
      this.isRunning = false;
      this.currentState = PomodoroState.PAUSED;
      this.onStateChange?.(PomodoroState.PAUSED);
    }
  }
  
  resume(): void {
    if (this.currentState === PomodoroState.PAUSED) {
      this.isRunning = true;
      this.startTimer();
      // 恢复之前的状态
      this.onStateChange?.(this.currentState);
    }
  }
  
  reset(): void {
    this.stopTimer();
    this.currentState = PomodoroState.IDLE;
    this.remainingSeconds = this.config.focusDuration * 60;
    this.isRunning = false;
    this.onStateChange?.(PomodoroState.IDLE);
  }
  
  private stopTimer(): void {
    if (this.timerId !== null) {
      clearInterval(this.timerId);
      this.timerId = null;
    }
  }
  
  formatTime(seconds: number): string {
    const mins = Math.floor(seconds / 60);
    const secs = seconds % 60;
    return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
  }
  
  getProgress(): number {
    const total = this.config.focusDuration * 60;
    return (total - this.remainingSeconds) / total;
  }
  
  updateConfig(newConfig: Partial<PomodoroConfig>): void {
    this.config = { ...this.config, ...newConfig };
  }
}

2.2 番茄钟周期管理

完整的番茄工作法周期包含以下阶段,每个阶段都有明确的时间分配和目标:

class PomodoroCycle {
  static getCycleInfo(sessionNumber: number, config: PomodoroConfig): {
    phase: string;
    duration: number;
    description: string;
  } {
    const isLongBreak = sessionNumber > 0 && 
      sessionNumber % config.sessionsBeforeLongBreak === 0;
    
    if (sessionNumber === 0) {
      return {
        phase: 'focus',
        duration: config.focusDuration,
        description: '第1个番茄钟,开始专注工作'
      };
    }
    
    if (isLongBreak) {
      return {
        phase: 'long_break',
        duration: config.longBreakDuration,
        description: `已完成${sessionNumber}个番茄钟,进行长休息`
      };
    }
    
    return {
      phase: 'short_break',
      duration: config.shortBreakDuration,
      description: `已完成${sessionNumber}个番茄钟,进行短休息`
    };
  }
  
  // 计算预计完成时间
  static estimateCompletion(
    currentSession: number,
    config: PomodoroConfig,
    startTime: Date
  ): Date {
    let totalMinutes = 0;
    
    for (let i = 0; i < currentSession; i++) {
      totalMinutes += config.focusDuration;
      if ((i + 1) % config.sessionsBeforeLongBreak === 0) {
        totalMinutes += config.longBreakDuration;
      } else {
        totalMinutes += config.shortBreakDuration;
      }
    }
    
    return new Date(startTime.getTime() + totalMinutes * 60 * 1000);
  }
}

三、定时器系统

3.1 精确倒计时

class PrecisionTimer {
  private targetTime: number = 0;
  private timerId: number | null = null;
  private isRunning: boolean = false;
  private remainingMs: number = 0;
  private onTick: ((remainingMs: number) => void) | null = null;
  private onComplete: (() => void) | null = null;
  
  // 开始倒计时(使用系统时间校正,避免setInterval漂移)
  start(durationMs: number): void {
    this.remainingMs = durationMs;
    this.targetTime = Date.now() + durationMs;
    this.isRunning = true;
    
    const tick = () => {
      if (!this.isRunning) return;
      
      this.remainingMs = Math.max(0, this.targetTime - Date.now());
      this.onTick?.(this.remainingMs);
      
      if (this.remainingMs <= 0) {
        this.stop();
        this.onComplete?.();
        return;
      }
      
      // 使用setTimeout递归调用,每次重新计算延迟
      this.timerId = setTimeout(tick, 100); // 每100ms检查一次
    };
    
    tick();
  }
  
  stop(): void {
    this.isRunning = false;
    if (this.timerId !== null) {
      clearTimeout(this.timerId);
      this.timerId = null;
    }
  }
  
  pause(): void {
    if (this.isRunning) {
      this.stop();
      this.remainingMs = Math.max(0, this.targetTime - Date.now());
    }
  }
  
  resume(): void {
    if (this.remainingMs > 0) {
      this.start(this.remainingMs);
    }
  }
  
  getRemainingMs(): number {
    if (this.isRunning) {
      return Math.max(0, this.targetTime - Date.now());
    }
    return this.remainingMs;
  }
}

四、统计系统

4.1 数据模型与统计

interface PomodoroRecord {
  id: string;
  date: string;           // 日期 YYYY-MM-DD
  startTime: string;      // 开始时间 HH:mm
  endTime: string;        // 结束时间 HH:mm
  duration: number;       // 实际专注时长(秒)
  plannedDuration: number; // 计划时长(秒)
  completed: boolean;     // 是否完成
  taskId: string | null;  // 关联任务ID
  taskName: string;       // 任务名称
  interruptions: number;  // 中断次数
  notes: string;          // 备注
}

interface DailyStats {
  date: string;
  completedSessions: number;
  totalFocusMinutes: number;
  totalInterruptions: number;
  tasksCompleted: number;
  goalProgress: number;   // 0-100
}

class StatisticsCalculator {
  calculateDailyStats(records: PomodoroRecord[], date: string): DailyStats {
    const dayRecords = records.filter(r => r.date === date);
    const completed = dayRecords.filter(r => r.completed);
    
    return {
      date,
      completedSessions: completed.length,
      totalFocusMinutes: Math.round(completed.reduce((s, r) => s + r.duration, 0) / 60),
      totalInterruptions: completed.reduce((s, r) => s + r.interruptions, 0),
      tasksCompleted: new Set(completed.filter(r => r.taskId).map(r => r.taskId)).size,
      goalProgress: 0 // 在外部设置
    };
  }
  
  calculateWeeklyStats(records: PomodoroRecord[], weekStart: string): WeeklyStats {
    const weekEnd = this.addDays(weekStart, 7);
    const weekRecords = records.filter(r => r.date >= weekStart && r.date < weekEnd);
    const completed = weekRecords.filter(r => r.completed);
    
    const dailyBreakdown: DailyStats[] = [];
    for (let i = 0; i < 7; i++) {
      const date = this.addDays(weekStart, i);
      dailyBreakdown.push(this.calculateDailyStats(records, date));
    }
    
    return {
      weekStart,
      weekEnd,
      totalSessions: completed.length,
      totalFocusMinutes: Math.round(completed.reduce((s, r) => s + r.duration, 0) / 60),
      averagePerDay: Math.round(completed.length / 7 * 10) / 10,
      bestDay: Math.max(...dailyBreakdown.map(d => d.completedSessions)),
      dailyBreakdown
    };
  }
  
  private addDays(dateStr: string, days: number): string {
    const date = new Date(dateStr);
    date.setDate(date.getDate() + days);
    return date.toISOString().split('T')[0];
  }
}

五、任务关联系统

5.1 任务管理

interface PomodoroTask {
  id: string;
  name: string;
  estimatedPomodoros: number;
  completedPomodoros: number;
  status: 'pending' | 'in_progress' | 'completed';
  createdAt: string;
  completedAt: string | null;
  priority: 'low' | 'medium' | 'high';
  tags: string[];
}

class TaskManager {
  private tasks: PomodoroTask[] = [];
  private currentTaskId: string | null = null;
  
  addTask(name: string, estimatedPomodoros: number = 1, priority: 'low' | 'medium' | 'high' = 'medium'): PomodoroTask {
    const task: PomodoroTask = {
      id: Date.now().toString(),
      name,
      estimatedPomodoros,
      completedPomodoros: 0,
      status: 'pending',
      createdAt: new Date().toISOString(),
      completedAt: null,
      priority,
      tags: []
    };
    this.tasks.push(task);
    return task;
  }
  
  startTask(taskId: string): void {
    const task = this.tasks.find(t => t.id === taskId);
    if (task) {
      task.status = 'in_progress';
      this.currentTaskId = taskId;
    }
  }
  
  completePomodoro(taskId: string): void {
    const task = this.tasks.find(t => t.id === taskId);
    if (task) {
      task.completedPomodoros++;
      if (task.completedPomodoros >= task.estimatedPomodoros) {
        task.status = 'completed';
        task.completedAt = new Date().toISOString();
      }
    }
  }
  
  getCurrentTask(): PomodoroTask | null {
    return this.tasks.find(t => t.id === this.currentTaskId) || null;
  }
  
  getTasksByStatus(status: PomodoroTask['status']): PomodoroTask[] {
    return this.tasks.filter(t => t.status === status);
  }
  
  getTodayCompletedCount(): number {
    const today = new Date().toISOString().split('T')[0];
    return this.tasks.filter(t => 
      t.status === 'completed' && 
      t.completedAt?.startsWith(today)
    ).length;
  }
}

六、UI交互设计

6.1 番茄钟主界面

@Component
struct PomodoroView {
  @State currentState: PomodoroState = PomodoroState.IDLE;
  @State remainingSeconds: number = 25 * 60;
  @State completedSessions: number = 0;
  @State dailyGoal: number = 8;
  @State isRunning: boolean = false;
  
  private engine = new PomodoroEngine();
  
  build() {
    Column() {
      // 状态标签
      Text(this.getStateText())
        .fontSize(16)
        .fontColor(this.getStateColor())
        .fontWeight(FontWeight.Medium)
        .margin({ top: 32 })
      
      // 进度环 + 时间
      Stack() {
        // 背景环
        Circle()
          .width(240)
          .height(240)
          .fill('none')
          .stroke('#E0E0E0')
          .strokeWidth(8)
        
        // 进度环
        Circle()
          .width(240)
          .height(240)
          .fill('none')
          .stroke(this.getStateColor())
          .strokeWidth(8)
          .strokeDashOffset(this.getProgress() * 753.98)
          .rotate({ angle: -90 })
        
        // 时间显示
        Column() {
          Text(this.engine.formatTime(this.remainingSeconds))
            .fontSize(56)
            .fontWeight(FontWeight.Bold)
            .fontColor('#333333')
          
          Text(`番茄 ${this.completedSessions}/${this.dailyGoal}`)
            .fontSize(14)
            .fontColor('#666666')
            .margin({ top: 8 })
        }
      }
      .width(240)
      .height(240)
      .margin({ top: 32 })
      
      // 控制按钮
      Row({ space: 16 }) {
        this.renderControlButton('⏮', () => this.engine.reset(), '#E0E0E0')
        this.renderMainButton()
        this.renderControlButton('⏭', () => this.engine.startBreak(), '#E0E0E0')
      }
      .margin({ top: 32 })
      
      // 设置区域
      this.renderSettings()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
  
  @Builder
  private renderMainButton() {
    Button()
      .width(72)
      .height(72)
      .backgroundColor(this.isRunning ? '#FF9800' : '#4CAF50')
      .borderRadius(36)
      .onClick(() => {
        if (this.currentState === PomodoroState.IDLE) {
          this.engine.startFocus();
        } else if (this.isRunning) {
          this.engine.pause();
        } else {
          this.engine.resume();
        }
      })
      .shadow({ radius: 8, color: '#30000000', offsetY: 4 })
  }
  
  private getStateText(): string {
    switch (this.currentState) {
      case PomodoroState.IDLE: return '准备开始 🍅';
      case PomodoroState.FOCUS: return '专注工作 🎯';
      case PomodoroState.SHORT_BREAK: return '短休息 ☕';
      case PomodoroState.LONG_BREAK: return '长休息 🌿';
      case PomodoroState.PAUSED: return '已暂停 ⏸';
    }
  }
  
  private getStateColor(): string {
    switch (this.currentState) {
      case PomodoroState.IDLE: return '#4CAF50';
      case PomodoroState.FOCUS: return '#E53935';
      case PomodoroState.SHORT_BREAK: return '#4CAF50';
      case PomodoroState.LONG_BREAK: return '#2196F3';
      case PomodoroState.PAUSED: return '#FF9800';
    }
  }
  
  private getProgress(): number {
    if (this.currentState === PomodoroState.IDLE) return 0;
    const total = 25 * 60;
    return (total - this.remainingSeconds) / total;
  }
}

七、总结

7.1 核心技术要点

  1. 番茄工作法引擎:完整实现专注/短休息/长休息的自动循环,支持自动切换模式。
  2. 高精度定时器:使用系统时间校正的递归setTimeout,避免setInterval的时间漂移问题。
  3. 状态机管理:6种状态(IDLE/FOCUS/SHORT_BREAK/LONG_BREAK/PAUSED)的完整转换逻辑。
  4. 统计系统:日/周/月维度的番茄钟完成统计,支持数据趋势分析。
  5. 任务关联:每个番茄钟可与具体任务关联,实现时间追踪和工作量估算。
  6. 进度可视化:圆环进度条实时显示当前番茄钟的完成进度。

7.2 扩展方向

  1. 白噪音集成:内置多种专注白噪音(雨声、海浪、森林等)。
  2. 专注力分析:基于中断次数和完成率分析用户的专注力趋势。
  3. 团队协作:共享番茄钟进度,团队同步工作节奏。
  4. 智能提醒:基于AI分析最佳工作时段,自动安排番茄钟。
  5. 数据导出:支持导出CSV/JSON格式的工作统计数据。

7.3 核心代码量统计

模块 核心代码行数 接口数 组件数
番茄钟引擎 180 8 -
精确计时器 70 5 -
统计计算器 100 5 -
任务管理器 120 7 -
UI组件 300 4 5
总计 770 29 5

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐