fix:安卓支付宝不设置schema
This commit is contained in:
parent
3c6fb81807
commit
3c9b5fcb08
|
|
@ -50,7 +50,7 @@ const ConfirmModal: React.FC<ConfirmModalProps> = ({
|
|||
<Text className="text-[12px] font-black tracking-wider">{badge}</Text>
|
||||
</Block>
|
||||
|
||||
<Text className="mb-[8px] mt-[4px] text-[24px] font-black italic tracking-tight text-black">{title}</Text>
|
||||
<Text className="mb-[8px] mt-[4px] text-[24px] font-black tracking-tight text-black">{title}</Text>
|
||||
<Block className="mb-[20px] h-[4px] w-full bg-black" />
|
||||
|
||||
<Block className="mb-[24px]">
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ const VideoBox = ({ url, needWeb = true, width = 256, style, ...videoProps }: Pr
|
|||
const [urlFinal, setUrlFinal] = useState('')
|
||||
|
||||
const createUrl = (url: string) => {
|
||||
return `https://modal-dev.bowong.cc/api/custom/video/converter/${encodeURI(url)}?options=compression_level=3,quality=70,loop=true,resolution=${width}x${width},fps=24`
|
||||
return `https://modal-dev.bowong.cc/api/custom/video/converter/v2?media_url=${encodeURI(url)}&options=compression_level=3,quality=70,loop=true,resolution=${width}x${width},fps=24`
|
||||
}
|
||||
|
||||
const isImg = (url: any) => {
|
||||
|
|
@ -55,7 +55,7 @@ const VideoBox = ({ url, needWeb = true, width = 256, style, ...videoProps }: Pr
|
|||
|
||||
const webpUrl = createUrl(url!)
|
||||
const finalUrl = await resolveRedirect(webpUrl)
|
||||
// console.log('finalUrl-----------', finalUrl)
|
||||
// console.log('setRedirectUrl finalUrl-----------', finalUrl)
|
||||
setUrlFinal(finalUrl!)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ function renderNavItem(item: (typeof navItems)[0], isActive: boolean, onRouteCha
|
|||
<Block className="relative z-10 flex-row items-center justify-center gap-[4px]">
|
||||
<Ionicons color={isActive ? Colors.light.text : '#9CA3AF'} name={iconName as any} size={isActive ? 20 : 24} />
|
||||
{isActive && (
|
||||
<Text className="text-[12px] font-[900] italic text-black" numberOfLines={1}>
|
||||
<Text className="text-[12px] font-[900] text-black" numberOfLines={1}>
|
||||
{label}
|
||||
</Text>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -426,7 +426,7 @@ const styles = StyleSheet.create({
|
|||
fontSize: 11,
|
||||
opacity: 0.6,
|
||||
marginTop: 2,
|
||||
fontStyle: 'italic',
|
||||
fontStyle: ' ',
|
||||
},
|
||||
connectionStatus: {
|
||||
fontSize: 12,
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ export default function Generate() {
|
|||
<Block className="items-center justify-center py-[40px]">
|
||||
<Text className="mb-[8px] text-[14px] text-white/60">加载失败</Text>
|
||||
<Block className="-skew-x-6 border-2 border-white bg-white px-[16px] py-[6px]" onClick={handleRetry}>
|
||||
<Text className="text-[12px] font-[900] italic text-black">重试</Text>
|
||||
<Text className="text-[12px] font-[900] text-black">重试</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
)
|
||||
|
|
@ -262,11 +262,11 @@ const UploadCard = memo<UploadCardProps>(function UploadCard({ variant, img, onP
|
|||
<Block className="flex-1 overflow-hidden border-[3px] border-black bg-white" onClick={onPick}>
|
||||
{isMe ? (
|
||||
<Block className="absolute left-0 top-0 z-10 border-b-2 border-r-2 border-black bg-accent px-[8px] py-[2px]">
|
||||
<Text className="text-[10px] font-[900] italic text-black">我</Text>
|
||||
<Text className="text-[10px] font-[900] text-black">我</Text>
|
||||
</Block>
|
||||
) : (
|
||||
<Block className="absolute right-0 top-0 z-10 border-b-2 border-l-2 border-white bg-black px-[8px] py-[2px]">
|
||||
<Text className="text-[10px] font-[900] italic text-white">朋友</Text>
|
||||
<Text className="text-[10px] font-[900] text-white">朋友</Text>
|
||||
</Block>
|
||||
)}
|
||||
|
||||
|
|
@ -285,7 +285,7 @@ const UploadCard = memo<UploadCardProps>(function UploadCard({ variant, img, onP
|
|||
</Block>
|
||||
)}
|
||||
|
||||
<Text className="font-700 text-[9px] italic text-black/50">点击上传</Text>
|
||||
<Text className="font-700 text-[9px] text-black/50">点击上传</Text>
|
||||
</Block>
|
||||
)}
|
||||
</Block>
|
||||
|
|
@ -317,7 +317,7 @@ const PromptSection = memo<PromptSectionProps>(function PromptSection({ prompt,
|
|||
<Block className="mt-[24px] -skew-x-6">
|
||||
<Block className="mb-[4px] flex-row items-center px-[4px]">
|
||||
<Block className="border border-white/20 bg-black px-[8px] py-[2px]">
|
||||
<Text className="text-[12px] font-[900] italic text-accent">提示词</Text>
|
||||
<Text className="text-[12px] font-[900] text-accent">提示词</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
|
||||
|
|
@ -353,12 +353,13 @@ type TemplateItemProps = {
|
|||
const TemplateItem = memo<TemplateItemProps>(function TemplateItem({ item, itemWidth, isSelected, onSelect }) {
|
||||
return (
|
||||
<Block
|
||||
className={`relative border-2 ${isSelected ? 'border-accent' : 'border-black'}`}
|
||||
className={`relative overflow-hidden border-2 ${isSelected ? 'border-accent' : 'border-black'}`}
|
||||
style={{
|
||||
transform: [{ skewX: '-6deg' }],
|
||||
height: itemWidth,
|
||||
width: itemWidth,
|
||||
borderWidth: 2,
|
||||
marginBottom: 12,
|
||||
borderColor: isSelected ? '#FFE500' : '#000000',
|
||||
}}
|
||||
onClick={onSelect}
|
||||
|
|
@ -369,7 +370,7 @@ const TemplateItem = memo<TemplateItemProps>(function TemplateItem({ item, itemW
|
|||
{isSelected && <Block className="absolute inset-[-5px] border-[3px] border-accent" />}
|
||||
|
||||
<Block className="absolute inset-x-0 bottom-0 items-center bg-black/90 p-[4px]">
|
||||
<Text className={`text-[8px] font-[700] italic ${isSelected ? 'text-accent' : 'text-white'}`}>{item.name}</Text>
|
||||
<Text className={`text-[8px] font-[700] ${isSelected ? 'text-accent' : 'text-white'}`}>{item.name}</Text>
|
||||
</Block>
|
||||
|
||||
{item.type === 'video' && (
|
||||
|
|
@ -399,14 +400,14 @@ const TemplateSectionHeader = memo<TemplateSectionHeaderProps>(function Template
|
|||
style={style}
|
||||
>
|
||||
<FontAwesome color="black" name="film" size={16} />
|
||||
<Text className="text-[10px] font-[900] italic text-black">视频模版</Text>
|
||||
<Text className="text-[10px] font-[900] text-black">视频模版</Text>
|
||||
</Block>
|
||||
|
||||
<Block
|
||||
className="-skew-x-6 items-center justify-center border-2 border-black bg-white px-[8px] py-[4px]"
|
||||
onClick={onRandom}
|
||||
>
|
||||
<Text className="text-[10px] font-[900] italic text-black">换一波</Text>
|
||||
<Text className="text-[10px] font-[900] text-black">换一波</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
</Block>
|
||||
|
|
@ -434,14 +435,14 @@ const GenerateSection = memo<GenerateSectionProps>(function GenerateSection({ se
|
|||
>
|
||||
{/* 左侧文字 */}
|
||||
<Block className="z-10 flex-col">
|
||||
<Text className={`text-[24px] italic text-black`}>立即生成</Text>
|
||||
<Text className={`text-[24px] text-black`}>立即生成</Text>
|
||||
<Text className={`text-[10px] font-bold text-black`}>开始创作</Text>
|
||||
</Block>
|
||||
|
||||
{/* 右侧 Goo 标签 */}
|
||||
<Block className="z-10 flex-row items-center gap-[8px] border-2 border-black bg-black px-[12px] py-[4px]">
|
||||
<Ionicons color="#FFE500" name="flash" size={14} />
|
||||
<Text className="text-[14px] font-black italic text-accent">{price} Goo</Text>
|
||||
<Text className="text-[14px] font-black text-accent">{price} Goo</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
</Block>
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ export default function Sync() {
|
|||
if (tab === 'like') {
|
||||
if (!isAuthenticated) {
|
||||
setHasMore(false)
|
||||
newItems = []
|
||||
} else {
|
||||
const { data } = await loadFavorites({ page, limit: pageSize })
|
||||
newItems =
|
||||
|
|
@ -252,6 +253,30 @@ export default function Sync() {
|
|||
)
|
||||
}
|
||||
|
||||
const renderListEmpty = () => {
|
||||
if (activeTab === 'like' && !isAuthenticated) {
|
||||
return (
|
||||
<Block className="mt-[40px] items-center justify-center gap-[16px] py-[60px]">
|
||||
<Block className="size-[80px] items-center justify-center rounded-full border-4 border-white/20 bg-white/10">
|
||||
<Ionicons color="rgba(255,255,255,0.6)" name="heart-outline" size={40} />
|
||||
</Block>
|
||||
<Text className="text-[16px] font-bold text-white/80">查看您收藏的模板</Text>
|
||||
<Text className="text-[12px] text-white/50">登录后即可查看和管理收藏</Text>
|
||||
<Block
|
||||
className="mt-[12px] flex-row items-center gap-[8px] border-[3px] border-white bg-accent px-[32px] py-[12px] shadow-[4px_4px_0px_rgba(255,255,255,0.2)]"
|
||||
style={{ transform: [{ skewX: '-6deg' }] }}
|
||||
onClick={() => router.push('/auth')}
|
||||
>
|
||||
<Ionicons color="black" name="flash" size={16} style={{ transform: [{ skewX: '6deg' }] }} />
|
||||
<Text className="font-900 text-[14px] text-black" style={{ transform: [{ skewX: '6deg' }] }}>
|
||||
立即登录
|
||||
</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** ================= UI ================= */
|
||||
return (
|
||||
<Block className="flex-1 bg-black px-[12px]">
|
||||
|
|
@ -299,6 +324,7 @@ export default function Sync() {
|
|||
onSelect={() => setSelectedItem(item)}
|
||||
/>
|
||||
)}
|
||||
ListEmptyComponent={renderListEmpty}
|
||||
showsVerticalScrollIndicator={false}
|
||||
data={allItems}
|
||||
// @ts-ignore
|
||||
|
|
@ -394,7 +420,7 @@ const GooActions = memo<GooActionsProps>(function GooActions({ gooPoints, onAddG
|
|||
className="flex-row items-center gap-[4px] rounded-full border-[3px] border-white bg-black px-[12px] py-[8px] shadow-[3px_3px_0px_rgba(0,0,0,0.5)]"
|
||||
onClick={onAddGoo}
|
||||
>
|
||||
<Text className="text-[12px] font-black italic text-accent">{gooPoints} Goo</Text>
|
||||
<Text className="text-[12px] font-black text-accent">{gooPoints} Goo</Text>
|
||||
<Ionicons color="#FFE500" name="add" size={12} style={{ fontWeight: 'bold' }} />
|
||||
</Block>
|
||||
|
||||
|
|
@ -459,7 +485,7 @@ const HeroCircle = memo<HeroCircleProps>(function HeroCircle({ selectedItem, onQ
|
|||
<Block className="pointer-events-none absolute inset-0 rounded-full border-2 border-black/10" />
|
||||
|
||||
<Block className="absolute bottom-[24px] left-[8px] z-20 max-w-[100px] bg-black px-[12px] py-[4px] shadow-small-accent">
|
||||
<Text className="font-900 -skew-x-12 text-[10px] italic text-white" ellipsizeMode="tail" numberOfLines={1}>
|
||||
<Text className="font-900 -skew-x-12 text-[10px] text-white" ellipsizeMode="tail" numberOfLines={1}>
|
||||
{selectedItem?.id}
|
||||
</Text>
|
||||
</Block>
|
||||
|
|
@ -469,7 +495,7 @@ const HeroCircle = memo<HeroCircleProps>(function HeroCircle({ selectedItem, onQ
|
|||
onClick={onQuickGen}
|
||||
>
|
||||
<Ionicons color="black" name="flash" size={14} />
|
||||
<Text className="font-900 text-[12px] italic text-black">GET 同款</Text>
|
||||
<Text className="font-900 text-[12px] text-black">GET 同款</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
|
||||
|
|
@ -507,9 +533,7 @@ const FilterSection = memo<FilterSectionProps>(function FilterSection({ activeTa
|
|||
transform: [{ skewX: '-6deg' }],
|
||||
}}
|
||||
>
|
||||
<Text className={`text-[10px] font-[900] italic ${isActive ? 'text-black' : 'text-gray-500'}`}>
|
||||
{label}
|
||||
</Text>
|
||||
<Text className={`text-[10px] font-[900] ${isActive ? 'text-black' : 'text-gray-500'}`}>{label}</Text>
|
||||
</Block>
|
||||
)
|
||||
})}
|
||||
|
|
@ -577,13 +601,13 @@ const GridItem = memo<GridItemProps>(function GridItem({ item, isSelected, itemW
|
|||
|
||||
<Block className="w-full flex-1 flex-row items-center justify-between px-[4px]">
|
||||
<Block className="">
|
||||
<Text className="font-900 max-w-[40px] -skew-x-6 text-[7px] italic text-white" numberOfLines={1}>
|
||||
<Text className="font-900 max-w-[40px] -skew-x-6 text-[7px] text-white" numberOfLines={1}>
|
||||
{item.id}
|
||||
</Text>
|
||||
</Block>
|
||||
<Block className="-skew-x-6 flex-row items-center gap-[2px]">
|
||||
<Ionicons color="#FF0000" name="heart" size={10} />
|
||||
<Text className="font-900 text-[8px] italic text-white">{item.likeCount}</Text>
|
||||
<Text className="font-900 text-[8px] text-white">{item.likeCount}</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
</Block>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import React, { useMemo, useState, useCallback, useEffect } from 'react'
|
||||
import { Block, Text, Toast, ConfirmModal, VideoBox } from '@share/components'
|
||||
import Img from '@share/components/Img'
|
||||
import { Ionicons } from '@expo/vector-icons'
|
||||
import { Dimensions, ScrollView } from 'react-native'
|
||||
import { Block, ConfirmModal, Text, Toast, VideoBox } from '@share/components'
|
||||
import Img from '@share/components/Img'
|
||||
import { LinearGradient } from 'expo-linear-gradient'
|
||||
import { useAuth } from '@/hooks/core/use-auth'
|
||||
import { useTemplateGenerations, TemplateGeneration } from '@/hooks/data/use-template-generations'
|
||||
import { useUserBalance } from '@/hooks/core/use-user-balance'
|
||||
import { useTemplateActions } from '@/hooks/actions/use-template-actions'
|
||||
import { router } from 'expo-router'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { Dimensions, ScrollView } from 'react-native'
|
||||
|
||||
import { useTemplateActions } from '@/hooks/actions/use-template-actions'
|
||||
import { useAuth } from '@/hooks/core/use-auth'
|
||||
import { useUserBalance } from '@/hooks/core/use-user-balance'
|
||||
import { type TemplateGeneration, useTemplateGenerations } from '@/hooks/data/use-template-generations'
|
||||
|
||||
const BACKGROUND_VIDEOS = [
|
||||
'https://cdn.roasmax.cn/material/b46f380532e14cf58dd350dbacc7c34a.mp4',
|
||||
|
|
@ -39,23 +40,27 @@ export default function My() {
|
|||
}, [user?.id])
|
||||
|
||||
const generations = useMemo(() => generationsData?.data || [], [generationsData])
|
||||
const filteredPosts = activeFilter === 'my_gen' ? generations : generations.filter((g: TemplateGeneration) => g.status === 'completed')
|
||||
const filteredPosts =
|
||||
activeFilter === 'my_gen' ? generations : generations.filter((g: TemplateGeneration) => g.status === 'completed')
|
||||
|
||||
const toggleSelectionMode = useCallback(() => {
|
||||
setIsSelectionMode((v) => !v)
|
||||
setSelectedIds(new Set())
|
||||
}, [])
|
||||
|
||||
const handleItemClick = useCallback((generation: any) => {
|
||||
if (isSelectionMode) {
|
||||
setSelectedIds((prev) => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(generation.id)) next.delete(generation.id)
|
||||
else next.add(generation.id)
|
||||
return next
|
||||
})
|
||||
}
|
||||
}, [isSelectionMode])
|
||||
const handleItemClick = useCallback(
|
||||
(generation: any) => {
|
||||
if (isSelectionMode) {
|
||||
setSelectedIds((prev) => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(generation.id)) next.delete(generation.id)
|
||||
else next.add(generation.id)
|
||||
return next
|
||||
})
|
||||
}
|
||||
},
|
||||
[isSelectionMode],
|
||||
)
|
||||
|
||||
const handleDelete = useCallback(async () => {
|
||||
if (selectedIds.size === 0) return
|
||||
|
|
@ -96,49 +101,50 @@ export default function My() {
|
|||
const { width: screenWidth } = Dimensions.get('window')
|
||||
const itemWidth = Math.floor((screenWidth - 24 - 12 * 2) / 3)
|
||||
|
||||
const renderBanner = useCallback(() => (
|
||||
<Block className="absolute inset-[0px] z-[0] overflow-hidden">
|
||||
<VideoBox url={bgVideo} style={{ position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, opacity: 0.6 }} />
|
||||
<Block className="absolute inset-[0px] bg-black/10" />
|
||||
</Block>
|
||||
), [bgVideo])
|
||||
const renderBanner = useCallback(
|
||||
() => (
|
||||
<Block className="absolute inset-0 z-0 overflow-hidden">
|
||||
<VideoBox url={bgVideo} style={{ position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, opacity: 0.6 }} />
|
||||
<Block className="absolute inset-0 bg-black/10" />
|
||||
</Block>
|
||||
),
|
||||
[bgVideo],
|
||||
)
|
||||
|
||||
const renderHeaderCard = useCallback(() => {
|
||||
const username = user?.name || user?.email || 'Guest'
|
||||
const avatarUrl = user?.image || 'https://image.pollinations.ai/prompt/cool%20anime%20boy%20avatar%20hoodie?seed=123&nologo=true'
|
||||
const avatarUrl =
|
||||
user?.image || 'https://image.pollinations.ai/prompt/cool%20anime%20boy%20avatar%20hoodie?seed=123&nologo=true'
|
||||
const uid = user?.id?.slice(-6) || '000000'
|
||||
const generationCount = generations.length
|
||||
const completedCount = generations.filter((g: TemplateGeneration) => g.status === 'completed').length
|
||||
|
||||
return (
|
||||
<Block className="relative mb-[24px] mt-[16px] px-[12px]">
|
||||
<Block className="relative overflow-hidden border-[4px] border-black bg-white p-[16px] shadow-deep-black">
|
||||
<Block className="relative overflow-hidden border-4 border-black bg-white p-[16px] shadow-deep-black">
|
||||
<Block style={{ position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, opacity: 0.1 }} />
|
||||
<Block className="relative z-[10] flex-row items-center gap-[16px]">
|
||||
<Block className="h-[96px] w-[96px] border-[3px] border-black bg-accent p-[4px] shadow-soft-black-10">
|
||||
<Img
|
||||
src={avatarUrl}
|
||||
className="h-full w-full border border-black"
|
||||
/>
|
||||
<Block className="relative z-10 flex-row items-center gap-[16px]">
|
||||
<Block className="size-[96px] border-[3px] border-black bg-accent p-[4px] shadow-soft-black-10">
|
||||
<Img src={avatarUrl} className="size-full border border-black" />
|
||||
</Block>
|
||||
<Block className="flex-1">
|
||||
<Block className="mb-[4px] flex-row items-center justify-between">
|
||||
<Text className="font-900 skew-x-[-6deg] text-[24px] italic tracking-[-0.5px] text-black" numberOfLines={1}>
|
||||
<Text className="font-900 -skew-x-6 text-[24px] tracking-[-0.5px] text-black" numberOfLines={1}>
|
||||
{username}
|
||||
</Text>
|
||||
<Block onClick={handleLogout} className="border-[2px] border-black bg-black p-[8px] shadow-medium-gray">
|
||||
<Block onClick={handleLogout} className="border-2 border-black bg-black p-[8px] shadow-medium-gray">
|
||||
<Ionicons name="log-out-outline" size={18} color="white" />
|
||||
</Block>
|
||||
</Block>
|
||||
<Block className="flex-row items-center gap-[8px]">
|
||||
<Text className="font-900 border border-black bg-black px-[8px] py-[2px] text-[10px] italic text-white">
|
||||
<Text className="font-900 border border-black bg-black px-[8px] py-[2px] text-[10px] text-white">
|
||||
UID: {uid}
|
||||
</Text>
|
||||
<Text className="font-900 border-[2px] border-black bg-accent px-[8px] py-[2px] text-[10px] italic text-black shadow-small-accent">
|
||||
<Text className="font-900 border-2 border-black bg-accent px-[8px] py-[2px] text-[10px] text-black shadow-small-accent">
|
||||
CREDITS: {balance}
|
||||
</Text>
|
||||
</Block>
|
||||
<Block className="mt-[12px] flex-row items-center gap-[16px] border-t-[2px] border-black pt-[8px]">
|
||||
<Block className="mt-[12px] flex-row items-center gap-[16px] border-t-2 border-black pt-[8px]">
|
||||
<Block>
|
||||
<Text className="font-700 text-[10px] text-gray-500">TOTAL</Text>
|
||||
<Text className="font-900 text-[14px] text-black">{generationCount}</Text>
|
||||
|
|
@ -155,7 +161,7 @@ export default function My() {
|
|||
</Block>
|
||||
</Block>
|
||||
{balance > 100 && (
|
||||
<Block className="absolute right-[-32px] top-[1px] z-[20] rotate-[45deg] border-y-[2px] border-black bg-[#e61e25] px-[40px] py-[4px]">
|
||||
<Block className="absolute right-[-32px] top-px z-20 rotate-45 border-y-2 border-black bg-[#e61e25] px-[40px] py-[4px]">
|
||||
<Text className="font-900 text-[12px] text-white">PRO</Text>
|
||||
</Block>
|
||||
)}
|
||||
|
|
@ -164,46 +170,58 @@ export default function My() {
|
|||
)
|
||||
}, [user, balance, generations])
|
||||
|
||||
const renderActions = useCallback(() => (
|
||||
<Block className="mb-[24px] flex-row gap-[12px] px-[12px]">
|
||||
{[
|
||||
{ label: 'SHOP', color: '#4ADE80', icon: 'bag-outline' as const },
|
||||
{ label: 'SYNC', color: '#FFE500', icon: 'watch-outline' as const },
|
||||
{ label: 'PAY', color: '#e61e25', icon: 'card-outline' as const },
|
||||
].map(({ label, color, icon }) => (
|
||||
<Block key={label} className="relative flex-1 items-center justify-center border-[3px] border-black bg-white" style={{ height: 56 }}>
|
||||
<Ionicons name={icon} size={20} color="black" />
|
||||
<Text className="font-900 text-[10px] italic text-black">{label}</Text>
|
||||
</Block>
|
||||
))}
|
||||
</Block>
|
||||
), [])
|
||||
const renderActions = useCallback(
|
||||
() => (
|
||||
<Block className="mb-[24px] flex-row gap-[12px] px-[12px]">
|
||||
{[
|
||||
{ label: 'SHOP', color: '#4ADE80', icon: 'bag-outline' as const },
|
||||
{ label: 'SYNC', color: '#FFE500', icon: 'watch-outline' as const },
|
||||
{ label: 'PAY', color: '#e61e25', icon: 'card-outline' as const },
|
||||
].map(({ label, color, icon }) => (
|
||||
<Block
|
||||
key={label}
|
||||
className="relative flex-1 items-center justify-center border-[3px] border-black bg-white"
|
||||
style={{ height: 56 }}
|
||||
>
|
||||
<Ionicons name={icon} size={20} color="black" />
|
||||
<Text className="font-900 text-[10px] text-black">{label}</Text>
|
||||
</Block>
|
||||
))}
|
||||
</Block>
|
||||
),
|
||||
[],
|
||||
)
|
||||
|
||||
const renderFilters = useCallback(() => (
|
||||
<Block className="mb-[12px] flex-row items-center justify-between px-[12px]">
|
||||
<Block className="flex-row gap-[8px]">
|
||||
{['我的生成', '我的专辑'].map((label) => {
|
||||
const target = label === '我的生成' ? 'my_gen' : 'my_album'
|
||||
const isActive = activeFilter === (target as 'my_gen' | 'my_album')
|
||||
return (
|
||||
<Block
|
||||
key={label}
|
||||
onClick={() => setActiveFilter(target as 'my_gen' | 'my_album')}
|
||||
className={`border-[2px] border-black px-[16px] py-[4px] ${isActive ? 'bg-black' : 'bg-white'} skew-x-[-12deg]`}
|
||||
>
|
||||
<Text className={`font-900 text-[10px] italic ${isActive ? 'text-accent' : 'text-black'} skew-x-[12deg]`}>{label}</Text>
|
||||
</Block>
|
||||
)
|
||||
})}
|
||||
const renderFilters = useCallback(
|
||||
() => (
|
||||
<Block className="mb-[12px] flex-row items-center justify-between px-[12px]">
|
||||
<Block className="flex-row gap-[8px]">
|
||||
{['我的生成', '我的专辑'].map((label) => {
|
||||
const target = label === '我的生成' ? 'my_gen' : 'my_album'
|
||||
const isActive = activeFilter === (target as 'my_gen' | 'my_album')
|
||||
return (
|
||||
<Block
|
||||
key={label}
|
||||
onClick={() => setActiveFilter(target as 'my_gen' | 'my_album')}
|
||||
className={`border-2 border-black px-[16px] py-[4px] ${isActive ? 'bg-black' : 'bg-white'} -skew-x-12`}
|
||||
>
|
||||
<Text className={`font-900 text-[10px] ${isActive ? 'text-accent' : 'text-black'} skew-x-12`}>
|
||||
{label}
|
||||
</Text>
|
||||
</Block>
|
||||
)
|
||||
})}
|
||||
</Block>
|
||||
<Block
|
||||
onClick={toggleSelectionMode}
|
||||
className={`border-2 border-black px-[16px] py-[6px] shadow-medium-black ${isSelectionMode ? 'bg-[#e61e25] text-white' : 'bg-accent text-black'}`}
|
||||
>
|
||||
<Text className="font-900 text-[12px]">{isSelectionMode ? '取消' : '管理'}</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
<Block
|
||||
onClick={toggleSelectionMode}
|
||||
className={`border-[2px] border-black px-[16px] py-[6px] shadow-medium-black ${isSelectionMode ? 'bg-[#e61e25] text-white' : 'bg-accent text-black'}`}
|
||||
>
|
||||
<Text className="font-900 text-[12px] italic">{isSelectionMode ? '取消' : '管理'}</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
), [activeFilter, isSelectionMode, toggleSelectionMode])
|
||||
),
|
||||
[activeFilter, isSelectionMode, toggleSelectionMode],
|
||||
)
|
||||
|
||||
const renderGrid = useCallback(() => {
|
||||
if (filteredPosts.length === 0) {
|
||||
|
|
@ -211,7 +229,7 @@ export default function My() {
|
|||
<Block className="items-center justify-center px-[12px] py-[80px]">
|
||||
<Block className="items-center gap-[16px] border-[3px] border-black bg-white p-[32px] shadow-large-black">
|
||||
<Ionicons name="images-outline" size={64} color="#cccccc" />
|
||||
<Text className="font-900 text-[16px] italic text-gray-500">
|
||||
<Text className="font-900 text-[16px] text-gray-500">
|
||||
{activeFilter === 'my_gen' ? '暂无生成作品' : '暂无收藏'}
|
||||
</Text>
|
||||
</Block>
|
||||
|
|
@ -230,7 +248,7 @@ export default function My() {
|
|||
return (
|
||||
<Block key={generation.id} onClick={() => handleItemClick(generation)} className="relative">
|
||||
<Block
|
||||
className={`relative overflow-hidden border-[2px] ${isSelected ? 'shadow-[0px_0px_0px_4px_#FFE500]' : 'shadow-hard-black'} ${isSelected ? 'border-accent' : 'border-black'}`}
|
||||
className={`relative overflow-hidden border-2 ${isSelected ? 'shadow-[0px_0px_0px_4px_#FFE500]' : 'shadow-hard-black'} ${isSelected ? 'border-accent' : 'border-black'}`}
|
||||
style={{ transform: [{ skewX: '-6deg' }], height: itemWidth, width: itemWidth }}
|
||||
>
|
||||
<Block style={{ transform: [{ skewX: '6deg' }], height: itemWidth, width: itemWidth }}>
|
||||
|
|
@ -238,33 +256,36 @@ export default function My() {
|
|||
isVideoUrl(imageUrl) ? (
|
||||
<VideoBox url={`${imageUrl}#t=0.1`} style={{ height: itemWidth, width: itemWidth }} />
|
||||
) : (
|
||||
<Img src={imageUrl} className="h-full w-full" />
|
||||
<Img src={imageUrl} className="size-full" />
|
||||
)
|
||||
) : (
|
||||
<Block className="h-full w-full items-center justify-center bg-gray-200">
|
||||
<Block className="size-full items-center justify-center bg-gray-200">
|
||||
<Ionicons name="image-outline" size={32} color="#999" />
|
||||
</Block>
|
||||
)}
|
||||
</Block>
|
||||
{isSelected && <Block className="absolute inset-[0px] border-[3px] border-accent" />}
|
||||
{isSelected && <Block className="absolute inset-0 border-[3px] border-accent" />}
|
||||
|
||||
{isSelectionMode && (
|
||||
<Block className="absolute inset-[0px] z-[30] items-center justify-center" style={{ transform: [{ skewX: '6deg' }] }}>
|
||||
<Block
|
||||
className="absolute inset-0 z-30 items-center justify-center"
|
||||
style={{ transform: [{ skewX: '6deg' }] }}
|
||||
>
|
||||
<Block
|
||||
className={`h-[32px] w-[32px] items-center justify-center rounded-full border-[3px] border-black ${isSelected ? 'bg-accent' : 'bg-white/50'}`}
|
||||
className={`size-[32px] items-center justify-center rounded-full border-[3px] border-black ${isSelected ? 'bg-accent' : 'bg-white/50'}`}
|
||||
>
|
||||
<Ionicons name="checkmark" size={20} color="black" />
|
||||
</Block>
|
||||
</Block>
|
||||
)}
|
||||
{!isSelectionMode && (
|
||||
<Block className="absolute left-[0px] top-[0px] z-[20] border-b-[2px] border-r-[2px] border-black bg-accent px-[6px] py-[2px]">
|
||||
<Block className="absolute left-0 top-0 z-20 border-b-2 border-r-2 border-black bg-accent px-[6px] py-[2px]">
|
||||
<Text className="font-900 text-[8px] text-black">{statusRank}</Text>
|
||||
</Block>
|
||||
)}
|
||||
{!isSelectionMode && (
|
||||
<Block
|
||||
className="absolute bottom-[0px] left-[0px] right-[0px] z-[20] items-end justify-between p-[4px] pt-[16px]"
|
||||
className="absolute inset-x-0 bottom-0 z-20 items-end justify-between p-[4px] pt-[16px]"
|
||||
style={{ transform: [{ skewX: '6deg' }] }}
|
||||
>
|
||||
<LinearGradient
|
||||
|
|
@ -273,12 +294,12 @@ export default function My() {
|
|||
end={{ x: 0, y: 0 }}
|
||||
style={{ position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 }}
|
||||
/>
|
||||
<Text className="font-900 max-w-[40px] skew-x-[-6deg] text-[7px] italic text-white" numberOfLines={1}>
|
||||
<Text className="font-900 max-w-[40px] -skew-x-6 text-[7px] text-white" numberOfLines={1}>
|
||||
{generation.type}
|
||||
</Text>
|
||||
<Block className="skew-x-[-6deg] flex-row items-center gap-[2px]">
|
||||
<Block className="-skew-x-6 flex-row items-center gap-[2px]">
|
||||
<Ionicons name="time-outline" size={10} color="#FFE500" />
|
||||
<Text className="font-900 text-[8px] italic text-white">
|
||||
<Text className="font-900 text-[8px] text-white">
|
||||
{new Date(generation.createdAt).toLocaleDateString()}
|
||||
</Text>
|
||||
</Block>
|
||||
|
|
@ -297,26 +318,24 @@ export default function My() {
|
|||
const renderSelection = useCallback(() => {
|
||||
if (!isSelectionMode) return null
|
||||
return (
|
||||
<Block className="absolute bottom-[96px] left-[16px] right-[16px] z-[50]">
|
||||
<Block className="skew-x-[-3deg] flex-row items-center justify-between border-[3px] border-black bg-white p-[12px] shadow-large-black">
|
||||
<Block className="skew-x-[3deg] flex-row items-center gap-[12px] pl-[8px]">
|
||||
<Text className="font-900 text-[14px] italic">已选: {selectedIds.size}</Text>
|
||||
<Block className="absolute inset-x-[16px] bottom-[96px] z-50">
|
||||
<Block className="-skew-x-3 flex-row items-center justify-between border-[3px] border-black bg-white p-[12px] shadow-large-black">
|
||||
<Block className="skew-x-3 flex-row items-center gap-[12px] pl-[8px]">
|
||||
<Text className="font-900 text-[14px]">已选: {selectedIds.size}</Text>
|
||||
<Block onClick={selectAll} className="text-[12px] underline">
|
||||
<Text className="font-700 text-[12px]">全选</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
<Block
|
||||
onClick={handleDelete}
|
||||
className={`font-900 skew-x-[3deg] transform flex-row items-center gap-[8px] border-[2px] border-black px-[16px] py-[8px] text-[14px] shadow-medium-black ${selectedIds.size > 0 && !deleteLoading ? 'bg-[#e61e25]' : 'bg-gray-200'}`}
|
||||
className={`font-900 skew-x-3 flex-row items-center gap-[8px] border-2 border-black px-[16px] py-[8px] text-[14px] shadow-medium-black${selectedIds.size > 0 && !deleteLoading ? 'bg-[#e61e25]' : 'bg-gray-200'}`}
|
||||
>
|
||||
{deleteLoading ? (
|
||||
<Ionicons name="hourglass-outline" size={16} color="white" />
|
||||
) : (
|
||||
<Ionicons name="trash-outline" size={16} color="white" />
|
||||
)}
|
||||
<Text className="font-900 text-[14px] text-white">
|
||||
{deleteLoading ? '删除中...' : '删除'}
|
||||
</Text>
|
||||
<Text className="font-900 text-[14px] text-white">{deleteLoading ? '删除中...' : '删除'}</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
</Block>
|
||||
|
|
@ -326,9 +345,9 @@ export default function My() {
|
|||
return (
|
||||
<Block className="relative flex-1 items-center justify-center bg-black">
|
||||
{renderBanner()}
|
||||
<Block className="relative z-[10] items-center gap-[16px] border-[3px] border-accent bg-white p-[32px] shadow-large-accent">
|
||||
<Block className="relative z-10 items-center gap-[16px] border-[3px] border-accent bg-white p-[32px] shadow-large-accent">
|
||||
<Ionicons name="hourglass-outline" size={48} color="#FFE500" />
|
||||
<Text className="font-900 text-[16px] italic text-black">加载中...</Text>
|
||||
<Text className="font-900 text-[16px] text-black">加载中...</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
)
|
||||
|
|
@ -337,7 +356,7 @@ export default function My() {
|
|||
return (
|
||||
<Block className="relative flex-1 bg-black text-white">
|
||||
{renderBanner()}
|
||||
<ScrollView className="relative z-[10] flex-1">
|
||||
<ScrollView className="relative z-10 flex-1">
|
||||
{renderHeaderCard()}
|
||||
{renderActions()}
|
||||
{renderFilters()}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Block, ConfirmModal, Text, Toast, VideoBox } from '@share/components'
|
|||
import Img from '@share/components/Img'
|
||||
import { FlashList } from '@shopify/flash-list'
|
||||
import * as ImagePicker from 'expo-image-picker'
|
||||
import { router } from 'expo-router'
|
||||
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { ActivityIndicator, RefreshControl, ScrollView, View } from 'react-native'
|
||||
import Animated, { Easing, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated'
|
||||
|
|
@ -104,7 +105,7 @@ const Sync = () => {
|
|||
}
|
||||
}, [posts])
|
||||
|
||||
console.log('selectedItem-----------', selectedItem)
|
||||
// console.log('selectedItem-----------', selectedItem)
|
||||
|
||||
// 事件处理函数
|
||||
const handleConnectToggle = useCallback(
|
||||
|
|
@ -154,7 +155,7 @@ const Sync = () => {
|
|||
>
|
||||
<SpinningLoader />
|
||||
<Block style={{ marginLeft: 8 }}>
|
||||
<Text className="font-[bla]ck text-[14px] italic text-white">正在同步文件...</Text>
|
||||
<Text className="font-[bla]ck text-[14px] text-white">正在同步文件...</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
),
|
||||
|
|
@ -234,8 +235,7 @@ const Sync = () => {
|
|||
<ConfirmModal
|
||||
content={
|
||||
<Text className="font-[bol]d text-[14px] leading-relaxed text-gray-800">
|
||||
生成同款风格将消耗 <Text className="font-[bla]ck mx-[4px] text-[20px] italic text-[#e61e25]">2 Goo</Text>{' '}
|
||||
算力。
|
||||
生成同款风格将消耗 <Text className="font-[bla]ck mx-[4px] text-[20px] text-[#e61e25]">2 Goo</Text> 算力。
|
||||
</Text>
|
||||
}
|
||||
onCancel={() => Toast.hideModal()}
|
||||
|
|
@ -273,7 +273,7 @@ const Sync = () => {
|
|||
>
|
||||
<AntDesign color="white" name="check-circle" size={20} />
|
||||
<Block style={{ marginLeft: 8 }}>
|
||||
<Text className="font-[bla]ck text-[14px] italic text-white">生成中...</Text>
|
||||
<Text className="font-[bla]ck text-[14px] text-white">生成中...</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
),
|
||||
|
|
@ -474,11 +474,11 @@ const DeviceItem = memo(({ device, onConnectToggle }: { device: any; onConnectTo
|
|||
<Block className="relative z-10 ml-[16px] flex-1 flex-row items-center justify-between gap-[16px]">
|
||||
<Block className="flex-1">
|
||||
<Block className="flex-1 flex-row gap-[8px]">
|
||||
<Text className="break-words text-[18px] font-[900] italic">{name}</Text>
|
||||
<Text className="break-words text-[18px] font-[900]">{name}</Text>
|
||||
</Block>
|
||||
<Block className="mt-[4px] flex-row gap-[8px]">
|
||||
<Text
|
||||
className={`border-2 border-black px-[6px] text-[10px] font-[900] italic ${isConnected ? 'bg-black text-accent' : 'bg-gray-200 text-gray-500'}`}
|
||||
className={`border-2 border-black px-[6px] text-[10px] font-[900] ${isConnected ? 'bg-black text-accent' : 'bg-gray-200 text-gray-500'}`}
|
||||
>
|
||||
{isConnected ? '已连接' : '未连接'}
|
||||
</Text>
|
||||
|
|
@ -487,10 +487,10 @@ const DeviceItem = memo(({ device, onConnectToggle }: { device: any; onConnectTo
|
|||
|
||||
<Block className="relative z-10 items-center">
|
||||
<Block
|
||||
className={`h-[40px] items-center justify-center border-[3px] border-black px-[16px] text-[12px] font-[900] italic ${isConnected ? 'bg-accent text-black shadow-medium-black' : 'bg-white text-black shadow-medium-gray'}`}
|
||||
className={`h-[40px] items-center justify-center border-[3px] border-black px-[16px] text-[12px] font-[900] ${isConnected ? 'bg-accent text-black shadow-medium-black' : 'bg-white text-black shadow-medium-gray'}`}
|
||||
onClick={() => onConnectToggle(device)}
|
||||
>
|
||||
<Text className="text-[12px] font-[900] italic">{isConnected ? '已连接' : '连接'}</Text>
|
||||
<Text className="text-[12px] font-[900]">{isConnected ? '已连接' : '连接'}</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
</Block>
|
||||
|
|
@ -529,7 +529,7 @@ const GridItem = memo(
|
|||
style={{ transform: [{ skewX: '6deg' }] }}
|
||||
>
|
||||
<Ionicons color="white" name="close-circle" size={14} />
|
||||
<Text className="text-[10px] font-[900] italic text-white">失败</Text>
|
||||
<Text className="text-[10px] font-[900] text-white">失败</Text>
|
||||
</Block>
|
||||
)
|
||||
}
|
||||
|
|
@ -541,7 +541,7 @@ const GridItem = memo(
|
|||
style={{ transform: [{ skewX: '6deg' }] }}
|
||||
>
|
||||
<SpinningLoader />
|
||||
<Text className="text-[10px] font-[900] italic text-black">生成中</Text>
|
||||
<Text className="text-[10px] font-[900] text-black">生成中</Text>
|
||||
</Block>
|
||||
)
|
||||
}
|
||||
|
|
@ -589,7 +589,7 @@ const FilterButtons = memo(
|
|||
key={label}
|
||||
className={`-skew-x-3 border-2 border-black bg-black px-[16px] py-[4px] shadow-small-accent`}
|
||||
>
|
||||
<Text className={`text-[10px] font-[900] italic text-accent`}>{label}</Text>
|
||||
<Text className={`text-[10px] font-[900] text-accent`}>{label}</Text>
|
||||
</Block>
|
||||
)
|
||||
})}
|
||||
|
|
@ -598,7 +598,7 @@ const FilterButtons = memo(
|
|||
className={`border-2 border-black px-[16px] py-[6px] shadow-medium-black ${isSelectionMode ? 'bg-[#e61e25] text-white' : 'bg-accent text-black'}`}
|
||||
onClick={onToggleSelectionMode}
|
||||
>
|
||||
<Text className="text-[12px] font-[900] italic">{isSelectionMode ? '取消' : '管理'}</Text>
|
||||
<Text className="text-[12px] font-[900]">{isSelectionMode ? '取消' : '管理'}</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
)
|
||||
|
|
@ -624,7 +624,7 @@ const SelectionBar = memo(
|
|||
<Block className="absolute inset-x-[16px] bottom-[96px] z-50" style={{ paddingBottom: insets.bottom }}>
|
||||
<Block className="-skew-x-3 flex-row items-center justify-between border-[3px] border-black bg-white p-[12px] shadow-large-black">
|
||||
<Block className="skew-x-3 flex-row items-center gap-[12px] pl-[8px]">
|
||||
<Text className="text-[14px] font-[900] italic">已选: {selectedCount}</Text>
|
||||
<Text className="text-[14px] font-[900]">已选: {selectedCount}</Text>
|
||||
<Block className="text-[12px] underline" onClick={onSelectAll}>
|
||||
<Text className="text-[12px] font-[700]">全选</Text>
|
||||
</Block>
|
||||
|
|
@ -655,7 +655,7 @@ const FABButtons = memo(
|
|||
>
|
||||
<Block className="z-10 skew-x-6 flex-row items-center gap-[8px]">
|
||||
<MaterialCommunityIcons color="white" name="star-four-points" size={20} />
|
||||
<Text className="font-[bol]d text-[16px] italic text-white">再次生成</Text>
|
||||
<Text className="font-[bol]d text-[16px] text-white">再次生成</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
</Block>
|
||||
|
|
@ -667,7 +667,7 @@ const FABButtons = memo(
|
|||
>
|
||||
<Block className="z-10 flex-row items-center gap-[8px]">
|
||||
<Ionicons color={'white'} name="refresh" size={20} />
|
||||
<Text className={cn`font-[bol]d bg-black text-[16px] italic text-white`}>同步</Text>
|
||||
<Text className={cn`font-[bol]d bg-black text-[16px] text-white`}>同步</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
</Block>
|
||||
|
|
@ -679,6 +679,21 @@ const FABButtons = memo(
|
|||
|
||||
// ============ 主要组件部分 ============
|
||||
const HeaderBanner = memo(({ connectedDevice, onPick }: { connectedDevice: any; onPick: () => void }) => {
|
||||
const { user, isAuthenticated, signOut } = useAuth()
|
||||
|
||||
console.log('user-----------', user)
|
||||
|
||||
const handleLogout = () => {
|
||||
if (isAuthenticated) {
|
||||
signOut().then(() => {
|
||||
Toast.show({ title: '已登出' })
|
||||
})
|
||||
} else {
|
||||
router.replace('/auth')
|
||||
}
|
||||
}
|
||||
const loginText = isAuthenticated ? '登出' : '登录'
|
||||
|
||||
return (
|
||||
<Block className="relative z-40 flex-row items-center justify-between py-[12px]">
|
||||
<Block className="flex-row items-center gap-[8px] border-2 border-white bg-black py-[4px] shadow-hard-accent">
|
||||
|
|
@ -687,12 +702,20 @@ const HeaderBanner = memo(({ connectedDevice, onPick }: { connectedDevice: any;
|
|||
{connectedDevice ? '设备已连接' : '设备离线'}
|
||||
</Text>
|
||||
</Block>
|
||||
<Block
|
||||
className="h-[40px] flex-row items-center justify-center gap-[8px] border-[3px] border-black bg-white px-[12px] shadow-hard-black"
|
||||
onClick={onPick}
|
||||
>
|
||||
<Ionicons color="black" name="cloud-upload-outline" size={18} />
|
||||
<Text className="text-[12px] font-[900] italic text-black">上传本地</Text>
|
||||
<Block className="flex-row">
|
||||
<Block
|
||||
className="!mr-[12px] h-[40px] flex-row items-center justify-center gap-[8px] border-[3px] border-black bg-white px-[12px] shadow-hard-black"
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<Text className="text-[12px] font-[900] text-black">{loginText}</Text>
|
||||
</Block>
|
||||
<Block
|
||||
className="h-[40px] flex-row items-center justify-center gap-[8px] border-[3px] border-black bg-white px-[12px] shadow-hard-black"
|
||||
onClick={onPick}
|
||||
>
|
||||
<Ionicons color="black" name="cloud-upload-outline" size={18} />
|
||||
<Text className="text-[12px] font-[900] text-black">上传本地</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
</Block>
|
||||
)
|
||||
|
|
@ -728,7 +751,7 @@ const TopCircleSection = memo(
|
|||
name={connectedDevice ? 'wifi' : 'wifi-outline'}
|
||||
size={20}
|
||||
/>
|
||||
<Text className="text-[10px] font-[900] italic">{connectedDevice ? '已连接' : '选择设备'}</Text>
|
||||
<Text className="text-[10px] font-[900]">{connectedDevice ? '已连接' : '选择设备'}</Text>
|
||||
</Block>
|
||||
<Block className="relative size-[256px] items-center justify-center">
|
||||
<Block
|
||||
|
|
@ -741,7 +764,7 @@ const TopCircleSection = memo(
|
|||
{connectedDevice && (
|
||||
<Block className="absolute inset-0 items-center justify-center">
|
||||
{/* <Ionicons name="desktop-outline" size={48} color="white" /> */}
|
||||
<Text className="border border-white bg-black px-[12px] py-[2px] text-[12px] font-[900] italic text-white">
|
||||
<Text className="border border-white bg-black px-[12px] py-[2px] text-[12px] font-[900] text-white">
|
||||
离线模式
|
||||
</Text>
|
||||
</Block>
|
||||
|
|
@ -758,7 +781,7 @@ const TopCircleSection = memo(
|
|||
const GalleryRenderer = memo(({ selectedItem }: { selectedItem: any }) => {
|
||||
const url = selectedItem?.url || selectedItem?.imageUrl
|
||||
|
||||
console.log('GalleryRenderer--------------', selectedItem)
|
||||
// console.log('GalleryRenderer--------------', selectedItem)
|
||||
|
||||
if (!url) return null
|
||||
|
||||
|
|
@ -804,7 +827,7 @@ const ManagerView = memo(
|
|||
>
|
||||
<Ionicons color="black" name="chevron-back" size={24} />
|
||||
</Block>
|
||||
<Text className="text-[20px] font-[900] italic tracking-[-0.5px]">设备管理</Text>
|
||||
<Text className="text-[20px] font-[900] tracking-[-0.5px]">设备管理</Text>
|
||||
<Block className="size-[40px] items-center justify-center bg-black/0">{/* 空占位符 */}</Block>
|
||||
</Block>
|
||||
|
||||
|
|
|
|||
16
app/auth.tsx
16
app/auth.tsx
|
|
@ -126,7 +126,7 @@ export default function Auth() {
|
|||
<Block className="size-[80px] items-center justify-center rounded-full border-4 border-black bg-accent shadow-deep-black">
|
||||
<Ionicons color="black" name="flash" size={40} />
|
||||
</Block>
|
||||
<Text className="font-900 mt-[16px] text-[32px] italic text-white">LOOMART</Text>
|
||||
<Text className="font-900 mt-[16px] text-[32px] text-white">LOOMART</Text>
|
||||
<Block className="mt-[8px] h-[4px] w-[120px] bg-accent" />
|
||||
</Block>
|
||||
|
||||
|
|
@ -141,7 +141,7 @@ export default function Auth() {
|
|||
onClick={() => setMode(tabMode)}
|
||||
>
|
||||
<Text
|
||||
className={`font-900 text-[14px] italic ${isActive ? 'text-black' : 'text-gray-500'}`}
|
||||
className={`font-900 text-[14px] ${isActive ? 'text-black' : 'text-gray-500'}`}
|
||||
style={{ transform: [{ skewX: '6deg' }] }}
|
||||
>
|
||||
{tabMode === 'login' ? '登录' : '注册'}
|
||||
|
|
@ -159,7 +159,7 @@ export default function Auth() {
|
|||
<Block className="mt-[4px] gap-[16px]">
|
||||
{mode === 'login' ? (
|
||||
<Block>
|
||||
<Text className="font-900 mb-[8px] text-[12px] italic text-black">账号</Text>
|
||||
<Text className="font-900 mb-[8px] text-[12px] text-black">账号</Text>
|
||||
<Block
|
||||
className="flex-row items-center border-[3px] border-black bg-white px-[12px] shadow-medium-black"
|
||||
style={{ height: 48 }}
|
||||
|
|
@ -183,7 +183,7 @@ export default function Auth() {
|
|||
) : (
|
||||
<>
|
||||
<Block>
|
||||
<Text className="font-900 mb-[8px] text-[12px] italic text-black">邮箱</Text>
|
||||
<Text className="font-900 mb-[8px] text-[12px] text-black">邮箱</Text>
|
||||
<Block
|
||||
className="flex-row items-center border-[3px] border-black bg-white px-[12px] shadow-medium-black"
|
||||
style={{ height: 48 }}
|
||||
|
|
@ -207,7 +207,7 @@ export default function Auth() {
|
|||
</Block>
|
||||
|
||||
<Block>
|
||||
<Text className="font-900 mb-[8px] text-[12px] italic text-black">用户名</Text>
|
||||
<Text className="font-900 mb-[8px] text-[12px] text-black">用户名</Text>
|
||||
<Block
|
||||
className="flex-row items-center border-[3px] border-black bg-white px-[12px] shadow-medium-black"
|
||||
style={{ height: 48 }}
|
||||
|
|
@ -232,7 +232,7 @@ export default function Auth() {
|
|||
)}
|
||||
|
||||
<Block>
|
||||
<Text className="font-900 mb-[8px] text-[12px] italic text-black">密码</Text>
|
||||
<Text className="font-900 mb-[8px] text-[12px] text-black">密码</Text>
|
||||
<Block
|
||||
className="flex-row items-center border-[3px] border-black bg-white px-[12px] shadow-medium-black"
|
||||
style={{ height: 48 }}
|
||||
|
|
@ -256,7 +256,7 @@ export default function Auth() {
|
|||
|
||||
{mode === 'register' && (
|
||||
<Block>
|
||||
<Text className="font-900 mb-[8px] text-[12px] italic text-black">确认密码</Text>
|
||||
<Text className="font-900 mb-[8px] text-[12px] text-black">确认密码</Text>
|
||||
<Block
|
||||
className="flex-row items-center border-[3px] border-black bg-white px-[12px] shadow-medium-black"
|
||||
style={{ height: 48 }}
|
||||
|
|
@ -288,7 +288,7 @@ export default function Auth() {
|
|||
) : (
|
||||
<Ionicons color="black" name="flash" size={20} />
|
||||
)}
|
||||
<Text className="font-900 text-[16px] italic text-black">
|
||||
<Text className="font-900 text-[16px] text-black">
|
||||
{loading ? '处理中...' : mode === 'login' ? '登录' : '注册'}
|
||||
</Text>
|
||||
</Block>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import Alipay from 'expo-native-alipay'
|
|||
import { Stack, useRouter } from 'expo-router'
|
||||
import ExpoWeChat from 'expo-wechat'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Dimensions, ScrollView } from 'react-native'
|
||||
import { Dimensions, Platform, ScrollView } from 'react-native'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
|
||||
import { Block, Img, Text } from '@/@share/components'
|
||||
|
|
@ -43,9 +43,12 @@ export default function ChargePage() {
|
|||
|
||||
const initPay = async () => {
|
||||
// 设置支付宝回调 URL Scheme(使用 App 的统一 scheme)
|
||||
Alipay.setAlipayScheme(SCHEME)
|
||||
|
||||
Alipay.setAlipaySandbox(true) // 开启沙箱模式,测试环境使用
|
||||
if (Platform.OS === 'ios') {
|
||||
Alipay.setAlipayScheme(SCHEME)
|
||||
} else {
|
||||
Alipay.setAlipaySandbox(true) // 开启沙箱模式,测试环境使用
|
||||
}
|
||||
|
||||
console.log('-------alipay version:', await Alipay.getVersion())
|
||||
|
||||
|
|
@ -105,7 +108,7 @@ export default function ChargePage() {
|
|||
const renderMyPoints = () => (
|
||||
<Block className="mt-[12px] items-center justify-center py-[24px]">
|
||||
<Text className="text-[14px] font-[400] text-[#B0B0B0]">我的积分</Text>
|
||||
<Text className="mt-[4px] text-[40px] font-[900] italic tracking-tighter text-fg">{balanceRes?.balance}</Text>
|
||||
<Text className="mt-[4px] text-[40px] font-[900] tracking-tighter text-fg">{balanceRes?.balance}</Text>
|
||||
</Block>
|
||||
)
|
||||
|
||||
|
|
@ -211,7 +214,7 @@ export default function ChargePage() {
|
|||
opacity={0.8}
|
||||
onClick={handlePay}
|
||||
>
|
||||
<Text className="text-[16px] font-[900] italic text-black">确认充值</Text>
|
||||
<Text className="text-[16px] font-[900] text-black">确认充值</Text>
|
||||
</Block>
|
||||
|
||||
<Block className="mt-[12px] flex-row justify-center">
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export default function ChargePage() {
|
|||
const renderMyPoints = () => (
|
||||
<Block className="mt-[12px] items-center justify-center py-[24px]">
|
||||
<Text className="text-[14px] font-[400] text-[#B0B0B0]">我的积分</Text>
|
||||
<Text className="mt-[4px] text-[40px] font-[900] italic tracking-tighter text-fg">{balanceRes?.balance}</Text>
|
||||
<Text className="mt-[4px] text-[40px] font-[900] tracking-tighter text-fg">{balanceRes?.balance}</Text>
|
||||
</Block>
|
||||
)
|
||||
|
||||
|
|
@ -155,7 +155,7 @@ export default function ChargePage() {
|
|||
opacity={0.8}
|
||||
onClick={handlePay}
|
||||
>
|
||||
<Text className="text-[16px] font-[900] italic text-black">确认充值</Text>
|
||||
<Text className="text-[16px] font-[900] text-black">确认充值</Text>
|
||||
</Block>
|
||||
<Block className="mt-[12px] flex-row justify-center">
|
||||
<Text className="text-[11px] text-[#888888]">已阅读并同意 </Text>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export function AuthLoadingScreen() {
|
|||
<Block className="flex-1 items-center justify-center bg-black">
|
||||
<Block className="items-center gap-[16px] border-4 border-accent bg-white p-[32px] shadow-large-accent">
|
||||
<Ionicons color="#FFE500" name="hourglass-outline" size={48} />
|
||||
<Text className="font-900 text-[16px] italic text-black">加载中...</Text>
|
||||
<Text className="font-900 text-[16px] text-black">加载中...</Text>
|
||||
</Block>
|
||||
</Block>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { router } from 'expo-router'
|
||||
import * as SecureStore from 'expo-secure-store'
|
||||
import { storage } from './storage';
|
||||
|
||||
import { storage } from './storage'
|
||||
|
||||
interface FetchLoggerOptions {
|
||||
enableLogging?: boolean;
|
||||
logRequest?: boolean;
|
||||
logResponse?: boolean;
|
||||
logError?: boolean;
|
||||
enableLogging?: boolean
|
||||
logRequest?: boolean
|
||||
logResponse?: boolean
|
||||
logError?: boolean
|
||||
}
|
||||
|
||||
const defaultOptions: FetchLoggerOptions = {
|
||||
|
|
@ -14,89 +14,87 @@ const defaultOptions: FetchLoggerOptions = {
|
|||
logRequest: true,
|
||||
logResponse: true,
|
||||
logError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const originalFetch = global.fetch;
|
||||
const originalFetch = global.fetch
|
||||
|
||||
export const createFetchWithLogger = (options: FetchLoggerOptions = {}) => {
|
||||
const config = { ...defaultOptions, ...options };
|
||||
const config = { ...defaultOptions, ...options }
|
||||
|
||||
return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
||||
const startTime = Date.now();
|
||||
const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
|
||||
const method = init?.method || 'GET';
|
||||
const startTime = Date.now()
|
||||
const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url
|
||||
const method = init?.method || 'GET'
|
||||
|
||||
try {
|
||||
const token = await storage.getItem('token');
|
||||
const token = await storage.getItem('token')
|
||||
|
||||
if (token) {
|
||||
init = {
|
||||
...init,
|
||||
headers: {
|
||||
...init?.headers,
|
||||
'authorization': `Bearer ${token}`,
|
||||
authorization: `Bearer ${token}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const response = await originalFetch(input, init);
|
||||
const response = await originalFetch(input, init)
|
||||
|
||||
if (response.status === 401) {
|
||||
console.warn('🔐 401 未授权,跳转到登录页')
|
||||
router.replace('/auth')
|
||||
}
|
||||
|
||||
return response;
|
||||
return response
|
||||
} catch (error) {
|
||||
const duration = Date.now() - startTime;
|
||||
const duration = Date.now() - startTime
|
||||
|
||||
if (config.enableLogging && config.logError) {
|
||||
console.group(`❌ [FETCH 错误] ${method} ${url}`);
|
||||
console.error('📍 URL:', url);
|
||||
console.error('🔧 Method:', method);
|
||||
console.error('⏱️ Duration:', `${duration}ms`);
|
||||
console.group(`❌ [FETCH 错误] ${method} ${url}`)
|
||||
console.error('📍 URL:', url)
|
||||
console.error('🔧 Method:', method)
|
||||
console.error('⏱️ Duration:', `${duration}ms`)
|
||||
|
||||
if (init?.headers) {
|
||||
console.error('📋 Request Headers:', JSON.stringify(init.headers, null, 2));
|
||||
console.error('📋 Request Headers:', JSON.stringify(init.headers, null, 2))
|
||||
}
|
||||
|
||||
if (init?.body) {
|
||||
try {
|
||||
const bodyContent = typeof init.body === 'string'
|
||||
? init.body
|
||||
: JSON.stringify(init.body);
|
||||
console.error('📦 Request Body:', bodyContent);
|
||||
const bodyContent = typeof init.body === 'string' ? init.body : JSON.stringify(init.body)
|
||||
console.error('📦 Request Body:', bodyContent)
|
||||
} catch (e) {
|
||||
console.error('📦 Request Body: [无法序列化]');
|
||||
console.error('📦 Request Body: [无法序列化]')
|
||||
}
|
||||
}
|
||||
|
||||
console.error('💥 Error Type:', error?.constructor?.name || 'Unknown');
|
||||
console.error('💥 Error Message:', error instanceof Error ? error.message : String(error));
|
||||
console.error('💥 Error Type:', error?.constructor?.name || 'Unknown')
|
||||
console.error('💥 Error Message:', error instanceof Error ? error.message : String(error))
|
||||
|
||||
if (error instanceof Error && error.stack) {
|
||||
console.error('📚 Stack Trace:', error.stack);
|
||||
console.error('📚 Stack Trace:', error.stack)
|
||||
}
|
||||
|
||||
if (error && typeof error === 'object') {
|
||||
console.error('🔍 Error Details:', JSON.stringify(error, Object.getOwnPropertyNames(error), 2));
|
||||
console.error('🔍 Error Details:', JSON.stringify(error, Object.getOwnPropertyNames(error), 2))
|
||||
}
|
||||
|
||||
console.groupEnd();
|
||||
console.groupEnd()
|
||||
}
|
||||
|
||||
throw error;
|
||||
throw error
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchWithLogger = createFetchWithLogger();
|
||||
export const fetchWithLogger = createFetchWithLogger()
|
||||
|
||||
export const setupGlobalFetchLogger = (options?: FetchLoggerOptions) => {
|
||||
const loggedFetch = createFetchWithLogger(options);
|
||||
global.fetch = loggedFetch as typeof fetch;
|
||||
const loggedFetch = createFetchWithLogger(options)
|
||||
global.fetch = loggedFetch as typeof fetch
|
||||
|
||||
return () => {
|
||||
global.fetch = originalFetch;
|
||||
};
|
||||
};
|
||||
global.fetch = originalFetch
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue