diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index aaed35a..b5b2871 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -328,6 +328,17 @@ export default function TabLayout() { tabBarStyle: { display: 'none' } // 确保在标签栏中不显示 }} /> + + {/* 购买权益页面 */} + null, // 隐藏底部标签栏 + headerShown: false, // 隐藏导航栏 + tabBarStyle: { display: 'none' } // 确保在标签栏中不显示 + }} + /> ); } diff --git a/app/(tabs)/owner.tsx b/app/(tabs)/owner.tsx index 9b6a75f..b1d34cc 100644 --- a/app/(tabs)/owner.tsx +++ b/app/(tabs)/owner.tsx @@ -15,7 +15,7 @@ import { CountData, UserInfoDetails } from '@/types/user'; import { useRouter } from 'expo-router'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { FlatList, StyleSheet, View } from 'react-native'; +import { FlatList, StyleSheet, TouchableOpacity, View } from 'react-native'; import { useSafeAreaInsets } from "react-native-safe-area-context"; export default function OwnerPage() { @@ -86,13 +86,19 @@ export default function OwnerPage() { {/* 资源数据 */} - + router.push({ + pathname: '/rights', + params: { credit: userInfoDetails?.remain_points } + })} + style={styles.resourceContainer} + > {t("generalSetting.premium", { ns: "personal" })} {t("generalSetting.unlock", { ns: "personal" })} - + {/* 分类 */} diff --git a/app/(tabs)/rights.tsx b/app/(tabs)/rights.tsx new file mode 100644 index 0000000..73241a8 --- /dev/null +++ b/app/(tabs)/rights.tsx @@ -0,0 +1,223 @@ +import ProCardSvg from '@/assets/icons/svg/proCard.svg'; +import ReturnArrowSvg from '@/assets/icons/svg/returnArrow.svg'; +import RightsCardSvg from '@/assets/icons/svg/rightCard.svg'; +import StarSvg from '@/assets/icons/svg/whiteStart.svg'; +import PrivacyModal from '@/components/owner/qualification/privacy'; +import Normal from '@/components/owner/rights/normal'; +import Premium, { PayItem } from '@/components/owner/rights/premium'; +import ProRights from '@/components/owner/rights/proRights'; +import { maxDiscountProduct } from '@/components/owner/rights/utils'; +import { ThemedText } from '@/components/ThemedText'; +import { fetchApi } from '@/lib/server-api-util'; +import { useLocalSearchParams, useRouter } from "expo-router"; +import { useEffect, useState } from 'react'; +import { StyleSheet, TouchableOpacity, View } from 'react-native'; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +export default function Rights() { + const insets = useSafeAreaInsets(); + const router = useRouter(); + // 获取路由参数 + const { credit } = useLocalSearchParams<{ + credit: string; + }>(); + // 普通用户,会员 + const [userType, setUserType] = useState<'normal' | 'premium'>('normal'); + // 选择权益方式 + const [payType, setPayType] = useState(''); + + // 用户协议弹窗打开 + const [showTerms, setShowTerms] = useState(false); + + // 调接口获取支付信息 + const [premiumPay, setPremiumPay] = useState(); + const [loading, setLoading] = useState(false); + const getPAy = async () => { + setLoading(true); + const payInfo = await fetchApi(`/order/product-items?product_type=Membership`) + let bestValue = maxDiscountProduct(payInfo) + setPayType(bestValue?.product_code) + setPremiumPay([bestValue, ...payInfo?.filter((item) => item.product_code !== bestValue?.product_code)]); + setLoading(false); + } + useEffect(() => { + getPAy(); + }, []); + + return ( + + {/* 导航栏 */} + + { router.push('/owner') }} style={{ padding: 16 }}> + + + + Subscription + + 123 + + {/* 会员卡 */} + + {userType === 'normal' ? ( + + ) : ( + + )} + + + + Purchase + + + + {credit} + + + + + + {/* 会员信息 */} + + {/* 切换按钮 */} + + { setUserType("normal") }} style={[styles.switchButtonItem, { backgroundColor: userType === 'normal' ? "#FFB645" : "#fff", borderColor: userType === 'normal' ? "#FFB645" : "#E2793F" }]}> + Free + + { setUserType("premium") }} style={[styles.switchButtonItem, { backgroundColor: userType === 'premium' ? "#E2793F" : "#fff", borderColor: userType === 'premium' ? "#E2793F" : "#E2793F" }]}> + Pro + + + {/* 普通权益 */} + + {/* 会员权益 */} + + + {/* 会员权益信息 */} + + {/* 付费按钮 */} + + { + setUserType('premium'); + }} + activeOpacity={0.8} + > + + Subscribe Yearly + + + { + setShowTerms(true); + }} + activeOpacity={0.8} + > + + Terms • Privacy + + + + {/* 协议弹窗 */} + + + ); +} + +const styles = StyleSheet.create({ + goPay: { + backgroundColor: '#E2793F', + borderRadius: 24, + paddingVertical: 10, + display: "flex", + alignItems: "center", + width: "100%", + }, + switchButton: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + gap: 16, + marginBottom: 16 + }, + switchButtonItem: { + width: "48%", + borderRadius: 24, + paddingVertical: 6, + display: "flex", + alignItems: "center", + borderWidth: 1 + }, + info: { + marginHorizontal: 16, + marginVertical: 16, + padding: 16, + borderRadius: 12, + shadowColor: "#000", + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.25, + shadowRadius: 3.84, + elevation: 5, + }, + container: { + flex: 1, + backgroundColor: 'white', + }, + header: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginVertical: 16, + }, + headerTitle: { + fontSize: 20, + fontWeight: '700', + color: '#4C320C', + }, + card: { + marginHorizontal: 16, + marginVertical: 16, + backgroundColor: '#FFB645', + borderRadius: 12, + }, + cardContent: { + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + padding: 16, + justifyContent: 'space-between' + }, + cardinfo: { + alignItems: 'flex-end', + }, + cardTitle: { + fontSize: 12, + fontWeight: '700', + color: '#E2793F', + backgroundColor: '#fff', + paddingHorizontal: 8, + paddingVertical: 2, + borderRadius: 20, + textAlign: 'center', + marginBottom: 24 + }, + cardPoints: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + gap: 4 + }, + cardPointsText: { + fontSize: 32, + fontWeight: '700', + color: '#4C320C' + } +}); diff --git a/assets/icons/svg/blackStar.svg b/assets/icons/svg/blackStar.svg new file mode 100644 index 0000000..c2c076f --- /dev/null +++ b/assets/icons/svg/blackStar.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/svg/get.svg b/assets/icons/svg/get.svg new file mode 100644 index 0000000..1fd3a95 --- /dev/null +++ b/assets/icons/svg/get.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/svg/proCard.svg b/assets/icons/svg/proCard.svg new file mode 100644 index 0000000..834fb22 --- /dev/null +++ b/assets/icons/svg/proCard.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/svg/rightCard.svg b/assets/icons/svg/rightCard.svg new file mode 100644 index 0000000..80ed82e --- /dev/null +++ b/assets/icons/svg/rightCard.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/svg/whiteStart.svg b/assets/icons/svg/whiteStart.svg new file mode 100644 index 0000000..17fc045 --- /dev/null +++ b/assets/icons/svg/whiteStart.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/png/placeholder.png b/assets/images/png/placeholder.png index 5371d4a..0565ebd 100644 Binary files a/assets/images/png/placeholder.png and b/assets/images/png/placeholder.png differ diff --git a/components/owner/rights/normal.tsx b/components/owner/rights/normal.tsx new file mode 100644 index 0000000..8179dae --- /dev/null +++ b/components/owner/rights/normal.tsx @@ -0,0 +1,72 @@ +import GetSvg from "@/assets/icons/svg/get.svg"; +import { ThemedText } from "@/components/ThemedText"; +import { StyleProp, StyleSheet, TouchableOpacity, View, ViewStyle } from "react-native"; + +interface Props { + setUserType: (type: 'normal' | 'premium') => void; + style?: StyleProp; +} + +const Normal = (props: Props) => { + const { setUserType } = props; + + return ( + + + + + + Enjoy 100 Bonus Credits Every Month + + Generate more memory pictures & videos and explore your past. + + + + + 10GB of Cloud Storage + + Safely store your cherished photos, videos, and generated memories. + + + { + setUserType('premium'); + }} + activeOpacity={0.8} + > + + Go Premium + + + + ); +} + +export default Normal; +const styles = StyleSheet.create({ + goPro: { + backgroundColor: '#E2793F', + borderRadius: 24, + paddingVertical: 6, + display: "flex", + alignItems: "center", + width: "100%", + }, + normalInfo: { + display: "flex", + flexDirection: "column", + alignItems: "center", + gap: 16 + }, + normalItem: { + display: "flex", + flexDirection: "column", + alignItems: "flex-start", + gap: 16 + }, + normalItemContent: { + display: "flex", + flexDirection: "column", + } +}); \ No newline at end of file diff --git a/components/owner/rights/premium.tsx b/components/owner/rights/premium.tsx new file mode 100644 index 0000000..2485d10 --- /dev/null +++ b/components/owner/rights/premium.tsx @@ -0,0 +1,139 @@ +import BlackStarSvg from '@/assets/icons/svg/blackStar.svg'; +import { ThemedText } from "@/components/ThemedText"; +import { StyleProp, StyleSheet, TouchableOpacity, View, ViewStyle } from "react-native"; +import { ScrollView } from 'react-native-gesture-handler'; +import { maxDiscountProduct } from './utils'; + +interface Props { + style?: StyleProp; + payType: string; + setPayType: (type: string) => void; + premiumPay: any; + loading: boolean; + setShowTerms: (visible: boolean) => void; +} + +export interface PayItem { + id: number; + product_id: number; + product_type: string; + product_code: string; + product_name: string; + unit_price: { + amount: string; + currency: string; + }, + discount_amount: { + amount: string; + currency: string; + } + +} +const Premium = (props: Props) => { + const { style, payType, setPayType, premiumPay, loading, setShowTerms } = props; + const bestValue = maxDiscountProduct(premiumPay)?.product_code + + return ( + + + {loading + ? + + Loading... + + : + premiumPay?.map((item: PayItem) => { + return { + setPayType(item?.product_code); + }} + style={[styles.yearly, { borderColor: payType === item?.product_code ? '#FFB645' : '#E1E1E1', opacity: payType === item?.product_code ? 1 : 0.5 }]} + activeOpacity={0.8} + > + + + + Best Value + + + + + {item.product_code?.split('_')[item.product_code?.split('_')?.length - 1]} + + + $ {item.unit_price.amount} + + + + $ {item.discount_amount.amount} + + + + }) + } + + + + Cancel anytime before renewal. Learn more + + setShowTerms(true)}> + Terms & Conditions + + + + ); +} + +export default Premium; +const styles = StyleSheet.create({ + proInfo: { + borderRadius: 24, + display: "flex", + width: "100%", + }, + yearly: { + display: "flex", + flexDirection: "column", + alignItems: "center", + gap: 16, + borderColor: "#FFB645", + borderWidth: 2, + borderRadius: 24, + width: "48%", + paddingBottom: 16 + }, + quarterly: { + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + gap: 20, + borderColor: "#FAF9F6", + borderWidth: 2, + borderRadius: 24, + width: "48%", + paddingBottom: 16, + height: "100%", + }, + title: { + display: "flex", + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 8, + backgroundColor: "#FFB645", + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + padding: 4, + width: "100%" + }, + titleText: { + color: '#4C320C', + fontWeight: '700' + } +}); \ No newline at end of file diff --git a/components/owner/rights/proRights.tsx b/components/owner/rights/proRights.tsx new file mode 100644 index 0000000..11a5210 --- /dev/null +++ b/components/owner/rights/proRights.tsx @@ -0,0 +1,47 @@ +import GetSvg from "@/assets/icons/svg/get.svg"; +import { ThemedText } from "@/components/ThemedText"; +import { StyleProp, StyleSheet, View, ViewStyle } from "react-native"; +const ProRights = (props: { style?: StyleProp }) => { + const { style } = props; + return ( + + + Enjoy MemoWake Pro Benefits + + + + + no advertisement + + There are no advertisements, so you can use the product with peace of mind. + + + + + Enjoy 1000 Bonus Credits Every Month + + Generate more memory pictures & videos and explore your past. + + + + + 100GB of Cloud Storage + + Safely store your cherished photos, videos, and generated memories. + + + ); +} + +const styles = StyleSheet.create({ + proRights: { + padding: 16, + gap: 8 + }, + itemContent: { + display: "flex", + flexDirection: "column", + } +}) + +export default ProRights; diff --git a/components/owner/rights/utils.ts b/components/owner/rights/utils.ts new file mode 100644 index 0000000..b7e16c8 --- /dev/null +++ b/components/owner/rights/utils.ts @@ -0,0 +1,12 @@ +import { PayItem } from "./premium"; + +// 使用 reduce 方法获取 discount_amount 的 amount 值最大的对象 +export const maxDiscountProduct = (products: PayItem[]) => { + return products?.reduce((max, current) => { + // 将 amount 转换为数字进行比较 + const maxAmount = parseFloat(max.discount_amount.amount); + const currentAmount = parseFloat(current.discount_amount.amount); + + return currentAmount > maxAmount ? current : max; + }); +} \ No newline at end of file