Przeglądaj źródła

fix: 修复应用宝审核拒绝的3个问题

1. 修复应用名称不符: Android打包名称改为艾斯(app.json name), iOS保留艾斯潮盒(CFBundleDisplayName)
2. 修复首次下载无隐私政策弹窗: 增加全局 PrivacyPopup 强制指引组件
3. 修复登录底部隐私政策/用户协议打不开: 修复 TouchableOpacity 阻挡并接入服务端协议富文本
4. 更新 Android app logo
zbb 1 miesiąc temu
rodzic
commit
f9a80979fe
5 zmienionych plików z 308 dodań i 96 usunięć
  1. 4 3
      app.json
  2. 5 3
      app/_layout.tsx
  3. 127 90
      app/login.tsx
  4. BIN
      assets/images/android-app-icon.png
  5. 172 0
      components/PrivacyPopup.tsx

+ 4 - 3
app.json

@@ -1,6 +1,6 @@
 {
   "expo": {
-    "name": "艾斯潮盒",
+    "name": "艾斯",
     "slug": "asios",
     "version": "1.0.3",
     "orientation": "portrait",
@@ -14,6 +14,7 @@
       "appleTeamId": "Y9ZVX3FRX6",
       "buildNumber": "19",
       "infoPlist": {
+        "CFBundleDisplayName": "艾斯潮盒",
         "ITSAppUsesNonExemptEncryption": false,
         "NSPhotoLibraryUsageDescription": "App需要访问您的相册以便您可以上传头像或保存商品分享图片。",
         "NSCameraUsageDescription": "App需要访问您的相机以便您可以拍摄并上传头像。"
@@ -22,12 +23,12 @@
     "android": {
       "adaptiveIcon": {
         "backgroundColor": "#1a1a2e",
-        "foregroundImage": "./assets/images/icon.png"
+        "foregroundImage": "./assets/images/android-app-icon.png"
       },
       "edgeToEdgeEnabled": true,
       "predictiveBackGestureEnabled": false,
       "package": "com.acetoys",
-      "versionCode": 102
+      "versionCode": 103
     },
     "web": {
       "output": "static",

+ 5 - 3
app/_layout.tsx

@@ -1,12 +1,13 @@
 import {
-    DarkTheme,
-    DefaultTheme,
-    ThemeProvider,
+  DarkTheme,
+  DefaultTheme,
+  ThemeProvider,
 } from "@react-navigation/native";
 import { Stack } from "expo-router";
 import { StatusBar } from "expo-status-bar";
 import "react-native-reanimated";
 
+import { PrivacyPopup } from "@/components/PrivacyPopup";
 import { AuthProvider } from "@/contexts/AuthContext";
 import { useColorScheme } from "@/hooks/use-color-scheme";
 import { LogBox } from "react-native";
@@ -70,6 +71,7 @@ export default function RootLayout() {
           <StatusBar style="auto" />
         </ThemeProvider>
       </AuthProvider>
+      <PrivacyPopup />
     </PaperProvider>
   );
 }

+ 127 - 90
app/login.tsx

@@ -1,34 +1,34 @@
-import { useRouter } from 'expo-router';
-import React, { useEffect, useRef, useState } from 'react';
+import { useRouter } from "expo-router";
+import React, { useEffect, useRef, useState } from "react";
 import {
-    Alert,
-    ImageBackground,
-    KeyboardAvoidingView,
-    Platform,
-    ScrollView,
-    StatusBar,
-    StyleSheet,
-    Text,
-    TextInput,
-    TouchableOpacity,
-    View,
-} from 'react-native';
-import { useSafeAreaInsets } from 'react-native-safe-area-context';
+  Alert,
+  ImageBackground,
+  KeyboardAvoidingView,
+  Platform,
+  ScrollView,
+  StatusBar,
+  StyleSheet,
+  Text,
+  TextInput,
+  TouchableOpacity,
+  View,
+} from "react-native";
+import { useSafeAreaInsets } from "react-native-safe-area-context";
 
-import { Images } from '@/constants/images';
-import { login, sendVerifyCode } from '@/services/user';
+import { Images } from "@/constants/images";
+import { login, sendVerifyCode } from "@/services/user";
 
 export default function LoginScreen() {
   const router = useRouter();
   const insets = useSafeAreaInsets();
 
-  const [phone, setPhone] = useState('');
-  const [verifyCode, setVerifyCode] = useState('');
+  const [phone, setPhone] = useState("");
+  const [verifyCode, setVerifyCode] = useState("");
   const [agreeFlag, setAgreeFlag] = useState(false);
   const [countdown, setCountdown] = useState(0);
   const [disabled, setDisabled] = useState(false);
   const [loading, setLoading] = useState(false);
-  const [tips, setTips] = useState('获取验证码');
+  const [tips, setTips] = useState("获取验证码");
 
   const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
 
@@ -51,7 +51,7 @@ export default function LoginScreen() {
     setDisabled(true);
     let count = 60;
     setTips(`${count}s后重新获取`);
-    
+
     timerRef.current = setInterval(() => {
       count--;
       if (count > 0) {
@@ -65,7 +65,7 @@ export default function LoginScreen() {
   // 重置倒计时
   const resetCountdown = () => {
     setDisabled(false);
-    setTips('获取验证码');
+    setTips("获取验证码");
     if (timerRef.current) {
       clearInterval(timerRef.current);
       timerRef.current = null;
@@ -75,38 +75,38 @@ export default function LoginScreen() {
   // 获取验证码
   const handleGetVerifyCode = async () => {
     if (disabled) return;
-    
+
     if (phone && isChinesePhoneNumber(phone)) {
       try {
-        const res = await sendVerifyCode(phone, 'LOGIN');
+        const res = await sendVerifyCode(phone, "LOGIN");
         if (res) {
-          Alert.alert('提示', '验证码已发送');
+          Alert.alert("提示", "验证码已发送");
           startCountdown();
         }
       } catch (error) {
-        Alert.alert('错误', '获取验证码失败,请重试');
+        Alert.alert("错误", "获取验证码失败,请重试");
       }
     } else {
-      Alert.alert('提示', '请输入正确的手机号');
+      Alert.alert("提示", "请输入正确的手机号");
     }
   };
 
   // 登录
   const handleLogin = async () => {
     if (!agreeFlag) {
-      Alert.alert('提示', '请您先阅读并同意用户协议和隐私政策');
+      Alert.alert("提示", "请您先阅读并同意用户协议和隐私政策");
       return;
     }
-    
+
     if (phone && isChinesePhoneNumber(phone) && verifyCode) {
       setLoading(true);
       try {
         const result = await login({
-          loginWay: 'MOBILE',
+          loginWay: "MOBILE",
           mobile: phone,
           verifycode: verifyCode,
         });
-        
+
         if (result.success) {
           // TODO: 如果 needInfo 为 true,跳转到完善信息页面
           // if (result.needInfo) {
@@ -115,14 +115,14 @@ export default function LoginScreen() {
           // }
           router.back();
         } else {
-          Alert.alert('错误', '登录失败,请检查验证码');
+          Alert.alert("错误", "登录失败,请检查验证码");
         }
       } catch (error) {
-        Alert.alert('错误', '登录失败');
+        Alert.alert("错误", "登录失败");
       }
       setLoading(false);
     } else {
-      Alert.alert('提示', '请输入手机号和验证码');
+      Alert.alert("提示", "请输入手机号和验证码");
     }
   };
 
@@ -140,7 +140,7 @@ export default function LoginScreen() {
       >
         <KeyboardAvoidingView
           style={styles.keyboardView}
-          behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
+          behavior={Platform.OS === "ios" ? "padding" : "height"}
         >
           <ScrollView
             contentContainerStyle={styles.scrollContent}
@@ -148,7 +148,9 @@ export default function LoginScreen() {
             showsVerticalScrollIndicator={false}
           >
             {/* 底部表单区域 */}
-            <View style={[styles.bottom, { paddingBottom: insets.bottom + 70 }]}>
+            <View
+              style={[styles.bottom, { paddingBottom: insets.bottom + 70 }]}
+            >
               {/* 表单 */}
               <View style={styles.form}>
                 {/* 手机号输入 */}
@@ -179,11 +181,19 @@ export default function LoginScreen() {
                     maxLength={6}
                   />
                   <TouchableOpacity
-                    style={[styles.verifyBtn, disabled && styles.verifyBtnDisabled]}
+                    style={[
+                      styles.verifyBtn,
+                      disabled && styles.verifyBtnDisabled,
+                    ]}
                     onPress={handleGetVerifyCode}
                     disabled={disabled}
                   >
-                    <Text style={[styles.verifyBtnText, disabled && styles.verifyBtnTextDisabled]}>
+                    <Text
+                      style={[
+                        styles.verifyBtnText,
+                        disabled && styles.verifyBtnTextDisabled,
+                      ]}
+                    >
                       {tips}
                     </Text>
                   </TouchableOpacity>
@@ -192,20 +202,43 @@ export default function LoginScreen() {
               </View>
 
               {/* 协议勾选 */}
-              <TouchableOpacity
-                style={styles.agree}
-                onPress={() => setAgreeFlag(!agreeFlag)}
-              >
-                <View style={[styles.radio, agreeFlag && styles.radioChecked]}>
-                  {agreeFlag && <View style={styles.radioInner} />}
-                </View>
-                <Text style={styles.agreeText}>
-                  我已阅读并同意
-                  <Text style={styles.linkText}>《用户协议》</Text>
-                  跟
-                  <Text style={styles.linkText}>《隐私政策》</Text>
+              <View style={styles.agree}>
+                <TouchableOpacity
+                  style={styles.agreeCheckboxArea}
+                  onPress={() => setAgreeFlag(!agreeFlag)}
+                  activeOpacity={0.8}
+                >
+                  <View
+                    style={[styles.radio, agreeFlag && styles.radioChecked]}
+                  >
+                    {agreeFlag && <View style={styles.radioInner} />}
+                  </View>
+                  <Text style={styles.agreeText}>我已阅读并同意</Text>
+                </TouchableOpacity>
+                <Text
+                  style={styles.linkText}
+                  onPress={() =>
+                    router.push({
+                      pathname: "/agreement",
+                      params: { type: "user.html" },
+                    })
+                  }
+                >
+                  《用户协议》
                 </Text>
-              </TouchableOpacity>
+                <Text style={styles.agreeText}>跟</Text>
+                <Text
+                  style={styles.linkText}
+                  onPress={() =>
+                    router.push({
+                      pathname: "/agreement",
+                      params: { type: "privacy.html" },
+                    })
+                  }
+                >
+                  《隐私政策》
+                </Text>
+              </View>
 
               {/* 按钮区域 */}
               <View style={styles.btnArea}>
@@ -221,7 +254,7 @@ export default function LoginScreen() {
                     resizeMode="stretch"
                   >
                     <Text style={styles.btnText}>
-                      {loading ? '登录中...' : '登录'}
+                      {loading ? "登录中..." : "登录"}
                     </Text>
                   </ImageBackground>
                 </TouchableOpacity>
@@ -241,7 +274,7 @@ export default function LoginScreen() {
 const styles = StyleSheet.create({
   container: {
     flex: 1,
-    backgroundColor: '#1a1a2e',
+    backgroundColor: "#1a1a2e",
   },
   background: {
     flex: 1,
@@ -251,60 +284,64 @@ const styles = StyleSheet.create({
   },
   scrollContent: {
     flexGrow: 1,
-    justifyContent: 'flex-end',
+    justifyContent: "flex-end",
   },
   bottom: {
-    width: '100%',
+    width: "100%",
   },
   form: {
     paddingHorizontal: 25,
   },
   formItem: {
-    flexDirection: 'row',
-    alignItems: 'center',
+    flexDirection: "row",
+    alignItems: "center",
     paddingVertical: 12,
   },
   label: {
-    color: '#fff',
+    color: "#fff",
     fontSize: 14,
     width: 50,
   },
   input: {
     flex: 1,
-    color: '#fff',
+    color: "#fff",
     fontSize: 14,
     paddingVertical: 8,
-    outlineStyle: 'none',
+    outlineStyle: "none",
   } as any,
   codeInput: {
     flex: 1,
   },
   divider: {
     height: 1,
-    backgroundColor: 'rgba(255,255,255,0.2)',
+    backgroundColor: "rgba(255,255,255,0.2)",
   },
   verifyBtn: {
-    backgroundColor: '#000',
+    backgroundColor: "#000",
     borderRadius: 4,
     paddingHorizontal: 10,
     paddingVertical: 5,
     minWidth: 80,
-    alignItems: 'center',
+    alignItems: "center",
   },
   verifyBtnDisabled: {
-    backgroundColor: '#ccc',
+    backgroundColor: "#ccc",
   },
   verifyBtnText: {
-    color: '#fff',
+    color: "#fff",
     fontSize: 12,
   },
   verifyBtnTextDisabled: {
-    color: '#666',
+    color: "#666",
+  },
+  agreeCheckboxArea: {
+    flexDirection: "row",
+    alignItems: "center",
   },
   agree: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    justifyContent: 'center',
+    flexDirection: "row",
+    alignItems: "center",
+    justifyContent: "center",
     marginTop: 25,
     paddingHorizontal: 25,
   },
@@ -313,64 +350,64 @@ const styles = StyleSheet.create({
     height: 16,
     borderRadius: 8,
     borderWidth: 1,
-    borderColor: 'rgba(255,255,255,0.5)',
+    borderColor: "rgba(255,255,255,0.5)",
     marginRight: 6,
-    justifyContent: 'center',
-    alignItems: 'center',
+    justifyContent: "center",
+    alignItems: "center",
   },
   radioChecked: {
-    borderColor: '#8b3dff',
-    backgroundColor: '#8b3dff',
+    borderColor: "#8b3dff",
+    backgroundColor: "#8b3dff",
   },
   radioInner: {
     width: 6,
     height: 6,
     borderRadius: 3,
-    backgroundColor: '#fff',
+    backgroundColor: "#fff",
   },
   agreeText: {
-    color: '#fff',
+    color: "#fff",
     fontSize: 12,
   },
   linkText: {
-    color: '#8b3dff',
+    color: "#8b3dff",
   },
   btnArea: {
     paddingTop: 15,
-    alignItems: 'center',
+    alignItems: "center",
   },
   btn: {
     width: 234,
     height: 45,
-    overflow: 'hidden',
+    overflow: "hidden",
   },
   loginBtnBg: {
-    width: '100%',
-    height: '100%',
-    justifyContent: 'center',
-    alignItems: 'center',
+    width: "100%",
+    height: "100%",
+    justifyContent: "center",
+    alignItems: "center",
   },
   btnDisabled: {
     opacity: 0.6,
   },
   btnText: {
-    color: '#fff',
+    color: "#fff",
     fontSize: 14,
-    fontWeight: '600',
+    fontWeight: "600",
   },
   btnBack: {
     width: 234,
     height: 35,
-    backgroundColor: '#fff',
+    backgroundColor: "#fff",
     borderRadius: 25,
-    justifyContent: 'center',
-    alignItems: 'center',
+    justifyContent: "center",
+    alignItems: "center",
     marginTop: 10,
     borderWidth: 1,
-    borderColor: '#fff',
+    borderColor: "#fff",
   },
   btnBackText: {
-    color: '#888',
+    color: "#888",
     fontSize: 12,
   },
 });

BIN
assets/images/android-app-icon.png


+ 172 - 0
components/PrivacyPopup.tsx

@@ -0,0 +1,172 @@
+import AsyncStorage from "@react-native-async-storage/async-storage";
+import { usePathname, useRouter } from "expo-router";
+import React, { useEffect, useState } from "react";
+import {
+    BackHandler,
+    Platform,
+    ScrollView,
+    StyleSheet,
+    Text,
+    TouchableOpacity,
+    View,
+} from "react-native";
+
+export function PrivacyPopup() {
+  const [agreed, setAgreed] = useState<boolean | null>(null);
+  const router = useRouter();
+  const pathname = usePathname();
+
+  useEffect(() => {
+    checkPrivacyStatus();
+  }, []);
+
+  const checkPrivacyStatus = async () => {
+    try {
+      const status = await AsyncStorage.getItem("hasAgreedPrivacy");
+      setAgreed(status === "true");
+    } catch (e) {
+      setAgreed(false);
+    }
+  };
+
+  const handleAgree = async () => {
+    try {
+      await AsyncStorage.setItem("hasAgreedPrivacy", "true");
+      setAgreed(true);
+    } catch (e) {
+      console.warn("Failed to save privacy status");
+    }
+  };
+
+  const handleDisagree = () => {
+    if (Platform.OS === "android") {
+      BackHandler.exitApp();
+    }
+  };
+
+  const openUserAgreement = () => {
+    router.push({ pathname: "/agreement", params: { type: "user.html" } });
+  };
+
+  const openPrivacyPolicy = () => {
+    router.push({ pathname: "/agreement", params: { type: "privacy.html" } });
+  };
+
+  if (agreed === null || agreed === true) {
+    return null;
+  }
+
+  // 隐藏弹窗以允许查看协议
+  if (pathname === "/agreement") {
+    return null;
+  }
+
+  return (
+    <View style={styles.overlay}>
+      <View style={styles.container}>
+        <Text style={styles.title}>个人信息保护指引</Text>
+        <ScrollView style={styles.scroll}>
+          <Text style={styles.content}>
+            感谢您使用我们要提供给您的服务!我们非常重视您的个人信息和隐私保护。在您使用我们的服务之前,请务必仔细阅读
+            <Text style={styles.link} onPress={openUserAgreement}>
+              《用户协议》
+            </Text>
+            和
+            <Text style={styles.link} onPress={openPrivacyPolicy}>
+              《隐私政策》
+            </Text>
+            。{"\n\n"}
+            我们将严格按照上述协议/政策为您提供服务,保护您的个人信息安全。如果您同意以上内容,请点击“同意”开始使用。
+          </Text>
+        </ScrollView>
+        <View style={styles.btnRow}>
+          <TouchableOpacity
+            style={styles.disagreeBtn}
+            onPress={handleDisagree}
+            activeOpacity={0.8}
+          >
+            <Text style={styles.disagreeText}>不同意并退出</Text>
+          </TouchableOpacity>
+          <TouchableOpacity
+            style={styles.agreeBtn}
+            onPress={handleAgree}
+            activeOpacity={0.8}
+          >
+            <Text style={styles.agreeText}>同意</Text>
+          </TouchableOpacity>
+        </View>
+      </View>
+    </View>
+  );
+}
+
+const styles = StyleSheet.create({
+  overlay: {
+    position: "absolute",
+    top: 0,
+    left: 0,
+    right: 0,
+    bottom: 0,
+    backgroundColor: "rgba(0,0,0,0.6)",
+    justifyContent: "center",
+    alignItems: "center",
+    padding: 30,
+    zIndex: 9999,
+    elevation: 9999,
+  },
+  container: {
+    width: "100%",
+    backgroundColor: "#fff",
+    borderRadius: 12,
+    padding: 24,
+    maxHeight: "70%",
+  },
+  title: {
+    fontSize: 18,
+    fontWeight: "bold",
+    color: "#333",
+    textAlign: "center",
+    marginBottom: 15,
+  },
+  scroll: {
+    maxHeight: 250,
+  },
+  content: {
+    fontSize: 14,
+    color: "#666",
+    lineHeight: 22,
+  },
+  link: {
+    color: "#8b3dff",
+  },
+  btnRow: {
+    flexDirection: "row",
+    justifyContent: "space-between",
+    marginTop: 20,
+  },
+  disagreeBtn: {
+    flex: 1,
+    paddingVertical: 12,
+    alignItems: "center",
+    backgroundColor: "#f5f5f5",
+    borderRadius: 20,
+    marginRight: 10,
+  },
+  disagreeText: {
+    color: "#999",
+    fontSize: 14,
+  },
+  agreeBtn: {
+    flex: 1,
+    paddingVertical: 12,
+    alignItems: "center",
+    backgroundColor: "#8b3dff",
+    borderRadius: 20,
+    marginLeft: 10,
+  },
+  agreeText: {
+    color: "#fff",
+    fontSize: 14,
+    fontWeight: "bold",
+  },
+});