欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net

示例效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1. 导论:睡眠惯性与脑电频段的物理坍缩

在传统的健康监控系统中,睡眠管理通常被简化为一条线性的一维倒计时。然而,现代脑神经科学与睡眠物理学表明,人类的睡眠绝不是一个均质的过程,而是一个由 N R E M NREM NREM(非快速眼动期)与 R E M REM REM(快速眼动期)交替形成的振荡波段,每个周期严格锁定在约为 90 90 90 分钟的物理节律内。

当人类在深度睡眠(Deep Sleep,脑电频率处于低频高幅的 Δ \Delta Δ 波状态)时被强行唤醒,大脑将经历极其严重的“睡眠惯性(Sleep Inertia)”,这是一种类似在深海中被瞬间拉出水面的脑神经减压综合征。本架构工程彻底抛弃了扁平的闹钟思维,利用 Flutter 的 CustomPainterAnimationController,建立了一套极度硬核的“昼夜节律与睡眠相位测绘台”。系统通过推演复杂的脑波波动函数,直击睡眠周期的波谷与波峰,精准测算出能让神经元物理状态损失最小的最佳“苏醒相位点”。

2. 生物钟数据建模:90分钟相位的非线性周期

要在代码层面防御睡眠剥夺与睡眠惯性,必须建立精确的离散数学映射模型,将人类的睡眠分解为 90 90 90 分钟的相位槽(Phase Slots)。

2.1 睡眠惯性的指数衰减方程

如果我们在时间点 t t t 醒来,所受到的睡眠惯性强度 I ( t ) I(t) I(t) 取决于距离上一次 REM 睡眠期结束的时间 t r e m t_{rem} trem。在理想状态下,我们构建了以下卡玛拉衰减函数用于判定苏醒风险:

I ( t ) = I 0 ⋅ e − k ( t − t r e m ) ⋅ δ ( Stage ) I(t) = I_0 \cdot e^{-k(t - t_{rem})} \cdot \delta(\text{Stage}) I(t)=I0ek(ttrem)δ(Stage)

其中,当处于 Deep Sleep 阶段时, δ \delta δ 系数达到最大值;而处于 REM 阶段末尾时, δ ≈ 0 \delta \approx 0 δ0

2.2 相位槽结构体的解耦设计

在系统架构中,我们提取出了包含了时间常量与循环计算的状态机类:

extension

calculates

1

*

«enumeration»

SleepQuality

optimal

grogginess

deprived

SleepQualityExt

+Color color

+String label

SleepCycleOption

+int cycles

+int totalMinutes

+TimeOfDay wakeTime

+SleepQuality quality

+String durationString

SleepPhaseDashboard

+TimeOfDay selectedBedTime

+List<SleepCycleOption> calculatedOptions

+_calculateCycles()

通过内置 14 14 14 分钟的 fallAsleepLatency(入睡潜伏期偏置),我们将每一个睡眠实体 SleepCycleOption 的目标唤醒点强制对齐在第 3 3 3 乃至第 6 6 6 个物理周期的末端波峰处。

3. 极客频段引擎:多模态正弦节律的拓扑渲染

为了展现脑电波(EEG)在不同睡眠相位下的剧烈波动,我们启用了 Canvas 引擎对脑波序列进行了实时光栅化渲染。这绝非简单的正弦曲线,而是一个叠加了深度衰减与高频扰动的复合干涉波。

3.1 复合频段干涉公式

BrainWavePainter 引擎内部,我们为整个坐标系的横轴(时间进度 x x x)映射了以下连续分段波动方程 Ψ ( x ) \Psi(x) Ψ(x)

Ψ ( x ) = A b a s e ( x ) + ∑ n = 1 N [ A d ( x ) sin ⁡ ( 2 π f d x ) + A r e m ( x ) cos ⁡ ( 2 π f r e m x + ϕ ( t ) ) ] \Psi(x) = A_{base}(x) + \sum_{n=1}^{N} \Big[ A_d(x) \sin(2\pi f_d x) + A_{rem}(x) \cos(2\pi f_{rem} x + \phi(t)) \Big] Ψ(x)=Abase(x)+n=1N[Ad(x)sin(2πfdx)+Arem(x)cos(2πfremx+ϕ(t))]

在进入“深度睡眠区(Deep Sleep)”时,振幅 A d A_d Ad 放大而频率 f d f_d fd 降低,模拟出低沉平缓的 Δ \Delta Δ 脑波;而在跨入“REM(苏醒窗)”时,受到时间参数 t t t 的动画影响, A r e m A_{rem} Arem 发生高频位移,呈现出快速眼动期的脑电抖动与活跃态。

< 0.2

< 0.6

< 0.8

> 0.8

循环每个90分钟 Cycle

判断当前周期进度 % 1.0

Wake to Deep 线性下潜

Deep Sleep 附加低频波动

Rising 上升潜出深睡眠

REM 窗体高频抖动干涉

随着周期数加深,降低下潜深度

判断周期边界,渲染发光最佳苏醒点

输出连续 Canvas Path 指令

3.2 自适应时间追踪与状态机偏置

系统内部同时开启了基于 Timer.periodic 的时钟流处理系统。如果没有人为覆盖入睡基点,系统将每隔 60 60 60 秒重新向状态机发送一次真实世界的时间戳,并推动所有右侧面板内的推荐节点发生雪崩式链式位移更新,保证用户获得的都是“如果我现在立刻闭眼”的最精确反馈。

4. 核心代码解构与极客实现法则

本架构中的代码不仅处理了繁杂的极坐标变换,更在布局与动画防爆层面上做出了卓越的防御。以下是四段封神级别的核心代码解构。

4.1 苏醒相位演算状态闭包

这是整个生物节律运算的物理内核,将分钟级别偏移严格卡在相位点:

    for (int i = 1; i <= 7; i++) {
      int totalSleep = i * cycleDuration;
      int wakeMinutes = baseMinutes + totalSleep;
      
      int wakeHour = (wakeMinutes ~/ 60) % 24;
      int wakeMinute = wakeMinutes % 60;

      SleepQuality q;
      if (i < 3) {
        q = SleepQuality.deprived;
      } else if (i >= 4 && i <= 6) {
        q = SleepQuality.optimal;
      } else {
        q = SleepQuality.grogginess; // 模拟过长睡眠或处于非最佳苏醒区
      }

      options.add(SleepCycleOption(
        cycles: i,

通过简单的整除模运算 % 24,极其干净地跨越了午夜的时间壁垒,并完成了多枚举维度的分类打击。

4.2 响应式界面引擎防溢出机制

延续对屏幕空间坍缩的管理,这里严格限制了 CustomScrollView 的装配边界:

          if (isCompact) {
            return CustomScrollView(
              slivers: [
                SliverToBoxAdapter(
                  child: SizedBox(
                    height: 380,
                    child: _buildBrainWaveAnalyzer(isCompact: true),
                  ),
                ),
                SliverToBoxAdapter(
                  child: Container(height: 1, color: const Color(0xFF1E1E2E)),
                ),
                SliverToBoxAdapter(
                  child: _buildControlPanel(),
                ),
                SliverList(
                  delegate: SliverChildBuilderDelegate(
                    (context, index) {
                      return _buildCycleCard(_calculatedOptions[index]);
                    },

在窄屏状态下,将顶部脑波图与底部的动态卡片列表整合至同一纵向滑动槽道中,直接物理阻断了任何纵向 OVERFLOWED 的可能性。

4.3 高维分段睡眠波形方程生成

在 Canvas 渲染器中,这一段利用数学强行挤压出了深度睡眠的低频曲线与 REM 的高频抖动:

      final double cyclePhase = progress % 1.0;
      double y;
      
      // Shape the sleep hypnogram using piecewise or a skewed trig function
      if (cyclePhase < 0.2) {
        // Falling asleep (Wake to Deep)
        y = ui.lerpDouble(wakeY, deepY, cyclePhase / 0.2)!;
      } else if (cyclePhase < 0.6) {
        // Deep sleep bottom
        y = deepY - math.sin((cyclePhase - 0.2) / 0.4 * math.pi) * 20; // Small fluctuation at bottom
      } else if (cyclePhase < 0.8) {
        // Rising to REM
        y = ui.lerpDouble(deepY, remY, (cyclePhase - 0.6) / 0.2)!;
      } else {
        // REM Phase
        y = remY + math.sin((cyclePhase - 0.8) / 0.2 * math.pi * 2) * 10;
        
        // Add a high-frequency jitter to represent REM brain waves, moving with animation
        y += math.sin((x + animationValue * 100) * 0.5) * 5;
      }
      
      // Tweak depth based on cycle index (later cycles have less deep sleep, more REM)
      final int currentCycleIndex = progress.floor();
      final double depthAttenuation = currentCycleIndex * 15.0; // Deep sleep gets shallower

这里通过 progress % 1.0 在时间域内斩断 90 90 90 分钟的循环,并在后半夜(currentCycleIndex 增大时)通过 depthAttenuation 逐渐削弱深度睡眠区,完美贴合人类睡眠前沉后浅的生物节律。

4.4 最佳苏醒窗口脉冲渲染器

为了引导用户直击最薄弱的苏醒防线,系统在波峰尽头注入了高亮脉冲点阵:

    // Draw optimal wake points (pulsing)
    final Paint pointPaint = Paint()
      ..color = const Color(0xFF00E5FF)
      ..style = PaintingStyle.fill;
      
    final Paint pulsePaint = Paint()
      ..color = const Color(0xFF00E5FF).withValues(alpha: 0.3 * (1 - animationValue))
      ..style = PaintingStyle.fill;

    for (final pt in optimalWakePoints) {
      canvas.drawCircle(pt, 4, pointPaint);
      canvas.drawCircle(pt, 4 + animationValue * 15, pulsePaint);

随着 animationValue 的线性推动,半透明的脉冲层向外极速扩张并消散,产生如同雷达信号般极具科技压迫感的科幻呼吸特效。

5. 结语:在比特海中测量意识坍缩

我们不仅在创造一个应用,更是在挑战人类生命体征数字化的极限。“昼夜节律与睡眠相位测绘台”打破了死板的数字时钟边界,用最底层、最纯粹的 Canvas 画布指令集硬生生勾勒出了在神经元层面发生的波形更迭。通过对 90 90 90 分钟相位的精算捕获与衰减函数渲染,系统把虚无缥缈的“最佳苏醒点”具象化为发光的深海脉冲雷达。当跨越平台的技术不再受限于控件的禁锢时,工业级代码就能在睡眠神经学的这片星辰大海中劈波斩浪!

完整代码

import 'dart:async';
import 'dart:math' as math;
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  SystemChrome.setPreferredOrientations([
    DeviceOrientation.landscapeLeft,
    DeviceOrientation.landscapeRight,
    DeviceOrientation.portraitUp,
  ]);
  runApp(const CircadianSleepApp());
}

class CircadianSleepApp extends StatelessWidget {
  const CircadianSleepApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '昼夜节律与睡眠相位测绘台',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        brightness: Brightness.dark,
        scaffoldBackgroundColor: const Color(0xFF03030A), // 极夜深渊黑
        useMaterial3: true,
        fontFamily: 'Roboto',
      ),
      home: const SleepPhaseDashboard(),
    );
  }
}

// ==========================================
// 领域模型 (Domain Models)
// ==========================================

enum SleepQuality {
  optimal,   // 极佳 (周期末尾苏醒)
  grogginess, // 睡眠惯性 (深度睡眠被唤醒)
  deprived,  // 睡眠剥夺 (周期太少)
}

extension SleepQualityExt on SleepQuality {
  Color get color {
    switch (this) {
      case SleepQuality.optimal:
        return const Color(0xFF00E5FF); // 霓虹青
      case SleepQuality.grogginess:
        return const Color(0xFFEAB308); // 警告黄
      case SleepQuality.deprived:
        return const Color(0xFFEF4444); // 剥夺红
    }
  }

  String get label {
    switch (this) {
      case SleepQuality.optimal:
        return '相位重合 (Optimal)';
      case SleepQuality.grogginess:
        return '睡眠惯性 (Grogginess)';
      case SleepQuality.deprived:
        return '周期不足 (Deprived)';
    }
  }
}

class SleepCycleOption {
  final int cycles;
  final int totalMinutes; // 包含入睡时间
  final TimeOfDay wakeTime;
  final SleepQuality quality;

  SleepCycleOption({
    required this.cycles,
    required this.totalMinutes,
    required this.wakeTime,
    required this.quality,
  });

  String get durationString {
    final int h = totalMinutes ~/ 60;
    final int m = totalMinutes % 60;
    return '${h}h ${m}m';
  }
}

// ==========================================
// 核心状态控制面板 (Main Dashboard)
// ==========================================

class SleepPhaseDashboard extends StatefulWidget {
  const SleepPhaseDashboard({super.key});

  @override
  State<SleepPhaseDashboard> createState() => _SleepPhaseDashboardState();
}

class _SleepPhaseDashboardState extends State<SleepPhaseDashboard> with TickerProviderStateMixin {
  TimeOfDay? _selectedBedTime;
  List<SleepCycleOption> _calculatedOptions = [];
  
  late AnimationController _brainWaveController;
  Timer? _realtimeTimer;
  TimeOfDay _currentTime = TimeOfDay.now();

  static const int fallAsleepLatency = 14; // 平均入睡潜伏期 14 分钟
  static const int cycleDuration = 90; // 睡眠周期 90 分钟

  @override
  void initState() {
    super.initState();
    _brainWaveController = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 4),
    )..repeat();

    _realtimeTimer = Timer.periodic(const Duration(minutes: 1), (timer) {
      setState(() {
        _currentTime = TimeOfDay.now();
        if (_selectedBedTime == null) {
          _calculateCycles(_currentTime);
        }
      });
    });

    _calculateCycles(_currentTime);
  }

  @override
  void dispose() {
    _brainWaveController.dispose();
    _realtimeTimer?.cancel();
    super.dispose();
  }

  void _calculateCycles(TimeOfDay bedTime) {
    List<SleepCycleOption> options = [];
    int baseMinutes = bedTime.hour * 60 + bedTime.minute + fallAsleepLatency;

    for (int i = 1; i <= 7; i++) {
      int totalSleep = i * cycleDuration;
      int wakeMinutes = baseMinutes + totalSleep;
      
      int wakeHour = (wakeMinutes ~/ 60) % 24;
      int wakeMinute = wakeMinutes % 60;

      SleepQuality q;
      if (i < 3) {
        q = SleepQuality.deprived;
      } else if (i >= 4 && i <= 6) {
        q = SleepQuality.optimal;
      } else {
        q = SleepQuality.grogginess; // 模拟过长睡眠或处于非最佳苏醒区
      }

      options.add(SleepCycleOption(
        cycles: i,
        totalMinutes: totalSleep + fallAsleepLatency,
        wakeTime: TimeOfDay(hour: wakeHour, minute: wakeMinute),
        quality: q,
      ));
    }
    
    // 只保留 3-6 个周期作为主要建议
    setState(() {
      _calculatedOptions = options.where((o) => o.cycles >= 3 && o.cycles <= 6).toList();
      // 反转,让最长睡眠时间在最上面
      _calculatedOptions = _calculatedOptions.reversed.toList();
    });
  }

  Future<void> _selectTime(BuildContext context) async {
    final TimeOfDay? picked = await showTimePicker(
      context: context,
      initialTime: _selectedBedTime ?? TimeOfDay.now(),
      builder: (context, child) {
        return Theme(
          data: ThemeData.dark().copyWith(
            colorScheme: const ColorScheme.dark(
              primary: Color(0xFF00E5FF),
              onPrimary: Colors.black,
              surface: Color(0xFF0A0A1A),
              onSurface: Colors.white,
            ),
          ),
          child: child!,
        );
      },
    );
    if (picked != null) {
      setState(() {
        _selectedBedTime = picked;
        _calculateCycles(picked);
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: LayoutBuilder(
        builder: (context, constraints) {
          final isCompact = constraints.maxWidth < 900;
          
          if (isCompact) {
            return CustomScrollView(
              slivers: [
                SliverToBoxAdapter(
                  child: SizedBox(
                    height: 380,
                    child: _buildBrainWaveAnalyzer(isCompact: true),
                  ),
                ),
                SliverToBoxAdapter(
                  child: Container(height: 1, color: const Color(0xFF1E1E2E)),
                ),
                SliverToBoxAdapter(
                  child: _buildControlPanel(),
                ),
                SliverList(
                  delegate: SliverChildBuilderDelegate(
                    (context, index) {
                      return _buildCycleCard(_calculatedOptions[index]);
                    },
                    childCount: _calculatedOptions.length,
                  ),
                ),
              ],
            );
          }

          return Row(
            children: [
              Expanded(
                flex: 4,
                child: Column(
                  children: [
                    _buildControlPanel(),
                    Expanded(
                      child: ListView.builder(
                        itemCount: _calculatedOptions.length,
                        itemBuilder: (context, index) {
                          return _buildCycleCard(_calculatedOptions[index]);
                        },
                      ),
                    ),
                  ],
                ),
              ),
              Container(width: 1, color: const Color(0xFF1E1E2E)),
              Expanded(
                flex: 5,
                child: _buildBrainWaveAnalyzer(isCompact: false),
              ),
            ],
          );
        },
      ),
    );
  }

  Widget _buildControlPanel() {
    final displayTime = _selectedBedTime ?? _currentTime;
    
    return Container(
      color: const Color(0xFF050510),
      padding: const EdgeInsets.all(24.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              const Icon(Icons.waves, color: Color(0xFF00E5FF), size: 28),
              const SizedBox(width: 12),
              const Text(
                '相位苏醒测绘引擎',
                style: TextStyle(
                  fontSize: 22,
                  fontWeight: FontWeight.w900,
                  color: Colors.white,
                  letterSpacing: 2,
                ),
              ),
              const Spacer(),
              Container(
                padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
                decoration: BoxDecoration(
                  color: const Color(0xFF6D28D9).withValues(alpha: 0.2),
                  border: Border.all(color: const Color(0xFF6D28D9)),
                  borderRadius: BorderRadius.circular(4),
                ),
                child: const Text(
                  'REM PHASE',
                  style: TextStyle(
                    color: Color(0xFFC4B5FD),
                    fontWeight: FontWeight.bold,
                    fontSize: 12,
                    letterSpacing: 1,
                  ),
                ),
              ),
            ],
          ),
          const SizedBox(height: 32),
          Text(
            '假设入睡节点 (Bedtime Node)',
            style: TextStyle(
              fontSize: 12,
              color: Colors.grey[500],
              fontWeight: FontWeight.bold,
              letterSpacing: 1,
            ),
          ),
          const SizedBox(height: 12),
          InkWell(
            onTap: () => _selectTime(context),
            borderRadius: BorderRadius.circular(12),
            child: Container(
              padding: const EdgeInsets.all(20),
              decoration: BoxDecoration(
                color: const Color(0xFF0A0A1A),
                borderRadius: BorderRadius.circular(12),
                border: Border.all(color: const Color(0xFF1E1E2E)),
              ),
              child: Row(
                children: [
                  Text(
                    displayTime.format(context),
                    style: const TextStyle(
                      fontSize: 42,
                      fontWeight: FontWeight.w900,
                      color: Colors.white,
                      fontFamily: 'monospace',
                    ),
                  ),
                  const Spacer(),
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.end,
                    children: [
                      Text(
                        _selectedBedTime == null ? 'REAL-TIME SYNC' : 'MANUAL OVERRIDE',
                        style: TextStyle(
                          fontSize: 10,
                          color: _selectedBedTime == null ? const Color(0xFF10B981) : const Color(0xFFEAB308),
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 8),
                      const Icon(Icons.edit_calendar, color: Colors.grey, size: 24),
                    ],
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),
          Row(
            children: [
              Icon(Icons.info_outline, color: Colors.grey[600], size: 14),
              const SizedBox(width: 8),
              Text(
                '已硬编码包含 $fallAsleepLatency 分钟的入睡潜伏期偏置。',
                style: TextStyle(
                  fontSize: 12,
                  color: Colors.grey[600],
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }

  Widget _buildCycleCard(SleepCycleOption option) {
    return Container(
      margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
      padding: const EdgeInsets.all(20),
      decoration: BoxDecoration(
        color: const Color(0xFF0A0A1A),
        borderRadius: BorderRadius.circular(16),
        border: Border.all(color: option.quality.color.withValues(alpha: 0.3)),
        boxShadow: [
          BoxShadow(
            color: option.quality.color.withValues(alpha: 0.05),
            blurRadius: 20,
            spreadRadius: -5,
          )
        ],
      ),
      child: Row(
        children: [
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Row(
                  children: [
                    Container(
                      width: 8,
                      height: 8,
                      decoration: BoxDecoration(
                        color: option.quality.color,
                        shape: BoxShape.circle,
                        boxShadow: [
                          BoxShadow(
                            color: option.quality.color,
                            blurRadius: 8,
                          )
                        ],
                      ),
                    ),
                    const SizedBox(width: 12),
                    Text(
                      'TARGET WAKE TIME',
                      style: TextStyle(
                        fontSize: 10,
                        color: Colors.grey[500],
                        fontWeight: FontWeight.bold,
                        letterSpacing: 2,
                      ),
                    ),
                  ],
                ),
                const SizedBox(height: 8),
                Text(
                  option.wakeTime.format(context),
                  style: TextStyle(
                    fontSize: 32,
                    fontWeight: FontWeight.w900,
                    color: option.quality.color,
                    fontFamily: 'monospace',
                  ),
                ),
              ],
            ),
          ),
          Container(
            padding: const EdgeInsets.only(left: 20),
            decoration: const BoxDecoration(
              border: Border(left: BorderSide(color: Color(0xFF1E1E2E))),
            ),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.end,
              children: [
                Text(
                  '${option.cycles} CYCLES',
                  style: const TextStyle(
                    fontSize: 16,
                    fontWeight: FontWeight.bold,
                    color: Colors.white,
                  ),
                ),
                const SizedBox(height: 4),
                Text(
                  option.durationString,
                  style: TextStyle(
                    fontSize: 14,
                    color: Colors.grey[400],
                  ),
                ),
                const SizedBox(height: 8),
                Container(
                  padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
                  decoration: BoxDecoration(
                    color: option.quality.color.withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(4),
                  ),
                  child: Text(
                    option.quality.label,
                    style: TextStyle(
                      fontSize: 10,
                      fontWeight: FontWeight.bold,
                      color: option.quality.color,
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildBrainWaveAnalyzer({required bool isCompact}) {
    return Container(
      color: const Color(0xFF03030A),
      child: Stack(
        children: [
          // Background Grid
          CustomPaint(
            size: Size.infinite,
            painter: GridMatrixPainter(),
          ),
          // Brain Wave Rendering
          AnimatedBuilder(
            animation: _brainWaveController,
            builder: (context, child) {
              return CustomPaint(
                size: Size.infinite,
                painter: BrainWavePainter(
                  animationValue: _brainWaveController.value,
                  cycles: _calculatedOptions.isNotEmpty ? _calculatedOptions.first.cycles : 6,
                ),
              );
            },
          ),
          // Overlay Texts
          Positioned(
            top: 32,
            left: 32,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text(
                  'NREM / REM 波函数干涉拓扑',
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 16,
                    fontWeight: FontWeight.bold,
                    letterSpacing: 2,
                  ),
                ),
                const SizedBox(height: 8),
                Text(
                  'ΔT = $cycleDuration MIN / Phase',
                  style: TextStyle(
                    color: const Color(0xFF00E5FF),
                    fontSize: 12,
                    fontWeight: FontWeight.bold,
                    fontFamily: 'monospace',
                  ),
                ),
              ],
            ),
          ),
          // Legend
          Positioned(
            bottom: 32,
            left: 32,
            child: Row(
              children: [
                _buildLegendItem(const Color(0xFF00E5FF), 'REM (苏醒窗)'),
                const SizedBox(width: 16),
                _buildLegendItem(const Color(0xFF6D28D9), 'DEEP (睡眠惯性区)'),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildLegendItem(Color color, String label) {
    return Row(
      children: [
        Container(
          width: 12,
          height: 4,
          decoration: BoxDecoration(
            color: color,
            boxShadow: [
              BoxShadow(color: color, blurRadius: 6),
            ],
          ),
        ),
        const SizedBox(width: 8),
        Text(
          label,
          style: TextStyle(
            color: Colors.grey[400],
            fontSize: 10,
            fontWeight: FontWeight.bold,
          ),
        ),
      ],
    );
  }
}

// ==========================================
// 极客物理渲染:背景坐标系与波形引擎
// ==========================================

class GridMatrixPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = const Color(0xFF1E1E2E).withValues(alpha: 0.5)
      ..strokeWidth = 1.0;

    const double spacing = 40.0;
    
    // Vertical lines
    for (double i = 0; i < size.width; i += spacing) {
      canvas.drawLine(Offset(i, 0), Offset(i, size.height), paint);
    }
    // Horizontal lines
    for (double i = 0; i < size.height; i += spacing) {
      canvas.drawLine(Offset(0, i), Offset(size.width, i), paint);
    }
    
    // Y-Axis labels (Stages)
    final textPainter = TextPainter(textDirection: TextDirection.ltr);
    final stages = ['WAKE', 'REM', 'LIGHT', 'DEEP'];
    
    for (int i = 0; i < stages.length; i++) {
      textPainter.text = TextSpan(
        text: stages[i],
        style: TextStyle(
          color: Colors.grey[600],
          fontSize: 10,
          fontWeight: FontWeight.bold,
          fontFamily: 'monospace',
        ),
      );
      textPainter.layout();
      final double yPos = (size.height / 5) * (i + 1) - textPainter.height / 2;
      textPainter.paint(canvas, Offset(8, yPos));
      
      // Draw sub-axis
      final axisPaint = Paint()
        ..color = const Color(0xFF1E1E2E)
        ..strokeWidth = 1.0
        ..style = PaintingStyle.stroke;
      
      final path = Path()
        ..moveTo(40, yPos + textPainter.height / 2)
        ..lineTo(size.width, yPos + textPainter.height / 2);
        
      canvas.drawPath(path.dashPath([5, 5]), axisPaint);
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

// Helper extension for dash path
extension PathExtension on Path {
  Path dashPath(List<double> dashArray) {
    Path dest = Path();
    for (final metric in computeMetrics()) {
      double distance = 0.0;
      bool draw = true;
      int index = 0;
      while (distance < metric.length) {
        final double len = dashArray[index % dashArray.length];
        if (draw) {
          dest.addPath(metric.extractPath(distance, distance + len), Offset.zero);
        }
        distance += len;
        draw = !draw;
        index++;
      }
    }
    return dest;
  }
}


class BrainWavePainter extends CustomPainter {
  final double animationValue;
  final int cycles;

  BrainWavePainter({
    required this.animationValue,
    required this.cycles,
  });

  @override
  void paint(Canvas canvas, Size size) {
    if (size.width <= 0 || size.height <= 0) return;

    final double startX = 60.0;
    final double endX = size.width - 20.0;
    final double graphWidth = endX - startX;
    
    // Defined Y levels
    final double wakeY = (size.height / 5) * 1;
    final double remY = (size.height / 5) * 2;
    final double deepY = (size.height / 5) * 4;

    final Path wavePath = Path();
    final List<Offset> optimalWakePoints = [];

    // The length of one cycle in pixels
    final double cycleWidth = graphWidth / cycles;

    for (double x = 0; x <= graphWidth; x++) {
      // Calculate progress across all cycles
      final double progress = x / cycleWidth; 
      
      // We want a wave that starts at wakeY, drops to deepY, comes back up to remY.
      // A cycle goes: Wake -> Light -> Deep -> Light -> REM.
      // Using a skewed cosine wave to simulate the hypnogram.
      
      final double cyclePhase = progress % 1.0;
      double y;
      
      // Shape the sleep hypnogram using piecewise or a skewed trig function
      if (cyclePhase < 0.2) {
        // Falling asleep (Wake to Deep)
        y = ui.lerpDouble(wakeY, deepY, cyclePhase / 0.2)!;
      } else if (cyclePhase < 0.6) {
        // Deep sleep bottom
        y = deepY - math.sin((cyclePhase - 0.2) / 0.4 * math.pi) * 20; // Small fluctuation at bottom
      } else if (cyclePhase < 0.8) {
        // Rising to REM
        y = ui.lerpDouble(deepY, remY, (cyclePhase - 0.6) / 0.2)!;
      } else {
        // REM Phase
        y = remY + math.sin((cyclePhase - 0.8) / 0.2 * math.pi * 2) * 10;
        
        // Add a high-frequency jitter to represent REM brain waves, moving with animation
        y += math.sin((x + animationValue * 100) * 0.5) * 5;
      }
      
      // Tweak depth based on cycle index (later cycles have less deep sleep, more REM)
      final int currentCycleIndex = progress.floor();
      final double depthAttenuation = currentCycleIndex * 15.0; // Deep sleep gets shallower
      if (y > deepY - 30) {
        y -= depthAttenuation.clamp(0, (deepY - remY) * 0.8);
      }

      final Offset pt = Offset(startX + x, y);

      if (x == 0) {
        wavePath.moveTo(pt.dx, pt.dy);
      } else {
        wavePath.lineTo(pt.dx, pt.dy);
      }

      // Detect end of cycle for optimal wake points
      if ((cyclePhase > 0.98 || x == graphWidth) && currentCycleIndex > 0) {
        if (!optimalWakePoints.any((p) => (p.dx - pt.dx).abs() < 10)) {
           optimalWakePoints.add(pt);
        }
      }
    }

    // Gradient stroke for the wave
    final Paint wavePaint = Paint()
      ..shader = ui.Gradient.linear(
        Offset(0, remY),
        Offset(0, deepY),
        [
          const Color(0xFF00E5FF), // REM Color
          const Color(0xFF6D28D9), // DEEP Color
        ],
      )
      ..style = PaintingStyle.stroke
      ..strokeWidth = 3.0
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round;

    canvas.drawPath(wavePath, wavePaint);

    // Draw optimal wake points (pulsing)
    final Paint pointPaint = Paint()
      ..color = const Color(0xFF00E5FF)
      ..style = PaintingStyle.fill;
      
    final Paint pulsePaint = Paint()
      ..color = const Color(0xFF00E5FF).withValues(alpha: 0.3 * (1 - animationValue))
      ..style = PaintingStyle.fill;

    for (final pt in optimalWakePoints) {
      canvas.drawCircle(pt, 4, pointPaint);
      canvas.drawCircle(pt, 4 + animationValue * 15, pulsePaint);
      
      // Draw vertical drop line
      final Paint dropPaint = Paint()
        ..color = const Color(0xFF00E5FF).withValues(alpha: 0.3)
        ..strokeWidth = 1.0;
      canvas.drawLine(pt, Offset(pt.dx, size.height), dropPaint);
    }
  }

  @override
  bool shouldRepaint(covariant BrainWavePainter oldDelegate) {
    return oldDelegate.animationValue != animationValue || 
           oldDelegate.cycles != cycles;
  }
}

Logo

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

更多推荐