# 正念 APP（客户端）

本目录用于存放「正念 APP」客户端工程，定位是面向宝妈人群的情绪价值与正念练习支持应用。

## 功能概览（一期）

- **APP-PUSH 推送**：定时推送情绪文字（固定时间 / 用户自定义时间），推送内容可由后端配置更新
- **iOS 小组件（Widget）**：展示当前情绪文字与背景图/渐变色，支持多尺寸与可配置刷新频率
- **情绪卡片滑动**：卡片列表左右滑动切换（类似 Tinder），收藏/分享可作为后续迭代

## 技术栈

- **React Native + Expo**
- **TypeScript**
- **React Navigation**：页面路由
- **状态管理**：Zustand（或 Redux Toolkit）
- **网络请求**：Axios（或 Fetch）
- **推送**：Expo Notifications
- **环境区分**：Expo App Config + `.env.dev` / `.env.prod`
- **iOS 打包**：EAS Build

## 开发环境要求

- **Node.js**：建议使用 **20+（LTS）**
- **包管理器**：pnpm 
- **Expo CLI**：推荐通过 `npx expo` 使用（避免全局版本漂移）
- **iOS 真机调试**：macOS + Xcode（如需）


> 本项目已内置 `postinstall` 补丁脚本用于兼容（安装依赖后会自动修复），但仍建议升级到 Node 20+。

## 快速开始

> 说明：本仓库已包含 Expo 工程文件；如果你只是想把客户端跑起来，从「安装依赖」开始即可。

### 1）初始化工程（若尚未创建）

在 `client/` 目录下创建 Expo 工程（示例）：

```bash
cd client
npx create-expo-app@latest .
```

执行后按提示选择模板即可；如果你希望固定使用 TypeScript 模板，也可以在创建时选择对应模板（以实际 CLI 提示为准）。

### 2）安装依赖

```bash
cd client
pnpm install
```

### 3）配置环境变量（dev / prod）

按根目录约定，客户端使用 `.env.dev` 与 `.env.prod` 区分环境；`.env` 文件不提交到 Git，仓库内应提供 `.env.example` 作为模板。

#### 本地开发如何注入环境变量（推荐）

Expo 在本地开发时会自动读取当前目录下的 `.env` / `.env.local`（仅以 `EXPO_PUBLIC_` 前缀变量注入到客户端运行时）。推荐做法：

```bash
cd client
cp .env.example .env.local
# 然后按需修改 .env.local
pnpm start
```

> 修改 `.env*` 后需要重启 `pnpm start` 才会生效。

#### 本地开发如何区分 dev / prod（可选）

如果你希望坚持使用 `.env.dev` / `.env.prod` 文件名，可以在启动前导出环境变量（zsh 示例）：

```bash
cd client
set -a
source .env.dev
set +a
pnpm start
```

> 注意：`source` 方式要求 `.env.dev` 内容是合法的 shell 格式（例如 `KEY=value`），不要带 `export` 以外的复杂语法；值包含空格时需要加引号。

#### 必填环境变量

- **`EXPO_PUBLIC_API_BASE_URL`**：后端 API 基地址
  - 本地：`http://localhost:8000`
  - 真机联调：改为电脑局域网 IP（例如 `http://192.168.1.10:8000`）
- **`EXPO_PUBLIC_ENV`**：环境标识（建议 `dev` / `prod`）

#### 可选环境变量

- **`EXPO_PUBLIC_DEFAULT_LANGUAGE`**：默认语言策略
  - `auto`：优先设备语言（默认）
  - `zh-CN/en/es/pt/zh-TW`：固定默认语言（仍允许用户在设置中切换并持久化）

建议字段（示例）：

```env
EXPO_PUBLIC_API_BASE_URL=http://localhost:8000
EXPO_PUBLIC_ENV=dev
EXPO_PUBLIC_DEFAULT_LANGUAGE=auto
```

> 提示：在 Expo 中建议使用 `EXPO_PUBLIC_` 前缀，方便在运行时读取并支持 EAS 构建注入。

### 4）启动开发服务器

```bash
cd client
pnpm start
```

启动后你会看到终端输出的二维码（QR Code）与可用命令提示。

### 5）运行到 iOS / Android / 真机 / Web

- **iOS 模拟器（macOS + Xcode）**：
  - 方式一：在 `pnpm start` 的终端里按 `i`
  - 方式二：直接运行：

```bash
cd client
pnpm ios
```

- **Android 模拟器（Android Studio）**：
  - 方式一：在 `pnpm start` 的终端里按 `a`
  - 方式二：直接运行：

```bash
cd client
pnpm android
```

- **真机（推荐使用 Expo Go）**：
  - 手机安装 **Expo Go**
  - 确保手机与电脑在**同一局域网**
  - 运行 `pnpm start` 后，用 Expo Go 扫描终端二维码即可打开 App

- **Web（可选）**：

```bash
cd client
pnpm web
```

常用命令（脚本）：

```bash
pnpm ios
pnpm android
pnpm web
```

### 6）常见问题

- **真机打不开 / 扫码后卡住**：
  - 检查手机与电脑是否同一 Wi-Fi
  - 尝试重启 `pnpm start -- --clear` 清缓存
- **启动时提示 `Networking has been disabled` / `Network connection is unreliable`**：
  - 这是 Expo CLI 的网络探测/请求失败导致的，按下面方式跳过网络请求即可：

```bash
cd client
EXPO_OFFLINE=1 pnpm start
```

  - 如果你本机设置了代理（例如 `HTTP_PROXY/ALL_PROXY`），也可以临时关闭代理后再启动：

```bash
cd client
unset HTTP_PROXY HTTPS_PROXY ALL_PROXY
pnpm start
```

- **Watchman 提示 Recrawl（不影响运行，但建议处理）**：
  - 按提示执行一次即可清除警告：

```bash
watchman watch-del '/Users/jojo/Desktop/lxy/gitea/mindfulness/client' ; watchman watch-project '/Users/jojo/Desktop/lxy/gitea/mindfulness/client'
```
- **联调时请求打到 localhost**：
  - 真机上 `localhost` 指向手机自身，请把 `EXPO_PUBLIC_API_BASE_URL` 改成电脑的局域网 IP（例如 `http://192.168.1.10:8000`），或使用内网穿透方案

## 与后端联调

- **后端基座**：FastAPI（详见 `server/README.md`）
- **API Base URL**：通过 `EXPO_PUBLIC_API_BASE_URL` 配置
- **本地真机注意**：如果在手机上调试，请将 `localhost` 替换为电脑局域网 IP，或使用内网穿透方案

## 多语言（i18n）：CN / EN / ES / PT / TC

### 语言码约定

- **CN（简体中文）**：`zh-CN`
- **EN（英语）**：`en`
- **ES（西班牙语）**：`es`
- **PT（葡萄牙语）**：`pt`
- **TC（繁体中文）**：`zh-TW`

### 推荐选型（Expo/RN 常用组合）

- **文案管理**：`i18next` + `react-i18next`
- **系统语言读取**：`expo-localization`

### 建议目录结构

```text
src/
└── i18n/
    ├── index.ts               # i18n 初始化（默认语言、回退语言、资源注册）
    ├── locales/
    │   ├── zh-CN.json
    │   ├── en.json
    │   ├── es.json
    │   ├── pt.json
    │   └── zh-TW.json
    └── types.ts               #（可选）语言码与 key 的类型定义
```

### 约定与最佳实践

- **key 规则**：使用稳定的点分层 key，例如 `common.ok`、`push.permissionTitle`、`cards.swipeHint`
- **默认与回退**：默认优先跟随用户当前设备语言（在支持列表内时生效）；设备语言不支持时回退到 `zh-CN`（或团队指定默认）
- **插值与复数**：优先使用 i18next 的插值（例如 `{{name}}`），避免在代码里拼字符串
- **动态切换**：提供“语言设置”入口允许手动切换；切换后持久化（例如 AsyncStorage），后续启动优先生效，并立即刷新文案
- **语言优先级**：用户设置（若存在）> 设备语言（在支持列表内）> 默认回退（`zh-CN` 或团队指定默认）
- **设置入口建议**：设置页 -> 语言（或 设置页 -> 通用 -> 语言）
- **与服务端一致性**：若后端也需要多语言（推送文案等），建议统一语言码（`zh-CN/en/es/pt/zh-TW`）并在接口里明确 `lang`

## 推送（Expo Notifications）

客户端侧通常需要：

- **申请通知权限**
- **获取 Expo Push Token**
- **将 Token 上报后端**（用于定时推送任务）

后端侧通常需要：

- **保存用户 Push Token**
- **按策略触发推送**（固定时间 / 用户自定义时间）
- **支持后台配置推送文案并更新**

> 具体实现以客户端工程代码为准；当代码接入后，建议在 README 补充「Token 上报接口」与「字段定义」。

## iOS 小组件（Widget）

小组件通常需要：

- **数据源**：来自本地缓存或后端拉取（需要设计刷新策略与缓存）
- **展示内容**：当前情绪文字 + 背景图/渐变
- **尺寸**：小 / 中 / 大
- **刷新频率**：如每小时/每天（iOS 对频率有系统限制，以实际效果为准）

> 若你计划使用 Expo 的相关能力，请在工程中明确选型与实现路径，并补充到本 README。

## EAS Build（iOS 打包）

建议按根目录约定区分 dev/prod：

- **dev**：`com.damer.mindfulness.dev`
- **prod**：`com.damer.mindfulness`

常见流程（示例）：

```bash
cd client
eas login
eas build --platform ios --profile development
```

> 实际 profile、证书、bundle id、环境变量注入方式以工程内 `eas.json` / app config 为准。

## 目录结构（建议）

当客户端工程落地后，建议使用更标准、可扩展的目录结构（Expo + TypeScript 常见组织方式）：

```text
client/
├── app/                      #（推荐）expo-router 路由目录（若使用 expo-router）
│   ├── (tabs)/               # Tabs 分组（可选）
│   ├── _layout.tsx           # 根布局
│   └── index.tsx             # 首页
├── src/                      # 业务源码（与路由/平台代码解耦）
│   ├── components/           # 通用 UI 组件
│   ├── features/             # 按功能域拆分（推荐）
│   │   ├── push/             # 推送相关（权限、token、上报等）
│   │   ├── widget/           # 小组件数据与样式相关
│   │   └── cards/            # 情绪卡片滑动相关
│   ├── hooks/                # 自定义 hooks
│   ├── navigation/           #（若不用 expo-router）React Navigation 配置
│   ├── screens/              #（若不用 expo-router）页面
│   ├── services/             # API 封装（request、接口定义）
│   ├── store/                # 状态管理（Zustand/RTK）
│   ├── utils/                # 工具函数（时间、格式化、校验等）
│   ├── constants/            # 常量与配置（主题、枚举等）
│   └── types/                # 全局类型声明
├── assets/                   # 静态资源（图片/字体/音频等）
├── app.json / app.config.ts  # Expo 配置（环境区分可在此处理）
├── eas.json                  # EAS Build 配置（如使用）
├── package.json
└── README.md
```

> 说明：如果你不使用 `expo-router`，可以删除 `app/`，并以 `src/navigation/` + `src/screens/` 作为主路由结构；其余目录保持不变即可。

## iOS 小组件开发（V1：写死文案）

> 重要：**Expo Go 不支持 WidgetKit**。要做真正的小组件，必须预构建 iOS 工程并用 Xcode 跑。

### 1）生成 iOS 工程

```bash
cd client
npx expo prebuild -p ios --no-install
```

生成后会得到 `client/ios/`（Xcode 工程文件都在里面）。

### 2）在 Xcode 创建 Widget Extension

- 用 Xcode 打开：`client/ios/client.xcworkspace`
- 菜单：`File -> New -> Target... -> Widget Extension`
- Target 名称示例：`MindfulnessWidget`

### 3）写死文案与三尺寸布局

仓库里已提供 SwiftUI 代码骨架（写死文案 + Small/Medium/Large + 点击跳转）：

- `client/ios/情绪小组件/EmotionWidget.swift`

创建 Widget target 后，把该文件加入到 Widget target 中即可（Target Membership 勾选：`情绪小组件`）。

### 4）点击跳转到 Home（Deep Link）

Widget 点击跳转使用：

- `client:///(app)/home`

代码里已通过 `widgetURL` 设置（见 `MindfulnessWidget.swift`）。
