no_health_data_page.dart 10.7 KB
import 'package:doublefeel_flutter/core/theme/app_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';

class NoHealthDataPage extends StatelessWidget {
  const NoHealthDataPage({
    super.key,
    this.onRefresh,
    this.onHelp,
  });

  static const _backgroundColor = Color(0xFFF5F2FF);

  static const _placeholderColor = Color(0xFFD9D9D9);
  static const _imagePlaceholderColor = Color(0xFFF3F3F3);
  static const _screenShotBackground = Color(0xFFF2F2F6);

  final VoidCallback? onRefresh;
  final VoidCallback? onHelp;

  @override
  Widget build(BuildContext context) {
    return AnnotatedRegion<SystemUiOverlayStyle>(
      value: AppTheme.systemUiOverlayStyle.copyWith(
        systemNavigationBarColor: _backgroundColor,
      ),
      child: Scaffold(
        backgroundColor: _backgroundColor,
        body: Column(
          children: [
            SafeArea(
              bottom: false,
              child: _TopBar(onRefresh: onRefresh),
            ),
            Expanded(
              child: ListView(
                physics: const ClampingScrollPhysics(),
                padding: EdgeInsets.fromLTRB(
                  16,
                  4,
                  16,
                  20 + MediaQuery.paddingOf(context).bottom,
                ),
                children: [
                  const _PageHeading(),
                  const SizedBox(height: 24),
                  const _InfoCard(
                    title: '错误1:Apple Watch 数据不可用',
                    body:
                        '看起来你在过去 12 个月内没有使用 Apple Watch。如果你刚开始使用 Apple Watch 并已启用所有数据权限,这条信息可能仍会出现。请继续佩戴 Apple Watch 以允许数据采集,或在首页手动添加 HRV 数据。',
                  ),
                  const SizedBox(height: 12),
                  const _AuthorizationCard(),
                  const SizedBox(height: 12),
                  const _SystemIssueCard(),
                  const SizedBox(height: 16),
                  Center(
                    child: GestureDetector(
                      behavior: HitTestBehavior.opaque,
                      onTap: onHelp,
                      child: Padding(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 16,
                          vertical: 4,
                        ),
                        child: Text(
                          '需要帮助?',
                          style: TextStyle(
                            color: context.colors.primary,
                            fontSize: 12,
                            fontWeight: FontWeight.w400,
                            height: 17 / 12,
                          ),
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _TopBar extends StatelessWidget {
  const _TopBar({this.onRefresh});

  final VoidCallback? onRefresh;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 44,
      child: Stack(
        children: [
          Positioned(
            left: 4,
            top: 0,
            bottom: 0,
            child: GestureDetector(
              behavior: HitTestBehavior.opaque,
              onTap: () => Get.back<void>(),
              child: const SizedBox(
                width: 44,
                height: 44,
                child: Center(
                  child: Image(
                    image: AssetImage('assets/images/common/ic_nav_back.webp'),
                    width: 24,
                    height: 24,
                  ),
                ),
              ),
            ),
          ),
          Positioned(
            right: 16,
            top: 0,
            bottom: 0,
            child: GestureDetector(
              behavior: HitTestBehavior.opaque,
              onTap: onRefresh,
              child: Center(
                child: Text(
                  '刷新',
                  style: TextStyle(
                    color: context.colors.primary,
                    fontSize: 14,
                    fontWeight: FontWeight.w400,
                    height: 20 / 14,
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class _PageHeading extends StatelessWidget {
  const _PageHeading();

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Padding(
          padding: EdgeInsets.symmetric(horizontal: 21),
          child: Text(
            '无心率数据可用',
            textAlign: TextAlign.center,
            style: TextStyle(
              color: Color(0xFF14121E),
              fontSize: 20,
              fontWeight: FontWeight.w600,
              height: 28 / 20,
            ),
          ),
        ),
        SizedBox(height: 8),
        Padding(
          padding: EdgeInsets.symmetric(horizontal: 8),
          child: Text(
            'DoubleFeel无法从 Apple Health 获取你的 HRV 数据。请按照提示授予权限,然后点击右上角“刷新”继续。',
            textAlign: TextAlign.center,
            style: TextStyle(
              color: context.colors.textPrimary,
              fontSize: 14,
              fontWeight: FontWeight.w400,
              height: 18 / 14,
            ),
          ),
        ),
      ],
    );
  }
}

class _InfoCard extends StatelessWidget {
  const _InfoCard({
    required this.title,
    required this.body,
  });

  final String title;
  final String body;

  @override
  Widget build(BuildContext context) {
    return _CardShell(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Center(child: _IconPlaceholder()),
          const SizedBox(height: 10),
          Text(
            title,
            style: TextStyle(
              color: Colors.black,
              fontSize: 16,
              fontWeight: FontWeight.w600,
              height: 22 / 16,
            ),
          ),
          const SizedBox(height: 6),
          Text(
            body,
            style: TextStyle(
              color: context.colors.textSecondary,
              fontSize: 12,
              fontWeight: FontWeight.w400,
              height: 17 / 12,
            ),
          ),
        ],
      ),
    );
  }
}

class _AuthorizationCard extends StatelessWidget {
  const _AuthorizationCard();

  @override
  Widget build(BuildContext context) {
    return _CardShell(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Center(child: _IconPlaceholder()),
          SizedBox(height: 10),
          Text(
            '错误 2:健康数据访问未授权',
            style: TextStyle(
              color: Colors.black,
              fontSize: 16,
              fontWeight: FontWeight.w600,
              height: 22 / 16,
            ),
          ),
          SizedBox(height: 6),
          Text(
            'DoubleFeel 需要访问 Apple Health 数据的权限,以提供压力统计、提醒和建议。如果未授权,部分功能可能无法正常使用。\n\n请放心,所有健康数据仅存储在本地,不会上传。\n\n要启用权限,请按提示操作,并在 iOS 设置中选择 允许所有 → 健康 → DoubleFeel。',
            style: TextStyle(
              color: context.colors.textSecondary,
              fontSize: 12,
              fontWeight: FontWeight.w400,
              height: 17 / 12,
            ),
          ),
          SizedBox(height: 37),
          Image.asset(
            'assets/images/today/health_data_permission_step_1.png',
            height: 140,
            alignment: Alignment(0, 0.12),
          ),
          SizedBox(height: 20),
          Image.asset(
            'assets/images/today/health_data_permission_step_2.png',
            height: 172,
            alignment: Alignment(0, 0.24),
          ),
          SizedBox(height: 20),
          Image.asset(
            'assets/images/today/health_data_permission_step_3.png',
            height: 210,
            alignment: Alignment(0, -0.13),
          ),
          SizedBox(height: 20),
          Image.asset(
            'assets/images/today/health_data_permission_step_4.png',
            height: 167,
            alignment: Alignment(0, -0.54),
          ),
        ],
      ),
    );
  }
}

class _SystemIssueCard extends StatelessWidget {
  const _SystemIssueCard();

  @override
  Widget build(BuildContext context) {
    return _CardShell(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Center(child: _IconPlaceholder()),
          SizedBox(height: 10),
          Text(
            '错误 3:系统问题',
            style: TextStyle(
              color: Colors.black,
              fontSize: 16,
              fontWeight: FontWeight.w600,
              height: 22 / 16,
            ),
          ),
          SizedBox(height: 6),
          Text(
            '根据用户反馈,我们发现 HRV 或心率数据可能缺失的两个原因:\n\n'
            '1. Apple Watch 未连接\n'
            '    · 如果你的 Apple Watch 长时间未佩戴,心率数据可能无法采集。\n'
            '    · 请检查 iOS 健康 App → “我的手表”,确认最近的心率数据是否在佩戴 Apple Watch 时记录。\n'
            '    · 如果没有,请尝试佩戴 Apple Watch 进行数据采集,并打开 Apple 支持的心率功能。\n\n'
            '2. 过去 30 天的心率或 HRV 数据缺失\n'
            '    · 打开 iOS 健康 App → 浏览 → “心率” 或 “HRV” → “未找到数据”,确认是否缺失。\n'
            '    · 如果数据缺失,请重新佩戴手表,并重启 iPhone 和 Apple Watch 后,再次打开 DoubleFeel。',
            style: TextStyle(
              color: context.colors.textSecondary,
              fontSize: 12,
              fontWeight: FontWeight.w400,
              height: 17 / 12,
            ),
          ),
        ],
      ),
    );
  }
}

class _CardShell extends StatelessWidget {
  const _CardShell({required this.child});

  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.fromLTRB(20, 20, 20, 20),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(16),
      ),
      child: child,
    );
  }
}

class _IconPlaceholder extends StatelessWidget {
  const _IconPlaceholder();

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 56,
      height: 56,
      color: NoHealthDataPage._placeholderColor,
    );
  }
}