diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..33eca0b
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,3 @@
+node_modules
+.vscode
+dist
\ No newline at end of file
diff --git a/.gitea/workflows/dev.yaml b/.gitea/workflows/dev.yaml
new file mode 100644
index 0000000..1675ae5
--- /dev/null
+++ b/.gitea/workflows/dev.yaml
@@ -0,0 +1,37 @@
+
+name: Dev Deploy
+run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
+on:
+ push:
+ branches:
+ - main
+ - v0.4.0_front
+
+jobs:
+ Explore-Gitea-Actions:
+ runs-on: self_hosted
+ steps:
+ - run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
+ - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
+ - run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
+ - name: Check out repository code
+ uses: https://git.fairclip.cn/mirrors/actions-checkout@v4
+ - run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
+ - run: echo "🖥️ The workflow is now ready to test your code on the runner."
+ - name: List files in the repository
+ run: |
+ ls ${{ gitea.workspace }}
+ - name: Build Docker Image
+ run: |
+ echo "Building the Docker image..."
+ branch=${{ gitea.ref_name }}
+ tag_name=$(git rev-parse HEAD)
+ docker build -t docker.fairclip.cn/memowake/memowake-front:${branch}-${tag_name} .
+ echo "Docker image built successfully!"
+ - name: Deploy
+ run: |
+ echo "Deploying the project..."
+ branch=${{ gitea.ref_name }}
+ tag_name=$(git rev-parse HEAD)
+ bash ./scripts/dev_deploy.sh ${branch} docker.fairclip.cn/memowake/memowake-front:${branch}-${tag_name}
+ echo "Deploy successful!"
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..04e3d59
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,16 @@
+# 第一阶段:构建 Expo Web 静态文件
+FROM docker.fairclip.cn/node:22 AS builder
+WORKDIR /app
+COPY package.json .
+# 设置npm源
+RUN npm config set registry http://192.168.31.115:8081/repository/npm/
+RUN npm install -g expo-cli && npm install
+COPY . .
+RUN npx expo export -p web
+
+# 第二阶段:使用 nginx 作为 Web 服务器
+FROM docker.fairclip.cn/nginx:1.29-alpine
+COPY --from=builder /app/dist /usr/share/nginx/html
+COPY nginx.conf /etc/nginx/nginx.conf
+EXPOSE 80
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/app.json b/app.json
index 1945c17..23f728d 100644
--- a/app.json
+++ b/app.json
@@ -9,15 +9,31 @@
"userInterfaceStyle": "automatic",
"newArchEnabled": true,
"ios": {
- "supportsTablet": true
+ "supportsTablet": true,
+ "infoPlist": {
+ "NSPhotoLibraryUsageDescription": "Allow $(PRODUCT_NAME) to access your photos.",
+ "NSLocationWhenInUseUsageDescription": "Allow $(PRODUCT_NAME) to access your location to get photo location data.",
+ "ITSAppUsesNonExemptEncryption": false
+ },
+ "bundleIdentifier": "com.memowake.app"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "",
"backgroundColor": "#ffffff"
},
+ "permissions": [
+ "READ_EXTERNAL_STORAGE",
+ "WRITE_EXTERNAL_STORAGE",
+ "ACCESS_MEDIA_LOCATION",
+ "android.permission.RECORD_AUDIO",
+ "android.permission.MODIFY_AUDIO_SETTINGS",
+ "android.permission.READ_EXTERNAL_STORAGE",
+ "android.permission.WRITE_EXTERNAL_STORAGE",
+ "android.permission.ACCESS_MEDIA_LOCATION"
+ ],
"edgeToEdgeEnabled": true,
- "package": "com.jinyaqiu.memowake"
+ "package": "com.memowake.app"
},
"web": {
"bundler": "metro",
@@ -28,20 +44,17 @@
"expo-router",
"expo-secure-store",
[
- "expo-font",
+ "expo-audio",
{
- "fonts": [
- "./assets/fonts/[font-file.ttf]"
- ]
+ "microphonePermission": "Allow $(PRODUCT_NAME) to access your microphone."
}
],
[
- "expo-splash-screen",
+ "expo-media-library",
{
- "image": "",
- "imageWidth": 200,
- "resizeMode": "contain",
- "backgroundColor": "#ffffff"
+ "photosPermission": "Allow $(PRODUCT_NAME) to access your photos.",
+ "savePhotosPermission": "Allow $(PRODUCT_NAME) to save photos.",
+ "isAccessMediaLocationEnabled": true
}
]
],
@@ -51,8 +64,8 @@
"extra": {
"router": {},
"eas": {
- "projectId": "e4634b8b-fdb8-4e6f-ac7e-7032e6898612"
+ "projectId": "04721dd4-6b15-495a-b9ec-98187c613172"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
index ecf1e1a..c3def1f 100644
--- a/app/(tabs)/_layout.tsx
+++ b/app/(tabs)/_layout.tsx
@@ -24,7 +24,8 @@ export default function TabLayout() {
},
default: {},
}),
- }}>
+ }}
+ >
{/* 落地页 */}
+ {/* ask页面 */}
+ null, // 隐藏底部标签栏
+ headerShown: false, // 隐藏导航栏
+ tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
+ }}
+ />
+ {/* memo list */}
+ null, // 隐藏底部标签栏
+ headerShown: false, // 隐藏导航栏
+ tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
+ }}
+ />
);
}
diff --git a/app/(tabs)/ask.tsx b/app/(tabs)/ask.tsx
new file mode 100644
index 0000000..aaf4aec
--- /dev/null
+++ b/app/(tabs)/ask.tsx
@@ -0,0 +1,211 @@
+import ReturnArrow from "@/assets/icons/svg/returnArrow.svg";
+import Chat from "@/components/ask/chat";
+import AskHello from "@/components/ask/hello";
+import AudioRecordPlay from "@/components/ask/voice";
+import { ThemedText } from "@/components/ThemedText";
+import { fetchApi } from "@/lib/server-api-util";
+import { Message } from "@/types/ask";
+import { router, useLocalSearchParams } from "expo-router";
+import { useCallback, useEffect, useRef, useState } from 'react';
+import { KeyboardAvoidingView, Platform, ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native';
+import { useSafeAreaInsets } from "react-native-safe-area-context";
+
+export default function AskScreen() {
+ const insets = useSafeAreaInsets();
+ // 在组件内部添加 ref
+ const scrollViewRef = useRef(null);
+ // 用于控制是否显示问候页面
+ const [isHello, setIsHello] = useState(true);
+
+ // 获取对话id
+ const [conversationId, setConversationId] = useState(null);
+
+ // 用户对话信息收集
+ const [userMessages, setUserMessages] = useState([]);
+
+ const createNewConversation = useCallback(async () => {
+ setUserMessages([{
+ content: {
+ text: "请输入您的问题,寻找,请稍等..."
+ },
+ role: 'Assistant',
+ timestamp: new Date().toISOString()
+ }]);
+ const data = await fetchApi("/chat/new", {
+ method: "POST",
+ });
+ setConversationId(data);
+ }, []);
+
+ // 获取路由参数
+ const { sessionId, newSession } = useLocalSearchParams<{
+ sessionId: string;
+ newSession: string;
+ }>();
+ // 添加自动滚动到底部的效果
+ useEffect(() => {
+ if (scrollViewRef.current && !isHello) {
+ scrollViewRef.current.scrollToEnd({ animated: true });
+ }
+ }, [userMessages, isHello]);
+
+ useEffect(() => {
+ if (sessionId) {
+ setConversationId(sessionId)
+ setIsHello(false)
+ fetchApi(`/chats/${sessionId}/message-history`).then((res) => {
+ setUserMessages(res)
+ })
+ }
+ if (newSession) {
+ setIsHello(false)
+ createNewConversation()
+ }
+ }, [sessionId, newSession])
+
+ return (
+
+ {/* 导航栏 - 保持在顶部 */}
+
+ {/* 点击去memo list 页面 */}
+ {
+ router.replace('/memo-list');
+ }}
+ >
+
+
+ MemoWake
+
+
+
+
+ {
+ if (scrollViewRef.current && !isHello) {
+ scrollViewRef.current.scrollToEnd({ animated: true });
+ }
+ }}
+ >
+ {/* 内容区域 */}
+
+ {isHello ? : }
+
+
+
+ {/* 功能区 - 放在 KeyboardAvoidingView 内但在 ScrollView 外 */}
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ navbar: {
+ boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
+ backgroundColor: 'white',
+ zIndex: 10,
+ },
+ container: {
+ flex: 1,
+ backgroundColor: 'white',
+ borderTopLeftRadius: 20,
+ borderTopRightRadius: 20,
+ paddingTop: 60
+ },
+ backButton: {
+ marginLeft: 16,
+ padding: 12
+ },
+ content: {
+ flex: 1,
+ padding: 20,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ description: {
+ fontSize: 16,
+ color: '#666',
+ textAlign: 'center',
+ marginBottom: 40,
+ paddingHorizontal: 20,
+ lineHeight: 24,
+ },
+ chipsContainer: {
+ width: "100%",
+ flexDirection: 'row',
+ flexWrap: 'nowrap',
+ justifyContent: 'center',
+ marginBottom: 40,
+ display: "flex",
+ alignItems: "center",
+ overflow: "scroll",
+ },
+ chip: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: '#FFF5E6',
+ paddingVertical: 10,
+ paddingHorizontal: 16,
+ borderRadius: 20,
+ margin: 5,
+ },
+ chipText: {
+ marginLeft: 6,
+ color: '#FF9500',
+ fontSize: 14,
+ },
+ inputContainer: {
+ flexDirection: 'row',
+ padding: 16,
+ paddingBottom: 30,
+ backgroundColor: 'white',
+ },
+ input: {
+ flex: 1,
+ borderColor: '#FF9500',
+ borderWidth: 1,
+ borderRadius: 25,
+ paddingHorizontal: 20,
+ paddingVertical: 12,
+ fontSize: 16,
+ width: '100%', // 确保输入框宽度撑满
+ },
+ voiceButton: {
+ width: 40,
+ height: 40,
+ borderRadius: 20,
+ backgroundColor: '#FF9500',
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginRight: 8, // 添加一点右边距
+ },
+});
\ No newline at end of file
diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx
index ac93a74..b595ae0 100644
--- a/app/(tabs)/index.tsx
+++ b/app/(tabs)/index.tsx
@@ -1,20 +1,23 @@
-import MemoChat from '@/assets/icons/svg/memo-chat.svg';
-import MemoIP from '@/assets/icons/svg/memo-ip.svg';
+import IP from '@/assets/icons/svg/ip.svg';
+import Lottie from '@/components/lottie/lottie';
import { useRouter } from 'expo-router';
+import * as SecureStore from 'expo-secure-store';
import { useTranslation } from 'react-i18next';
-import { Text, TouchableOpacity, View } from 'react-native';
+import { Platform, Text, TouchableOpacity, View } from 'react-native';
export default function HomeScreen() {
const router = useRouter();
const { t } = useTranslation();
+ let token;
+
return (
-
+
{/* 标题区域 */}
{t('auth.welcomeAwaken.awaken', { ns: 'login' })}
-
+ {"\n"}
{t('auth.welcomeAwaken.your', { ns: 'login' })}
-
+ {"\n"}
{t('auth.welcomeAwaken.pm', { ns: 'login' })}
@@ -23,35 +26,34 @@ export default function HomeScreen() {
{/* Memo 形象区域 */}
-
- {/* 气泡对话框 */}
-
-
-
-
- {t('auth.welcomeAwaken.hi', { ns: 'login' })}
-
- {t('auth.welcomeAwaken.memo', { ns: 'login' })}
-
-
-
- {/* Memo 形象 */}
-
-
-
-
+ {/* 如果是web端,使用静态ip形象,否则使用lottie */}
+ {Platform.OS === 'web' ? : }
{/* 介绍文本 */}
{t('auth.welcomeAwaken.gallery', { ns: 'login' })}
-
+ {"\n"}
{t('auth.welcomeAwaken.back', { ns: 'login' })}
{/* 唤醒按钮 */}
router.push('/login')}
+ onPress={async () => {
+ // 判断是否有用户信息,有的话直接到usermessage页面 没有到登录页
+ if (Platform.OS === 'web') {
+ token = localStorage.getItem('token') || "";
+ } else {
+ token = await SecureStore.getItemAsync('token') || "";
+ }
+
+ if (token) {
+ router.push('/user-message')
+ } else {
+ router.push('/login')
+ }
+
+ }}
activeOpacity={0.8}
>
diff --git a/app/(tabs)/login.tsx b/app/(tabs)/login.tsx
index 7611780..e4b0fe7 100644
--- a/app/(tabs)/login.tsx
+++ b/app/(tabs)/login.tsx
@@ -2,14 +2,14 @@ import Handers from '@/assets/icons/svg/handers.svg';
import LoginIP1 from '@/assets/icons/svg/loginIp1.svg';
import LoginIP2 from '@/assets/icons/svg/loginIp2.svg';
import ForgetPwd from '@/components/login/forgetPwd';
-import PhoneLogin from '@/components/login/phoneLogin';
+import Login from '@/components/login/login';
import SignUp from '@/components/login/signUp';
import { ThemedText } from '@/components/ThemedText';
import { ThemedView } from '@/components/ThemedView';
import { useLocalSearchParams, useRouter } from 'expo-router';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { LayoutChangeEvent, TouchableOpacity, View, ViewStyle, useWindowDimensions } from 'react-native';
+import { Keyboard, KeyboardAvoidingView, LayoutChangeEvent, Platform, ScrollView, StatusBar, TouchableOpacity, View, ViewStyle, useWindowDimensions } from 'react-native';
const LoginScreen = () => {
const router = useRouter();
@@ -18,118 +18,158 @@ const LoginScreen = () => {
const [error, setError] = useState('123');
const [containerHeight, setContainerHeight] = useState(0);
const { height: windowHeight } = useWindowDimensions();
- // 密码可视
const [showPassword, setShowPassword] = useState(false);
+ const [keyboardOffset, setKeyboardOffset] = useState(0);
+ // 判断是否有白边
+ const statusBarHeight = StatusBar?.currentHeight ?? 0;
+
+ useEffect(() => {
+ const keyboardWillShowListener = Keyboard.addListener(
+ Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow',
+ (e) => {
+ setKeyboardOffset(e.endCoordinates.height);
+ }
+ );
+ const keyboardWillHideListener = Keyboard.addListener(
+ Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide',
+ () => {
+ setKeyboardOffset(0);
+ }
+ );
+
+ return () => {
+ keyboardWillShowListener.remove();
+ keyboardWillHideListener.remove();
+ };
+ }, []);
+
const handleLayout = (event: LayoutChangeEvent) => {
const { height } = event.nativeEvent.layout;
setContainerHeight(height);
};
- // 更新URL参数而不刷新页面
const updateUrlParam = (key: string, value: string) => {
router.setParams({ [key]: value });
}
- // 初始化
useEffect(() => {
setError('123')
}, [])
return (
-
-
- 0 ? windowHeight - containerHeight - 210 : 0,
- transform: [{ translateX: -200 }]
- }}
- >
- {
- showPassword
- ?
-
- :
-
- }
-
- 0 ? windowHeight - containerHeight - 1 : 0
- }}
- >
-
-
-
-
+
- {/* 错误提示 */}
-
-
- {error}
-
-
- {(() => {
- const commonProps = {
- updateUrlParam,
- setError,
- };
-
- const components = {
- signUp: (
-
- ),
- forgetPwd: (
-
- ),
- login: (
-
- )
- };
-
- return components[status as keyof typeof components] || components.login;
- })()}
-
- {status == 'login' || !status &&
-
-
- {status === 'login' || !status ? t('auth.agree.logintext', { ns: 'login' }) : t('auth.agree.singupText', { ns: 'login' })}
-
- { }}>
-
- {t('auth.agree.terms', { ns: 'login' })}
-
-
-
- {t('auth.agree.join', { ns: 'login' })}
-
- { }}>
-
- {t('auth.agree.privacyPolicy', { ns: 'login' })}
-
-
+
+
+ 0 ? windowHeight - containerHeight - 210 + statusBarHeight : 0,
+ transform: [{ translateX: -200 }, { translateY: keyboardOffset > 0 ? -keyboardOffset + statusBarHeight : -keyboardOffset }]
+ }}
+ >
+ {
+ showPassword
+ ?
+
+ :
+
+ }
+
+ 0 ? windowHeight - containerHeight - 1 + statusBarHeight : 0,
+ transform: [{ translateX: -39.5 }, { translateY: keyboardOffset > 0 ? -4 - keyboardOffset + statusBarHeight : -4 - keyboardOffset }]
+ }}
+ >
+
+
- }
-
-
+
+ {/* 错误提示 */}
+
+
+ {error}
+
+
+ {(() => {
+ const commonProps = {
+ updateUrlParam,
+ setError,
+ };
+
+ const components = {
+ signUp: (
+
+ ),
+ forgetPwd: (
+
+ ),
+ login: (
+
+ )
+ };
+
+ return components[status as keyof typeof components] || components.login;
+ })()}
+
+ {status == 'login' || !status &&
+
+
+ {status === 'login' || !status ? t('auth.agree.logintext', { ns: 'login' }) : t('auth.agree.singupText', { ns: 'login' })}
+
+ { }}>
+
+ {t('auth.agree.terms', { ns: 'login' })}
+
+
+
+ {t('auth.agree.join', { ns: 'login' })}
+
+ { }}>
+
+ {t('auth.agree.privacyPolicy', { ns: 'login' })}
+
+
+
+ }
+
+
+
+
);
}
diff --git a/app/(tabs)/memo-list.tsx b/app/(tabs)/memo-list.tsx
new file mode 100644
index 0000000..3f34e24
--- /dev/null
+++ b/app/(tabs)/memo-list.tsx
@@ -0,0 +1,300 @@
+import ChatSvg from "@/assets/icons/svg/chat.svg";
+import IPSvg from "@/assets/icons/svg/ip.svg";
+import { fetchApi } from "@/lib/server-api-util";
+import { Chat } from "@/types/ask";
+import { Ionicons } from "@expo/vector-icons";
+import { router } from "expo-router";
+import React, { useEffect } from 'react';
+import { FlatList, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+const MemoList = () => {
+ const insets = useSafeAreaInsets();
+
+ // 历史消息
+ const [historyList, setHistoryList] = React.useState([]);
+
+ // 获取历史消息
+ const getHistoryList = async () => {
+ await fetchApi(`/chats`).then((res) => {
+ setHistoryList(res)
+ })
+ }
+
+ // 获取对话历史消息
+ const getChatHistory = async (id: string) => {
+ // 跳转到聊天页面,并携带参数
+ router.push({
+ pathname: '/ask',
+ params: {
+ sessionId: id,
+ }
+ });
+
+ }
+
+ const handleMemoPress = (item: Chat) => {
+ getChatHistory(item.session_id)
+ }
+
+ useEffect(() => {
+ getHistoryList()
+ }, [])
+
+ return (
+
+ {/* 顶部标题和上传按钮 */}
+
+ Memo List
+
+
+ {/* 历史对话 */}
+ item.session_id}
+ ItemSeparatorComponent={() => (
+
+ )}
+ renderItem={({ item }) => (
+ handleMemoPress(item)}
+ >
+
+
+
+ {item.title || 'memo list 历史消息'}
+
+
+ {item.latest_message?.content?.text || 'memo list 历史消息'}
+
+
+
+ )}
+ />
+ {/* 底部导航栏 */}
+
+
+
+
+
+ {
+ router.push({
+ pathname: '/ask',
+ params: {
+ newSession: "true",
+ }
+ });
+ }}
+ >
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ separator: {
+ height: 1,
+ backgroundColor: '#f0f0f0',
+ marginLeft: 60, // 与头像对齐
+ },
+ container: {
+ flex: 1,
+ backgroundColor: 'white',
+ },
+ header: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: 16,
+ },
+ title: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ color: '#4C320C',
+ },
+ uploadButton: {
+ padding: 8,
+ },
+ searchContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: '#FFF',
+ borderRadius: 20,
+ marginHorizontal: 16,
+ paddingHorizontal: 16,
+ height: 48,
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 2 },
+ shadowOpacity: 0.1,
+ shadowRadius: 4,
+ elevation: 2,
+ },
+ searchIcon: {
+ marginRight: 8,
+ },
+ memoItem: {
+ flexDirection: 'row',
+ borderRadius: 0, // 移除圆角
+ padding: 16,
+ marginBottom: 0, // 移除底部边距
+ alignItems: 'center',
+ gap: 16,
+ backgroundColor: 'white',
+ },
+ avatar: {
+ width: 60,
+ height: 60,
+ borderRadius: 30,
+ marginRight: 16,
+ },
+ memoContent: {
+ flex: 1,
+ marginLeft: 12,
+ gap: 6,
+ justifyContent: 'center',
+ minWidth: 0, // 这行很重要,确保文本容器可以收缩到比内容更小
+ },
+ memoTitle: {
+ fontSize: 16,
+ fontWeight: '500',
+ color: '#333',
+ flex: 1, // 或者 flexShrink: 1
+ marginLeft: 12,
+ },
+ memoSubtitle: {
+ fontSize: 14,
+ color: '#666',
+ },
+ tabBar: {
+ flexDirection: 'row',
+ justifyContent: 'space-around',
+ alignItems: 'center',
+ backgroundColor: '#FFF',
+ borderTopWidth: 1,
+ borderTopColor: '#EEE',
+ paddingVertical: 12,
+ },
+ tabBarSvg: {
+ color: 'red',
+ },
+ tabItem: {
+ flex: 1,
+ alignItems: 'center',
+ },
+ tabCenter: {
+ width: 60,
+ height: 60,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ centerTabIcon: {
+ width: 50,
+ height: 50,
+ borderRadius: 25,
+ backgroundColor: '#FF9500',
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: -30,
+ },
+ centerTabImage: {
+ width: 40,
+ height: 40,
+ borderRadius: 20,
+ },
+ // 在 tabBarContainer 样式中添加
+ tabBarContainer: {
+ position: 'relative',
+ paddingBottom: 0,
+ overflow: 'visible',
+ marginTop: 10, // 添加一些上边距
+ },
+ tabBarContent: {
+ flexDirection: 'row',
+ justifyContent: 'space-around',
+ alignItems: 'center',
+ height: 60,
+ position: 'relative',
+ backgroundColor: 'rgba(255, 255, 255, 0.7)', // 半透明白色背景
+ borderRadius: 30, // 圆角
+ marginHorizontal: 16, // 左右边距
+ // 添加边框效果
+ borderWidth: 1,
+ borderColor: 'rgba(255, 255, 255, 0.8)',
+ // 添加阴影
+ ...Platform.select({
+ ios: {
+ shadowColor: 'rgba(0, 0, 0, 0.1)',
+ shadowOffset: { width: 0, height: 2 },
+ shadowOpacity: 0.8,
+ shadowRadius: 4,
+ },
+ android: {
+ elevation: 8,
+ },
+ }),
+ },
+ // 移除之前的 tabBarBackground 样式
+ // 修改 centerTabShadow 样式
+ centerTabShadow: {
+ position: 'absolute',
+ bottom: 15,
+ width: 60,
+ height: 60,
+ borderRadius: 30,
+ backgroundColor: 'white',
+ ...Platform.select({
+ ios: {
+ shadowColor: 'rgba(0, 0, 0, 0.2)',
+ shadowOffset: { width: 0, height: 4 },
+ shadowOpacity: 0.3,
+ shadowRadius: 6,
+ },
+ android: {
+ elevation: 10,
+ },
+ }),
+ },
+ centerTabContainer: {
+ flex: 1,
+ alignItems: 'center',
+ position: 'relative',
+ height: '100%',
+ },
+ centerTabButton: {
+ width: '100%',
+ height: '100%',
+ borderRadius: 30,
+ backgroundColor: '#FF9500',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ notificationDot: {
+ position: 'absolute',
+ top: -2,
+ right: -4,
+ width: 10,
+ height: 10,
+ borderRadius: 5,
+ backgroundColor: '#FF3B30',
+ },
+});
+
+export default MemoList;
\ No newline at end of file
diff --git a/app/(tabs)/user-message.tsx b/app/(tabs)/user-message.tsx
index 60915fe..9494a85 100644
--- a/app/(tabs)/user-message.tsx
+++ b/app/(tabs)/user-message.tsx
@@ -1,22 +1,23 @@
-import { FileStatus } from '@/components/file-upload/file-uploader';
import Choice from '@/components/user-message.tsx/choice';
import Done from '@/components/user-message.tsx/done';
import Look from '@/components/user-message.tsx/look';
import UserName from '@/components/user-message.tsx/userName';
import { fetchApi } from '@/lib/server-api-util';
+import { FileUploadItem } from '@/types/upload';
import { User } from '@/types/user';
import { useEffect, useState } from 'react';
-import { View } from 'react-native';
+import { KeyboardAvoidingView, Platform, ScrollView, StatusBar, View } from 'react-native';
export type Steps = "userName" | "look" | "choice" | "done";
-
export default function UserMessage() {
// 步骤
const [steps, setSteps] = useState("userName")
const [username, setUsername] = useState('')
const [avatar, setAvatar] = useState('')
- const [fileData, setFileData] = useState([])
+ const [fileData, setFileData] = useState([])
const [isLoading, setIsLoading] = useState(false);
const [userInfo, setUserInfo] = useState(null);
+ const statusBarHeight = StatusBar?.currentHeight ?? 0;
+
// 获取用户信息
const getUserInfo = async () => {
const res = await fetchApi("/iam/user-info");
@@ -31,11 +32,10 @@ export default function UserMessage() {
method: "POST",
body: JSON.stringify({
username,
- avatar_file_id: fileData?.[0]?.id
+ avatar_file_id: fileData?.[0]?.originalFile?.file_id
})
}).then(() => {
setIsLoading(false);
- getUserInfo();
setSteps('done');
}).catch(() => {
setIsLoading(false);
@@ -46,34 +46,46 @@ export default function UserMessage() {
}, []);
return (
-
-
- {(() => {
- const components = {
- userName: (
-
- ),
- look: (
-
- ),
- choice: ,
- done:
- };
+
+
+
+ {(() => {
+ const components = {
+ userName: (
+
+ ),
+ look: (
+
+ ),
+ choice: ,
+ done:
+ };
- return components[steps as keyof typeof components] || null;
- })()}
-
-
+ return components[steps as keyof typeof components] || null;
+ })()}
+
+
+
);
}
diff --git a/assets/icons/svg/ataver.svg b/assets/icons/svg/ataver.svg
index 20a4896..cc050a5 100644
--- a/assets/icons/svg/ataver.svg
+++ b/assets/icons/svg/ataver.svg
@@ -1,4 +1,7 @@
-