16 Commits

Author SHA1 Message Date
915e995ab7 Merge pull request 'fix:更新' (#11) from damer into main
Reviewed-on: #11
2026-02-02 14:26:16 +00:00
吕新雨
0e42e6f2a9 fix:更新 2026-02-02 22:25:48 +08:00
39e4bcab6c Merge pull request 'fix:更新server' (#10) from damer into main
Reviewed-on: #10
2026-02-02 14:22:49 +00:00
吕新雨
69a1046ff4 fix:更新server 2026-02-02 22:22:20 +08:00
ceaf459d97 Merge pull request 'damer' (#9) from damer into main
Reviewed-on: #9
2026-02-02 14:01:03 +00:00
吕新雨
d9a5dbafd6 fix:新增工作流 2026-02-02 21:59:15 +08:00
吕新雨
86e4853709 Merge branch 'main' into damer 2026-02-02 18:37:44 +08:00
吕新雨
2adf2475fa fix:git忽略文件 2026-02-02 18:37:00 +08:00
Hao
9dbba04408 Merge pull request 'Hao' (#8) from Hao into main
Reviewed-on: #8
2026-02-02 09:33:41 +00:00
1
64b8352ad3 fix:并 2026-02-02 17:33:20 +08:00
1
4b739dd194 feat: 完善启动分发逻辑,优化小组件背景适配与点赞图标交互 2026-02-02 17:17:10 +08:00
吕新雨
240cdda68f Merge branch 'main' into damer 2026-02-02 16:54:51 +08:00
吕新雨
ce48e54c03 增加git忽略 2026-02-02 16:53:26 +08:00
吕新雨
d045237952 chore: 清理 Python 运行产物并忽略
- 从仓库移除 __pycache__/ 与 *.pyc 等运行产物,避免 git pull 覆盖未跟踪文件导致合并失败
- 在 .gitignore 增加 Python 运行产物忽略规则
2026-02-02 16:52:22 +08:00
1
228fd7fd84 feat: 首页支持风景图与纯色背景随滑动自动切换,优化点赞图标样式与交互 2026-02-02 16:50:38 +08:00
3587a24115 Merge pull request '新功能:个性化推荐算法' (#7) from damer into main
Reviewed-on: #7
2026-02-02 08:48:11 +00:00
101 changed files with 604 additions and 204 deletions

View File

@@ -0,0 +1,5 @@
docker exec -it gitea-runner bash
# 然后在容器里安装 Node.js
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt-get install -y nodejs
node -v

View File

@@ -0,0 +1,101 @@
name: Build and Push Server Docker Image
# 手动触发 workflow从哪个分支运行就打包哪个分支的代码
on:
workflow_dispatch:
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
# 1⃣ Checkout 仓库代码
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
# 需要能 push tag请在仓库 Secrets 配置 RUNNER_TOKEN
token: ${{ secrets.RUNNER_TOKEN }}
persist-credentials: true
# 2⃣ 自动递增 tag 并推送回 Gitea 仓库(默认按 vX.Y.Z 的 patch +1
- name: Auto bump tag and push to repository
shell: bash
run: |
set -euo pipefail
# 配置提交信息(用于创建注释 tag
git config user.name "gitea-actions"
git config user.email "actions@local"
# 确保本地有最新 tags
git fetch --tags --force
# 取最新的 semver tagvX.Y.Z按版本号排序
LATEST_TAG="$(git tag --list 'v*' --sort=-v:refname | head -n 1 || true)"
echo "LATEST_TAG=${LATEST_TAG}"
if [[ -z "${LATEST_TAG}" ]]; then
NEXT_TAG="v1.0.0"
else
if [[ "${LATEST_TAG}" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
MAJOR="${BASH_REMATCH[1]}"
MINOR="${BASH_REMATCH[2]}"
PATCH="${BASH_REMATCH[3]}"
NEXT_TAG="v${MAJOR}.${MINOR}.$((PATCH + 1))"
else
# 如果最新 tag 不符合 vX.Y.Z回退到 v1.0.0,避免误解析
NEXT_TAG="v1.0.0"
fi
fi
echo "NEXT_TAG=${NEXT_TAG}"
# 如果 tag 已存在则直接复用(避免重复运行失败)
if git rev-parse -q --verify "refs/tags/${NEXT_TAG}" >/dev/null; then
echo "Tag ${NEXT_TAG} 已存在,跳过创建。"
else
git tag -a "${NEXT_TAG}" -m "Release ${NEXT_TAG}"
git push origin "${NEXT_TAG}"
fi
# 输出给后续步骤使用
if [[ -n "${GITHUB_ENV:-}" ]]; then
echo "IMAGE_TAG=${NEXT_TAG}" >> "$GITHUB_ENV"
fi
# 兼容部分 Gitea Runner 环境变量命名
if [[ -n "${GITEA_ENV:-}" ]]; then
echo "IMAGE_TAG=${NEXT_TAG}" >> "$GITEA_ENV"
fi
# 3⃣ 设置 Docker 镜像名称
- name: Set image variables
shell: bash
run: |
# 修改为你的 Docker Hub 仓库名例如yourname/mindfulness-server
IMAGE_NAME=docker.damer.fun/damer/mindfulness-server
if [[ -n "${GITHUB_ENV:-}" ]]; then
echo "IMAGE_NAME=$IMAGE_NAME" >> "$GITHUB_ENV"
fi
if [[ -n "${GITEA_ENV:-}" ]]; then
echo "IMAGE_NAME=$IMAGE_NAME" >> "$GITEA_ENV"
fi
# 4⃣ 登录 Docker Hub
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# 5⃣ 构建 Docker 镜像(使用 server/ 作为构建上下文)
- name: Build Docker Image
shell: bash
run: |
docker build -f server/Dockerfile -t "$IMAGE_NAME:$IMAGE_TAG" server
# 6⃣ 推送 Docker 镜像到 Docker Hub
- name: Push Docker Image
shell: bash
run: |
docker push "$IMAGE_NAME:$IMAGE_TAG"

4
.gitignore vendored
View File

@@ -4,6 +4,10 @@
.DS_Store .DS_Store
*.pem *.pem
# Python运行产物
__pycache__/
*.py[cod]
# Node / JS # Node / JS
node_modules/ node_modules/
npm-debug.* npm-debug.*

View File

@@ -1,11 +1,11 @@
{ {
"expo": { "expo": {
"name": "Hey Mama", "name": "client",
"slug": "hey-mama", "slug": "client",
"version": "1.0.0", "version": "1.0.0",
"orientation": "portrait", "orientation": "portrait",
"icon": "./assets/images/icon.png", "icon": "./assets/images/icon.png",
"scheme": "heymama", "scheme": "client",
"userInterfaceStyle": "automatic", "userInterfaceStyle": "automatic",
"newArchEnabled": true, "newArchEnabled": true,
"splash": { "splash": {
@@ -15,7 +15,7 @@
}, },
"ios": { "ios": {
"supportsTablet": true, "supportsTablet": true,
"bundleIdentifier": "com.heymama.app" "bundleIdentifier": "com.damer.mindfulness"
}, },
"android": { "android": {
"adaptiveIcon": { "adaptiveIcon": {

View File

@@ -1,5 +1,5 @@
import { useEffect, useLayoutEffect, useMemo, useState, useCallback, useRef } from 'react'; import { useEffect, useLayoutEffect, useMemo, useState, useCallback, useRef } from 'react';
import { StyleSheet, View, Dimensions, Text, Pressable, PanResponder, Animated as RNAnimated } from 'react-native'; import { StyleSheet, View, Dimensions, Text, Pressable, PanResponder, Animated as RNAnimated, ImageBackground } from 'react-native';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useNavigation, useFocusEffect } from 'expo-router'; import { useNavigation, useFocusEffect } from 'expo-router';
import Animated, { import Animated, {
@@ -39,6 +39,40 @@ import LikeIcon from '@/assets/images/icon/like_icon.svg';
const { height: SCREEN_HEIGHT } = Dimensions.get('window'); const { height: SCREEN_HEIGHT } = Dimensions.get('window');
// 预定义风景图列表
const NATURE_IMAGES = [
require('@/assets/theme/nature/1.png'),
require('@/assets/theme/nature/2.png'),
require('@/assets/theme/nature/3.png'),
require('@/assets/theme/nature/4.png'),
require('@/assets/theme/nature/5.png'),
require('@/assets/theme/nature/6.png'),
require('@/assets/theme/nature/7.png'),
require('@/assets/theme/nature/8.png'),
require('@/assets/theme/nature/9.png'),
require('@/assets/theme/nature/10.png'),
require('@/assets/theme/nature/11.png'),
require('@/assets/theme/nature/12.png'),
require('@/assets/theme/nature/13.png'),
require('@/assets/theme/nature/14.png'),
require('@/assets/theme/nature/15.png'),
require('@/assets/theme/nature/17.png'),
require('@/assets/theme/nature/18.png'),
require('@/assets/theme/nature/19.png'),
require('@/assets/theme/nature/20.png'),
require('@/assets/theme/nature/22.png'),
];
// 预定义颜色列表
const THEME_COLORS = [
'#F7D9BF',
'#CBF2D8',
'#F5CDDE',
'#F2ECCB',
'#E2CBF2',
'#CBD9F2',
];
export default function HomeScreen() { export default function HomeScreen() {
const { t } = useTranslation(); const { t } = useTranslation();
const navigation = useNavigation(); const navigation = useNavigation();
@@ -126,12 +160,26 @@ export default function HomeScreen() {
}; };
}, []); }, []);
const backgroundColor = themeMode === 'color' ? '#F3D0E1' : '#F4D6C2'; const backgroundColor = useMemo(() => {
if (themeMode === 'color') {
const colorIndex = Math.floor(index / 10) % THEME_COLORS.length;
return THEME_COLORS[colorIndex];
}
return '#F4D6C2'; // 风景模式下的默认底色(图片加载前显示)
}, [themeMode, index]);
// 计算当前应该显示的风景图索引(滑动 10 次切换一张)
const natureImageIndex = useMemo(() => {
return Math.floor(index / 10) % NATURE_IMAGES.length;
}, [index]);
const currentNatureImage = NATURE_IMAGES[natureImageIndex];
useLayoutEffect(() => { useLayoutEffect(() => {
navigation.setOptions({ navigation.setOptions({
headerShadowVisible: false, headerShadowVisible: false,
headerStyle: { backgroundColor }, headerStyle: { backgroundColor: themeMode === 'scenery' ? 'transparent' : backgroundColor },
headerTransparent: themeMode === 'scenery',
headerRight: () => ( headerRight: () => (
<View style={styles.headerRight}> <View style={styles.headerRight}>
<CircleIconButton <CircleIconButton
@@ -149,7 +197,7 @@ export default function HomeScreen() {
</View> </View>
), ),
}); });
}, [backgroundColor, navigation, t]); }, [backgroundColor, themeMode, navigation, t]);
const textAnimatedStyle = useAnimatedStyle(() => ({ const textAnimatedStyle = useAnimatedStyle(() => ({
transform: [{ translateY: translateY.value }], transform: [{ translateY: translateY.value }],
@@ -241,10 +289,11 @@ export default function HomeScreen() {
// 2. 保存到收藏夹,包含当前背景信息 // 2. 保存到收藏夹,包含当前背景信息
await addFavorite({ await addFavorite({
id: typeof currentContentId === 'number' ? String(currentContentId) : (item as any).id, favId: String(Date.now()), // 生成唯一 ID
id: item.id,
date: dateStr, date: dateStr,
themeMode: themeMode, themeMode: themeMode,
background: backgroundColor, // 目前存储的是颜色值 background: themeMode === 'scenery' ? String(natureImageIndex) : backgroundColor,
}); });
// 3. 爱心缩放动画 // 3. 爱心缩放动画
@@ -267,8 +316,15 @@ export default function HomeScreen() {
return ( return (
<View style={[styles.container, { backgroundColor }]} {...panResponder.panHandlers}> <View style={[styles.container, { backgroundColor }]} {...panResponder.panHandlers}>
<Animated.View style={[styles.card, textAnimatedStyle]}> {themeMode === 'scenery' && (
<Text style={styles.text}>{item.text}</Text> <ImageBackground
source={currentNatureImage}
style={StyleSheet.absoluteFill}
resizeMode="cover"
/>
)}
<Animated.View style={[styles.card, textAnimatedStyle, themeMode === 'scenery' && styles.sceneryCard]}>
<Text style={[styles.text, themeMode === 'scenery' && styles.sceneryText]}>{item.text}</Text>
</Animated.View> </Animated.View>
<View style={styles.actions}> <View style={styles.actions}>
@@ -281,9 +337,13 @@ export default function HomeScreen() {
style={styles.reactionInner} style={styles.reactionInner}
> >
{likeFilled ? ( {likeFilled ? (
<LikeFilledIcon width={35} height={36} /> <LikeFilledIcon width={35} height={36} style={{ color: '#EA6969' }} />
) : ( ) : (
<LikeIcon width={35} height={36} /> <LikeIcon
width={35}
height={36}
style={{ color: themeMode === 'scenery' ? '#FFFFFF' : '#5E2A28' }}
/>
)} )}
</Pressable> </Pressable>
</Animated.View> </Animated.View>
@@ -342,9 +402,15 @@ const styles = StyleSheet.create({
justifyContent: 'center', justifyContent: 'center',
}, },
card: { card: {
paddingHorizontal: 30, position: 'absolute',
alignItems: 'center', top: 0,
left: 0,
right: 0,
bottom: 0,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 30,
zIndex: 5, // 降低层级,防止遮挡底部按钮
}, },
text: { text: {
fontSize: 22, fontSize: 22,
@@ -353,6 +419,16 @@ const styles = StyleSheet.create({
fontWeight: '700', fontWeight: '700',
textAlign: 'center', textAlign: 'center',
}, },
sceneryCard: {
// 风景模式下稍微收窄文案宽度,增加呼吸感
paddingHorizontal: 50,
},
sceneryText: {
color: '#FFFFFF',
textShadowColor: 'rgba(0, 0, 0, 0.5)',
textShadowOffset: { width: 0, height: 1 },
textShadowRadius: 4,
},
actions: { actions: {
position: 'absolute', position: 'absolute',
bottom: SCREEN_HEIGHT * 0.16, bottom: SCREEN_HEIGHT * 0.16,
@@ -360,6 +436,7 @@ const styles = StyleSheet.create({
right: 0, right: 0,
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'center', justifyContent: 'center',
zIndex: 20, // 提升层级,确保在最顶层可点击
}, },
reactionButton: { reactionButton: {
alignItems: 'center', alignItems: 'center',

View File

@@ -140,14 +140,13 @@ export default function OnboardingScreen() {
} }
}; };
const onSkip = () => { const onSkip = async () => {
// 跳过整个 Onboarding仍生成一个“全跳过”的最小画像保证下游可用 // 跳过整个 Onboarding仍生成一个“全跳过”的最小画像保证下游可用
const scoringProfile = buildUserProfileFromQuestionnaire({}); const scoringProfile = buildUserProfileFromQuestionnaire({});
void setUserProfileScoring(scoringProfile); await setUserProfileScoring(scoringProfile);
// 标记已完成,避免下次启动再次进入 Onboarding // 标记已完成,避免下次启动再次进入 Onboarding
void setOnboardingCompleted(true); await setOnboardingCompleted(true);
router.replace('/(app)/home'); router.replace('/(app)/home');
}; };

View File

@@ -1,9 +1,8 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { ActivityIndicator, StyleSheet, View } from 'react-native'; import { ActivityIndicator, StyleSheet, View } from 'react-native';
import { useRouter } from 'expo-router'; import { useRouter } from 'expo-router';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { getOnboardingCompleted, getConsentAccepted, setOnboardingCompleted, setConsentAccepted } from '@/src/storage/appStorage'; import { getOnboardingCompleted, getConsentAccepted } from '@/src/storage/appStorage';
/** /**
* 启动分发:根据 consent 和 onboarding 状态跳转 * 启动分发:根据 consent 和 onboarding 状态跳转
@@ -14,9 +13,6 @@ export default function Index() {
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
(async () => { (async () => {
// 注意:不要在启动时无条件清空存储,否则 Onboarding/画像等数据无法持久化。
// 如需调试重置,请在开发期手动清空或自行加调试开关。
// 1. 检查是否同意协议 // 1. 检查是否同意协议
const consentAccepted = await getConsentAccepted(); const consentAccepted = await getConsentAccepted();
if (cancelled) return; if (cancelled) return;
@@ -26,9 +22,17 @@ export default function Index() {
return; return;
} }
// 2. 检查 Onboarding // 2. 检查 Onboarding 是否已完成
const completed = await getOnboardingCompleted(); const completed = await getOnboardingCompleted();
router.replace(completed ? '/(app)/home' : '/(onboarding)/onboarding'); if (cancelled) return;
if (completed) {
// 如果已经完成过流程,直接进 Home
router.replace('/(app)/home');
} else {
// 如果是首次进入(或未完成流程),进入 Onboarding
router.replace('/(onboarding)/onboarding');
}
})(); })();
return () => { return () => {
cancelled = true; cancelled = true;
@@ -45,4 +49,3 @@ export default function Index() {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1, alignItems: 'center', justifyContent: 'center' }, container: { flex: 1, alignItems: 'center', justifyContent: 'center' },
}); });

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

View File

@@ -50,6 +50,29 @@ type Props = {
type Page = 'root' | 'favorites' | 'dailyReminder' | 'widget' | 'language' | 'widgetHowTo'; type Page = 'root' | 'favorites' | 'dailyReminder' | 'widget' | 'language' | 'widgetHowTo';
type NavDirection = 'forward' | 'back'; type NavDirection = 'forward' | 'back';
const NATURE_IMAGES = [
require('@/assets/theme/nature/1.png'),
require('@/assets/theme/nature/2.png'),
require('@/assets/theme/nature/3.png'),
require('@/assets/theme/nature/4.png'),
require('@/assets/theme/nature/5.png'),
require('@/assets/theme/nature/6.png'),
require('@/assets/theme/nature/7.png'),
require('@/assets/theme/nature/8.png'),
require('@/assets/theme/nature/9.png'),
require('@/assets/theme/nature/10.png'),
require('@/assets/theme/nature/11.png'),
require('@/assets/theme/nature/12.png'),
require('@/assets/theme/nature/13.png'),
require('@/assets/theme/nature/14.png'),
require('@/assets/theme/nature/15.png'),
require('@/assets/theme/nature/17.png'),
require('@/assets/theme/nature/18.png'),
require('@/assets/theme/nature/19.png'),
require('@/assets/theme/nature/20.png'),
require('@/assets/theme/nature/22.png'),
];
export default function ProfileModal({ visible, name: propName, onClose }: Props) { export default function ProfileModal({ visible, name: propName, onClose }: Props) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -260,11 +283,11 @@ function FavoritesPage({ visible, page }: { visible: boolean; page: Page }) {
setFavorites(list); setFavorites(list);
} }
async function handleRemove(id: string) { async function handleRemove(favId: string) {
// 1. 调用存储层移除收藏 // 1. 调用存储层移除收藏
await removeFavorite(id); await removeFavorite(favId);
// 2. 更新本地状态 // 2. 更新本地状态
setFavorites(prev => prev.filter(item => item.id !== id)); setFavorites(prev => prev.filter(item => item.favId !== favId));
} }
return ( return (
@@ -274,7 +297,7 @@ function FavoritesPage({ visible, page }: { visible: boolean; page: Page }) {
) : ( ) : (
<FlatList <FlatList
data={favorites} data={favorites}
keyExtractor={(it) => it.id} keyExtractor={(it) => it.favId}
contentContainerStyle={styles.favList} contentContainerStyle={styles.favList}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
renderItem={({ item }) => ( renderItem={({ item }) => (
@@ -290,11 +313,30 @@ function FavoritesPage({ visible, page }: { visible: boolean; page: Page }) {
<View style={styles.favRight}> <View style={styles.favRight}>
<View style={[ <View style={[
styles.favThumb, styles.favThumb,
{ backgroundColor: item.background } // 动态同步 Home 页的背景 item.themeMode === 'scenery' ? {} : { backgroundColor: item.background }
]}> ]}>
<Text style={styles.favThumbText} numberOfLines={4}>{item.text}</Text> {item.themeMode === 'scenery' ? (
<View style={StyleSheet.absoluteFill}>
<Image
source={NATURE_IMAGES[parseInt(item.background)]}
style={{
width: width * 0.6,
height: 800, // 假设原图较高,设置一个较大的高度
position: 'absolute',
bottom: 0, // 关键:将图片底部对齐容器底部
}}
resizeMode="cover"
/>
</View>
) : null}
<Text style={[
styles.favThumbText,
item.themeMode === 'scenery' && { color: '#FFFFFF', textShadowColor: 'rgba(0,0,0,0.5)', textShadowOffset: {width:0, height:1}, textShadowRadius: 3 }
]} numberOfLines={4}>
{item.text}
</Text>
<Pressable <Pressable
onPress={() => handleRemove(item.id)} onPress={() => handleRemove(item.favId)}
style={styles.favRemoveBtn} style={styles.favRemoveBtn}
hitSlop={10} hitSlop={10}
> >
@@ -765,6 +807,7 @@ const styles = StyleSheet.create({
position: 'relative', position: 'relative',
borderWidth: 1, borderWidth: 1,
borderColor: 'rgba(119, 47, 0, 0.05)', borderColor: 'rgba(119, 47, 0, 0.05)',
overflow: 'hidden',
}, },
favThumbText: { favThumbText: {
fontSize: 15, fontSize: 15,

View File

@@ -59,5 +59,13 @@ target 'client' do
:mac_catalyst_enabled => false, :mac_catalyst_enabled => false,
:ccache_enabled => ccache_enabled?(podfile_properties), :ccache_enabled => ccache_enabled?(podfile_properties),
) )
# 生成并随归档产物携带 dSYM用于崩溃符号化与 Upload Symbols Failed 修复)
installer.pods_project.targets.each do |target|
target.build_configurations.each do |build_config|
build_config.build_settings['DEBUG_INFORMATION_FORMAT'] = 'dwarf-with-dsym'
build_config.build_settings['DWARF_DSYM_FILE_SHOULD_ACCOMPANY_PRODUCT'] = 'YES'
end
end
end end
end end

View File

@@ -2397,6 +2397,6 @@ SPEC CHECKSUMS:
RNWorklets: 9ccdc8112b17af6eee2c85a233891cb80db150ad RNWorklets: 9ccdc8112b17af6eee2c85a233891cb80db150ad
Yoga: 5934998fbeaef7845dbf698f698518695ab4cd1a Yoga: 5934998fbeaef7845dbf698f698518695ab4cd1a
PODFILE CHECKSUM: dfe3cc75dee014a0abd367bc9e1bdbab0ba64ee3 PODFILE CHECKSUM: 4d5c52f9fa870c1d398cf59e37c149f66700c061
COCOAPODS: 1.16.2 COCOAPODS: 1.16.2

View File

@@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 56; objectVersion = 77;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@@ -17,6 +17,8 @@
EB3DAF812F2A4B8E00450593 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB3DAF802F2A4B8D00450593 /* WidgetKit.framework */; }; EB3DAF812F2A4B8E00450593 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB3DAF802F2A4B8D00450593 /* WidgetKit.framework */; };
EB3DAF832F2A4B8E00450593 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB3DAF822F2A4B8E00450593 /* SwiftUI.framework */; }; EB3DAF832F2A4B8E00450593 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB3DAF822F2A4B8E00450593 /* SwiftUI.framework */; };
EB3DAF942F2A4B8F00450593 /* 情绪小组件Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = EB3DAF7F2F2A4B8D00450593 /* 情绪小组件Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; EB3DAF942F2A4B8F00450593 /* 情绪小组件Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = EB3DAF7F2F2A4B8D00450593 /* 情绪小组件Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
EB630B302F30BE2700DC761A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EB630B2F2F30BE2700DC761A /* Assets.xcassets */; };
EB630B312F30BE2700DC761A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EB630B2F2F30BE2700DC761A /* Assets.xcassets */; };
F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748412D0307B40044C1D9 /* AppDelegate.swift */; }; F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748412D0307B40044C1D9 /* AppDelegate.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@@ -59,6 +61,7 @@
EB3DAF802F2A4B8D00450593 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; EB3DAF802F2A4B8D00450593 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
EB3DAF822F2A4B8E00450593 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; EB3DAF822F2A4B8E00450593 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
EB3DAF9A2F2A4D0900450593 /* MindfulnessWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MindfulnessWidget.swift; sourceTree = "<group>"; }; EB3DAF9A2F2A4D0900450593 /* MindfulnessWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MindfulnessWidget.swift; sourceTree = "<group>"; };
EB630B2F2F30BE2700DC761A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
F11748412D0307B40044C1D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = client/AppDelegate.swift; sourceTree = "<group>"; }; F11748412D0307B40044C1D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = client/AppDelegate.swift; sourceTree = "<group>"; };
F11748442D0722820044C1D9 /* client-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "client-Bridging-Header.h"; path = "client/client-Bridging-Header.h"; sourceTree = "<group>"; }; F11748442D0722820044C1D9 /* client-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "client-Bridging-Header.h"; path = "client/client-Bridging-Header.h"; sourceTree = "<group>"; };
@@ -156,6 +159,7 @@
83CBB9F61A601CBA00E9B192 = { 83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
EB630B2F2F30BE2700DC761A /* Assets.xcassets */,
13B07FAE1A68108700A75B9A /* client */, 13B07FAE1A68108700A75B9A /* client */,
832341AE1AAA6A7D00B99B32 /* Libraries */, 832341AE1AAA6A7D00B99B32 /* Libraries */,
EB3DAF842F2A4B8E00450593 /* 情绪小组件 */, EB3DAF842F2A4B8E00450593 /* 情绪小组件 */,
@@ -305,6 +309,7 @@
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */, 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */,
0BE245B56A79D95AB0A7B4BA /* PrivacyInfo.xcprivacy in Resources */, 0BE245B56A79D95AB0A7B4BA /* PrivacyInfo.xcprivacy in Resources */,
EB630B312F30BE2700DC761A /* Assets.xcassets in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -312,6 +317,7 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
EB630B302F30BE2700DC761A /* Assets.xcassets in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -475,9 +481,11 @@
baseConfigurationReference = FFF632A94C7A551AAA096858 /* Pods-client.debug.xcconfig */; baseConfigurationReference = FFF632A94C7A551AAA096858 /* Pods-client.debug.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = client/client.entitlements; CODE_SIGN_ENTITLEMENTS = client/client.entitlements;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_TEAM = WS92GPX9H2;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = ( GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)", "$(inherited)",
@@ -489,14 +497,14 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0.0;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
"-lc++", "-lc++",
); );
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = com.anonymous.client; PRODUCT_BUNDLE_IDENTIFIER = com.damer.mindfulness;
PRODUCT_NAME = client; PRODUCT_NAME = client;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
@@ -505,7 +513,7 @@
SWIFT_OBJC_BRIDGING_HEADER = "client/client-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "client/client-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = 1;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
}; };
name = Debug; name = Debug;
@@ -515,23 +523,27 @@
baseConfigurationReference = 3C76CA16D0801CBF0D731C7C /* Pods-client.release.xcconfig */; baseConfigurationReference = 3C76CA16D0801CBF0D731C7C /* Pods-client.release.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = client/client.entitlements; CODE_SIGN_ENTITLEMENTS = client/client.entitlements;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 2;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = WS92GPX9H2;
DWARF_DSYM_FILE_SHOULD_ACCOMPANY_PRODUCT = YES;
INFOPLIST_FILE = client/Info.plist; INFOPLIST_FILE = client/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.1; IPHONEOS_DEPLOYMENT_TARGET = 15.1;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0.0;
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-ObjC", "-ObjC",
"-lc++", "-lc++",
); );
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.anonymous.client; PRODUCT_BUNDLE_IDENTIFIER = com.damer.mindfulness;
PRODUCT_NAME = client; PRODUCT_NAME = client;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
@@ -539,7 +551,7 @@
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_OBJC_BRIDGING_HEADER = "client/client-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "client/client-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = 1;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
}; };
name = Release; name = Release;
@@ -676,8 +688,9 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 2;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = WS92GPX9H2;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -695,7 +708,7 @@
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = com.anonymous.client.emotionwidget; PRODUCT_BUNDLE_IDENTIFIER = com.damer.mindfulness.emotionwidget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -709,7 +722,7 @@
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = 1;
}; };
name = Debug; name = Debug;
}; };
@@ -728,8 +741,9 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 2;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = WS92GPX9H2;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -746,7 +760,7 @@
MARKETING_VERSION = 1.0.0; MARKETING_VERSION = 1.0.0;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.anonymous.client.emotionwidget; PRODUCT_BUNDLE_IDENTIFIER = com.damer.mindfulness.emotionwidget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -759,7 +773,7 @@
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = 1;
}; };
name = Release; name = Release;
}; };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 142 KiB

View File

@@ -1,81 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>client</string> <string>client</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string> <string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0.0</string> <string>$(MARKETING_VERSION)</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>
<array> <array>
<dict> <dict>
<key>CFBundleURLSchemes</key> <key>CFBundleURLSchemes</key>
<array> <array>
<string>client</string> <string>client</string>
<string>com.anonymous.client</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array> </array>
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>12.0</string> <string>12.0</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NSAppTransportSecurity</key> <key>NSAppTransportSecurity</key>
<dict> <dict>
<key>NSAllowsArbitraryLoads</key> <key>NSAllowsArbitraryLoads</key>
<false/> <false/>
<key>NSAllowsLocalNetworking</key> <key>NSAllowsLocalNetworking</key>
<true/> <true/>
</dict> </dict>
<key>NSUserActivityTypes</key> <key>NSUserActivityTypes</key>
<array> <array>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route</string>
</array> </array>
<key>RCTNewArchEnabled</key> <key>RCTNewArchEnabled</key>
<true/> <true/>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>SplashScreen</string> <string></string>
<key>UIRequiredDeviceCapabilities</key> <key>UIRequiredDeviceCapabilities</key>
<array> <array>
<string>arm64</string> <string>arm64</string>
</array> </array>
<key>UIRequiresFullScreen</key> <key>UIRequiresFullScreen</key>
<false/> <false/>
<key>UIStatusBarStyle</key> <key>UIStatusBarStyle</key>
<string>UIStatusBarStyleDefault</string> <string>UIStatusBarStyleDefault</string>
<key>UISupportedInterfaceOrientations</key> <key>UISupportedInterfaceOrientations</key>
<array> <array>
<string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string> <string>UIInterfaceOrientationPortraitUpsideDown</string>
</array> </array>
<key>UISupportedInterfaceOrientations~ipad</key> <key>UISupportedInterfaceOrientations~ipad</key>
<array> <array>
<string>UIInterfaceOrientationPortrait</string> <string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string> <string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>UIUserInterfaceStyle</key> <key>UIUserInterfaceStyle</key>
<string>Automatic</string> <string>Automatic</string>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<false/> <false/>
</dict> </dict>
</plist> </plist>

View File

@@ -3,6 +3,6 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>aps-environment</key> <key>aps-environment</key>
<string>development</string> <string>production</string>
</dict> </dict>
</plist> </plist>

View File

@@ -1,54 +1,28 @@
import WidgetKit import WidgetKit
import SwiftUI import SwiftUI
// V2 + Small/Medium/Large + Home // V1Small/Medium/Large + Home
struct EmotionProvider: TimelineProvider { struct EmotionProvider: TimelineProvider {
private let quotes = [
"你已经很努力了,今天也值得被温柔对待。",
"轻轻呼吸,感受当下的每一刻。",
"所有的压力,都会在深呼吸中慢慢消散。",
"给生活一点留白,给自己一点温柔。",
"不要走得太快,等一等落下的灵魂。",
"世界虽嘈杂,但你可以拥有一颗宁静的心。",
"每一个瞬间,都是生命最好的安排。",
"抱抱自己,辛苦了,亲爱的。",
"慢一点也没关系,只要你在前行。",
"今天,你对自己微笑了吗?",
"愿你历经山河,仍觉得人间值得。",
"心简单,世界就简单;心平顺,生活就平顺。",
"即使生活偶尔晦暗,你也要成为自己的光。",
"别让琐事挤走生活的快乐,别让压力消磨奋斗的激情。"
]
func placeholder(in context: Context) -> EmotionEntry { func placeholder(in context: Context) -> EmotionEntry {
EmotionEntry(date: Date(), text: quotes[0]) EmotionEntry(date: Date())
} }
func getSnapshot(in context: Context, completion: @escaping (EmotionEntry) -> ()) { func getSnapshot(in context: Context, completion: @escaping (EmotionEntry) -> ()) {
let entry = EmotionEntry(date: Date(), text: quotes.randomElement() ?? quotes[0]) completion(EmotionEntry(date: Date()))
completion(entry)
} }
func getTimeline(in context: Context, completion: @escaping (Timeline<EmotionEntry>) -> ()) { func getTimeline(in context: Context, completion: @escaping (Timeline<EmotionEntry>) -> ()) {
var entries: [EmotionEntry] = [] // V1
let currentDate = Date() let entry = EmotionEntry(date: Date())
let nextUpdate = Calendar.current.date(byAdding: .day, value: 7, to: Date())
// 24 6 4 ?? Date().addingTimeInterval(60 * 60 * 24 * 7)
for hourOffset in 0..<6 { completion(Timeline(entries: [entry], policy: .after(nextUpdate)))
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset * 4, to: currentDate)!
let entry = EmotionEntry(date: entryDate, text: quotes.randomElement() ?? quotes[0])
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
} }
} }
struct EmotionEntry: TimelineEntry { struct EmotionEntry: TimelineEntry {
let date: Date let date: Date
let text: String
} }
struct EmotionWidgetView: View { struct EmotionWidgetView: View {
@@ -56,37 +30,160 @@ struct EmotionWidgetView: View {
@Environment(\.widgetFamily) var family @Environment(\.widgetFamily) var family
private let title = "正念" private let title = "正念"
private let text = "你已经很努力了,今天也值得被温柔对待。"
private let deepLink = URL(string: "client:///(app)/home") private let deepLink = URL(string: "client:///(app)/home")
// #F7D9BF
private let backgroundColor = Color(red: 247/255, green: 217/255, blue: 191/255)
//
private let textColor = Color(red: 74/255, green: 52/255, blue: 40/255)
var body: some View { var body: some View {
VStack(alignment: .center, spacing: 0) { switch family {
Spacer(minLength: 0) case .systemSmall:
smallView()
Text(entry.text) case .systemMedium:
.font(.system(size: family == .systemSmall ? 17 : 20, weight: .medium)) mediumView()
.foregroundColor(textColor) case .systemLarge:
.lineSpacing(6) largeView()
.multilineTextAlignment(.center) default:
.minimumScaleFactor(0.7) smallView()
.fixedSize(horizontal: false, vertical: true) }
}
Spacer(minLength: 0)
// iOS 15
if family != .systemSmall { private func cardBackground(colors: [Color]) -> some View {
Text("Hey Mama") ZStack {
.font(.system(size: 10, weight: .semibold)) LinearGradient(
.foregroundColor(textColor.opacity(0.3)) colors: colors,
.padding(.bottom, 4) startPoint: .topLeading,
} endPoint: .bottomTrailing
)
//
RadialGradient(
gradient: Gradient(colors: [Color.white.opacity(0.16), Color.white.opacity(0.0)]),
center: .topTrailing,
startRadius: 10,
endRadius: 180
)
}
.overlay(
RoundedRectangle(cornerRadius: 18, style: .continuous)
.stroke(Color.white.opacity(0.14), lineWidth: 1)
)
.cornerRadius(18)
}
private func chip(_ text: String) -> some View {
Text(text)
.font(.system(size: 12, weight: .semibold))
.foregroundColor(Color.white.opacity(0.9))
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(Color.white.opacity(0.14))
.cornerRadius(999)
}
private func smallView() -> some View {
ZStack {
cardBackground(colors: [
Color(red: 0.06, green: 0.08, blue: 0.12),
Color(red: 0.13, green: 0.16, blue: 0.22),
])
VStack(alignment: .leading, spacing: 10) {
HStack {
chip(title)
Spacer(minLength: 0)
}
Text(text)
.font(.system(size: 15, weight: .semibold))
.foregroundColor(Color.white.opacity(0.92))
.lineSpacing(2)
.lineLimit(4)
Spacer(minLength: 0)
Text("点我回到 App")
.font(.system(size: 11, weight: .medium))
.foregroundColor(Color.white.opacity(0.65))
}
.padding(14)
}
.widgetURL(deepLink)
}
private func mediumView() -> some View {
ZStack {
cardBackground(colors: [
Color(red: 0.06, green: 0.08, blue: 0.12),
Color(red: 0.09, green: 0.11, blue: 0.17),
])
HStack(alignment: .top, spacing: 14) {
VStack(alignment: .leading, spacing: 10) {
chip(title)
Text(text)
.font(.system(size: 17, weight: .semibold))
.foregroundColor(Color.white.opacity(0.92))
.lineSpacing(3)
.lineLimit(5)
Spacer(minLength: 0)
Text("轻轻呼吸,回到当下")
.font(.system(size: 12, weight: .medium))
.foregroundColor(Color.white.opacity(0.7))
}
//
VStack(alignment: .trailing, spacing: 8) {
Text(entry.date, style: .time)
.font(.system(size: 12, weight: .semibold))
.foregroundColor(Color.white.opacity(0.8))
Spacer(minLength: 0)
Text("今日")
.font(.system(size: 28, weight: .bold))
.foregroundColor(Color.white.opacity(0.12))
}
}
.padding(16)
}
.widgetURL(deepLink)
}
private func largeView() -> some View {
ZStack {
cardBackground(colors: [
Color(red: 0.06, green: 0.08, blue: 0.12),
Color(red: 0.14, green: 0.18, blue: 0.28),
])
VStack(alignment: .leading, spacing: 14) {
HStack {
chip(title)
Spacer(minLength: 0)
Text(entry.date, style: .time)
.font(.system(size: 12, weight: .semibold))
.foregroundColor(Color.white.opacity(0.78))
}
Text(text)
.font(.system(size: 20, weight: .semibold))
.foregroundColor(Color.white.opacity(0.92))
.lineSpacing(4)
.lineLimit(8)
Spacer(minLength: 0)
HStack {
Text("点我回到 Home")
.font(.system(size: 12, weight: .medium))
.foregroundColor(Color.white.opacity(0.7))
Spacer(minLength: 0)
Text("🌿")
.font(.system(size: 18))
.opacity(0.9)
}
}
.padding(18)
} }
.padding(family == .systemSmall ? 16 : 24)
.frame(maxWidth: .infinity, maxHeight: .infinity) //
.background(backgroundColor) //
.widgetURL(deepLink) .widgetURL(deepLink)
} }
} }
@@ -97,13 +194,7 @@ struct EmotionWidget: Widget {
var body: some WidgetConfiguration { var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: EmotionProvider()) { entry in StaticConfiguration(kind: kind, provider: EmotionProvider()) { entry in
if #available(iOS 17.0, *) { EmotionWidgetView(entry: entry)
EmotionWidgetView(entry: entry)
.containerBackground(Color(red: 247/255, green: 217/255, blue: 191/255), for: .widget)
} else {
EmotionWidgetView(entry: entry)
.background(Color(red: 247/255, green: 217/255, blue: 191/255))
}
} }
.configurationDisplayName("情绪小组件") .configurationDisplayName("情绪小组件")
.description("一段温柔提醒,陪你回到当下。") .description("一段温柔提醒,陪你回到当下。")

View File

@@ -29,7 +29,7 @@ function getApiBaseUrl(env: AppRuntimeEnv): string {
const direct = process.env.EXPO_PUBLIC_API_BASE_URL; const direct = process.env.EXPO_PUBLIC_API_BASE_URL;
if (direct && String(direct).trim()) return String(direct).trim(); if (direct && String(direct).trim()) return String(direct).trim();
// 约定local/dev/prod 三套域名分别配置,便于后续直接切环境而不改代码 // 约定local/dev/prod 三套域名分别配置, 便于后续直接切环境而不改代码
if (env === 'local') { if (env === 'local') {
return getOptionalEnv('EXPO_PUBLIC_API_BASE_URL_LOCAL', 'http://localhost:8000'); return getOptionalEnv('EXPO_PUBLIC_API_BASE_URL_LOCAL', 'http://localhost:8000');
} }

View File

@@ -105,6 +105,7 @@ export async function setReaction(contentId: string, reaction: Reaction): Promis
} }
export type FavoriteItem = { export type FavoriteItem = {
favId: string; // 唯一标识,支持重复点赞同一文案
id: string; id: string;
date: string; date: string;
themeMode: ThemeMode; themeMode: ThemeMode;
@@ -117,15 +118,15 @@ export async function getFavorites(): Promise<FavoriteItem[]> {
export async function addFavorite(item: FavoriteItem): Promise<void> { export async function addFavorite(item: FavoriteItem): Promise<void> {
const list = await getFavorites(); const list = await getFavorites();
if (list.some(i => i.id === item.id)) return; // 允许重复点赞,不再根据 id 去重
const newList = [item, ...list]; const newList = [item, ...list];
console.log('Adding to favorites, new list size:', newList.length); console.log('Adding to favorites, new list size:', newList.length);
await setJson(KEY_FAVORITES_ITEMS, newList); await setJson(KEY_FAVORITES_ITEMS, newList);
} }
export async function removeFavorite(contentId: string): Promise<void> { export async function removeFavorite(favId: string): Promise<void> {
const list = await getFavorites(); const list = await getFavorites();
const next = list.filter(item => item.id !== contentId); const next = list.filter(item => item.favId !== favId);
await setJson(KEY_FAVORITES_ITEMS, next); await setJson(KEY_FAVORITES_ITEMS, next);
} }

20
server/.dockerignore Normal file
View File

@@ -0,0 +1,20 @@
.venv
__pycache__/
*.pyc
*.pyo
*.pyd
.pytest_cache/
.mypy_cache/
.ruff_cache/
# 本地/测试数据
.test.db
*.db
# 测试与开发脚本(按需移除)
tests/
.env*
# Git 元数据
.git/
.gitignore

27
server/Dockerfile Normal file
View File

@@ -0,0 +1,27 @@
FROM python:3.11-slim
# 运行时基础环境
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
# 系统依赖(按需扩展;多数依赖为纯 Python
RUN apt-get update \
&& apt-get install -y --no-install-recommends build-essential curl \
&& rm -rf /var/lib/apt/lists/*
# 先复制依赖清单以利用 Docker layer cache
COPY requirements.txt /app/requirements.txt
RUN python -m pip install -U pip \
&& pip install --no-cache-dir -r /app/requirements.txt
# 复制后端代码与迁移配置
COPY app /app/app
COPY alembic /app/alembic
COPY alembic.ini /app/alembic.ini
EXPOSE 8000
# 生产镜像默认不开启 reload
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

View File

@@ -46,5 +46,5 @@
## 6. 风险与注意事项 ## 6. 风险与注意事项
- **Expo Go 不支持**:必须用 Xcode 运行预构建后的 App或后续 EAS Build - **Expo Go 不支持**:必须用 Xcode 运行预构建后的 App或后续 EAS Build
- **Bundle Identifier**:当前 `client/app.json` 使用 `com.anonymous.client`,仅适合本地验证;上线前需替换为真实 bundle id并同步证书/签名 - **Bundle Identifier**:当前工程已统一为主 App `com.damer.mindfulness`Widget Extension `com.damer.mindfulness.emotionwidget`;上线前仍需在 Apple Developer / App Store Connect 侧确保 App ID、证书签名匹配

View File

@@ -65,7 +65,7 @@ npx expo prebuild -p ios
- **排查** - **排查**
- 桌面搜索不到:通常是没有 Run 安装过主 App或 Widget target 未加入编译 - 桌面搜索不到:通常是没有 Run 安装过主 App或 Widget target 未加入编译
- 点击不跳转:确认 Widget 里 `widgetURL``client:///(app)/home`,且 `app.json``scheme``client` - 点击不跳转:确认 Widget 里 `widgetURL``client:///(app)/home`,且 `app.json``scheme``client`
- 仍然搜不到:检查 Widget Extension 的 Bundle Identifier 是否有效(本仓库已修正为 `com.anonymous.client.emotionwidget`),然后 Clean + 重新安装 App - 仍然搜不到:检查 Widget Extension 的 Bundle Identifier 是否有效(本仓库已修正为 `com.damer.mindfulness.emotionwidget`),然后 Clean + 重新安装 App
## 6. 文档补充(可选但建议) ## 6. 文档补充(可选但建议)

Some files were not shown because too many files have changed in this diff Show More