diff --git a/app/(tabs)/my.tsx b/app/(tabs)/my.tsx
index 5105947..5ac04f0 100644
--- a/app/(tabs)/my.tsx
+++ b/app/(tabs)/my.tsx
@@ -21,11 +21,12 @@ import { PointsIcon, SearchIcon, SettingsIcon } from '@/components/icon'
import EditProfileDrawer from '@/components/drawer/EditProfileDrawer'
import Dropdown from '@/components/ui/dropdown'
import Toast from '@/components/ui/Toast'
-import { signOut , useSession } from '@/lib/auth'
+import { signOut, useSession } from '@/lib/auth'
import { useTemplateGenerations, type TemplateGeneration } from '@/hooks'
import { MySkeleton } from '@/components/skeleton/MySkeleton'
import { useUserBalance } from '@/hooks/use-user-balance'
+import { KeyboardAwareScrollView } from 'react-native-keyboard-controller'
const { width: screenWidth } = Dimensions.get('window')
const GALLERY_GAP = 2
@@ -173,7 +174,7 @@ export default function My() {
return t('my.languageSwitchEn')
}
}
-
+
const settingsOptions = [
{ label: t('my.changePassword'), value: 'changePassword' },
{ label: getLanguageLabel(), value: 'language' },
@@ -182,168 +183,175 @@ export default function My() {
return (
-
-
-
- {/* 顶部积分与设置 */}
-
- router.push('/membership' as any)}
- >
-
- {balance}
-
- handleSettingsSelect(value)}
- renderTrigger={(selectedOption, isOpen, toggle) => (
-
-
-
- )}
- dropdownStyle={{
- minWidth: 160,
- right: 10,
- backgroundColor: '#2A2A2A80',
+
+
+
+
+
+ {/* 顶部积分与设置 */}
+
+ router.push('/membership' as any)}
+ >
+
+ {balance}
+
+ handleSettingsSelect(value)}
+ renderTrigger={(selectedOption, isOpen, toggle) => (
+
+
+
+ )}
+ dropdownStyle={{
+ minWidth: 160,
+ right: 10,
+ backgroundColor: '#2A2A2A80',
+ }}
+ />
+
+
+ {/* 个人信息区 */}
+
+
+
+ {profileName}
+ ID {session?.user?.id?.slice(0, 8) || '--------'}
+
+ {
+ console.log('[my.tsx] Edit Profile button pressed')
+ setEditDrawerVisible(true)
+ }}
+ >
+ {t('my.editProfile')}
+
+
+
+ {/* "生成作品" 标题行 */}
+
+ {t('my.generatedWorks')}
+ router.push('/worksList' as any)}
+ >
+
+
+
+
+ {/* 作品九宫格 */}
+ {loading ? (
+
+ ) : (
+
+ }
+ >
+
+ {generations.map((item, index) => (
+ {
+ if (item.status === 'completed') {
+ router.push({
+ pathname: '/generationRecord' as any,
+ params: { id: item.id },
+ })
+ }
+ }}
+ disabled={item.status !== 'completed'}
+ >
+
+
+ {/* 遮罩:非完成状态 */}
+ {item.status !== 'completed' && (
+
+ )}
+
+ {/* 数量角标:已完成且有结果 */}
+ {item.status === 'completed' && (item.resultUrl?.length || 0) > 0 && (
+
+
+ {item.resultUrl?.length || 1}
+
+
+ )}
+
+ {/* 状态角标 */}
+ {item.status === 'running' && (
+
+ {t('my.generating')}
+
+ )}
+ {item.status === 'pending' && (
+
+ {t('my.queuing')}
+
+ )}
+
+ ))}
+
+ {/* 加载更多指示器 */}
+ {loadingMore && (
+
+
+
+ )}
+
+ {/* 空状态提示 */}
+ {!loading && generations.length === 0 && (
+
+
+ {t('my.noWorks')}
+
+
+ )}
+
+
+ )}
+
+ {/* 编辑资料抽屉 */}
+
+ setEditDrawerVisible(false)}
+ initialName={profileName}
+ initialAvatar={session?.user?.image}
+ onSave={(data) => {
+ setProfileName(data.name)
}}
/>
-
-
- {/* 个人信息区 */}
-
-
-
- {profileName}
- ID {session?.user?.id?.slice(0, 8) || '--------'}
-
- setEditDrawerVisible(true)}
- >
- {t('my.editProfile')}
-
-
-
- {/* "生成作品" 标题行 */}
-
- {t('my.generatedWorks')}
- router.push('/worksList' as any)}
- >
-
-
-
-
- {/* 作品九宫格 */}
- {loading ? (
-
- ) : (
-
- }
- >
-
- {generations.map((item, index) => (
- {
- if (item.status === 'completed') {
- router.push({
- pathname: '/generationRecord' as any,
- params: { id: item.id },
- })
- }
- }}
- disabled={item.status !== 'completed'}
- >
-
-
- {/* 遮罩:非完成状态 */}
- {item.status !== 'completed' && (
-
- )}
-
- {/* 数量角标:已完成且有结果 */}
- {item.status === 'completed' && (item.resultUrl?.length || 0) > 0 && (
-
-
- {item.resultUrl?.length || 1}
-
-
- )}
-
- {/* 状态角标 */}
- {item.status === 'running' && (
-
- {t('my.generating')}
-
- )}
- {item.status === 'pending' && (
-
- {t('my.queuing')}
-
- )}
-
- ))}
-
- {/* 加载更多指示器 */}
- {loadingMore && (
-
-
-
- )}
-
- {/* 空状态提示 */}
- {!loading && generations.length === 0 && (
-
-
- {t('my.noWorks')}
-
-
- )}
-
-
- )}
-
- {/* 编辑资料抽屉 */}
- setEditDrawerVisible(false)}
- initialName={profileName}
- initialAvatar={session?.user?.image}
- onSave={(data) => {
- setProfileName(data.name)
- }}
- />
+
)
}
diff --git a/app/auth.tsx b/app/auth.tsx
index 8e6fb95..6e0cc01 100644
--- a/app/auth.tsx
+++ b/app/auth.tsx
@@ -4,6 +4,7 @@ import { StatusBar } from 'expo-status-bar'
import { SafeAreaView } from 'react-native-safe-area-context'
import { useRouter } from 'expo-router'
import { LinearGradient } from 'expo-linear-gradient'
+import { KeyboardAwareScrollView } from 'react-native-keyboard-controller'
import { AuthForm } from '@/components/blocks/AuthForm'
import { useSession } from '@/lib/auth'
@@ -48,9 +49,15 @@ export default function Auth() {
-
-
-
+
+
+
+
+
)
@@ -66,10 +73,17 @@ const styles = StyleSheet.create({
flex: 1,
backgroundColor: 'transparent',
},
- content: {
+ scrollView: {
flex: 1,
+ },
+ scrollContent: {
+ flexGrow: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 20,
},
+ content: {
+ width: '100%',
+ alignItems: 'center',
+ },
})
diff --git a/app/changePassword.tsx b/app/changePassword.tsx
index 0ba7d99..2c66b0d 100644
--- a/app/changePassword.tsx
+++ b/app/changePassword.tsx
@@ -3,7 +3,6 @@ import {
View,
Text,
StyleSheet,
- ScrollView,
Pressable,
Platform,
TextInput,
@@ -14,6 +13,7 @@ import { SafeAreaView } from 'react-native-safe-area-context'
import { useRouter } from 'expo-router'
import { StatusBar as RNStatusBar } from 'react-native'
import { useTranslation } from 'react-i18next'
+import { KeyboardAwareScrollView } from 'react-native-keyboard-controller'
import { LeftArrowIcon } from '@/components/icon'
import { LinearGradient } from 'expo-linear-gradient'
import { useChangePassword } from '@/hooks/use-change-password'
@@ -140,11 +140,10 @@ export default function ChangePasswordScreen() {
{t('changePassword.title')}
-
@@ -257,7 +256,7 @@ export default function ChangePasswordScreen() {
-
+
)
}
diff --git a/app/generateVideo.tsx b/app/generateVideo.tsx
index a73e10d..182ace8 100644
--- a/app/generateVideo.tsx
+++ b/app/generateVideo.tsx
@@ -8,13 +8,13 @@ import {
Pressable,
StatusBar as RNStatusBar,
Platform,
- KeyboardAvoidingView,
} from 'react-native'
import { StatusBar } from 'expo-status-bar'
import { SafeAreaView } from 'react-native-safe-area-context'
import { Image } from 'expo-image'
import { useRouter, useLocalSearchParams } from 'expo-router'
import { useTranslation } from 'react-i18next'
+import { KeyboardAwareScrollView } from 'react-native-keyboard-controller'
import { LeftArrowIcon, WhitePointsIcon } from '@/components/icon'
import UploadReferenceImageDrawer from '@/components/drawer/UploadReferenceImageDrawer'
@@ -95,75 +95,69 @@ export default function GenerateVideoScreen() {
translucent
/>
)}
-
-
- {/* 顶部导航栏 */}
-
- router.back()}
- >
-
-
-
+ {/* 顶部导航栏 */}
+
+ router.back()}
+ >
+
+
+
- {/* 主要内容区域 */}
-
- {/* 模板预览卡片 */}
-
-
-
-
- {templateDetail?.title}
- {templateDetail?.description}
-
-
- {/* 价格标签 */}
-
-
- {templateDetail?.price || 10}
+ {/* 主要内容区域 */}
+
+ {/* 模板预览卡片 */}
+
+
+
+
+ {templateDetail?.title}
+ {templateDetail?.description}
-
- {/* 动态表单 */}
-
- {templateLoading ? (
-
- {t('generateVideo.loading') || '加载中...'}
-
- ) : formSchema.startNodes && formSchema.startNodes.length > 0 ? (
-
- ) : (
-
-
- {t('generateVideo.noFormFields') || '暂无可填写的表单项'}
-
-
- )}
+ {/* 价格标签 */}
+
+
+ {templateDetail?.price || 10}
-
-
+
+ {/* 动态表单 */}
+
+ {templateLoading ? (
+
+ {t('generateVideo.loading') || '加载中...'}
+
+ ) : formSchema.startNodes && formSchema.startNodes.length > 0 ? (
+
+ ) : (
+
+
+ {t('generateVideo.noFormFields') || '暂无可填写的表单项'}
+
+
+ )}
+
+
+
{/* 图片上传抽屉 */}
void
points?: number
+ /** 当外层已有滚动视图时,禁用内部滚动 */
+ disableScroll?: boolean
}
export const DynamicForm = forwardRef(
- function DynamicForm({ formSchema, onSubmit, loading = false, onOpenDrawer, points }, ref) {
+ function DynamicForm({ formSchema, onSubmit, loading = false, onOpenDrawer, points, disableScroll = false }, ref) {
const { t } = useTranslation()
const startNodes = formSchema.startNodes || []
@@ -456,55 +457,60 @@ export const DynamicForm = forwardRef(
)
}
- return (
-
-
- {startNodes.map(renderField)}
+ const formContent = (
+ <>
+ {startNodes.map(renderField)}
-
-
-
-
-
+
+
+
+ >
+ )
+
+ // 当外层已有滚动视图时,不使用内部滚动
+ if (disableScroll) {
+ return (
+
+ {formContent}
+
+ )
+ }
+
+ return (
+
+ {formContent}
+
)
}
)
const styles = StyleSheet.create({
- keyboardAvoidingView: {
- flex: 1,
- },
scrollView: {
flex: 1,
},
diff --git a/components/drawer/EditProfileDrawer.tsx b/components/drawer/EditProfileDrawer.tsx
index 36852c3..205a6a1 100644
--- a/components/drawer/EditProfileDrawer.tsx
+++ b/components/drawer/EditProfileDrawer.tsx
@@ -4,21 +4,19 @@ import {
Text,
StyleSheet,
Pressable,
- TextInput,
- Dimensions,
Platform,
Keyboard,
- ScrollView,
ActivityIndicator,
} from 'react-native'
import { Image } from 'expo-image'
-import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useTranslation } from 'react-i18next'
-import BottomSheet, { BottomSheetModal, BottomSheetView, BottomSheetBackdrop, BottomSheetScrollView } from '@gorhom/bottom-sheet'
+import { BottomSheetModal, BottomSheetBackdrop, BottomSheetTextInput, BottomSheetView } from '@gorhom/bottom-sheet'
import { CloseIcon, AvatarUploadIcon } from '@/components/icon'
import { LinearGradient } from 'expo-linear-gradient'
import { type ImagePickerAsset } from 'expo-image-picker'
import { useUpdateProfile } from '@/hooks'
+import { KeyboardAwareScrollView, useKeyboardContext, useKeyboardState } from 'react-native-keyboard-controller'
+import { TextInput } from 'react-native-gesture-handler'
interface EditProfileDrawerProps {
@@ -41,13 +39,14 @@ export default function EditProfileDrawer({
const [name, setName] = useState(initialName)
const [avatar, setAvatar] = useState(initialAvatar)
const [selectedImage, setSelectedImage] = useState(null)
- const insets = useSafeAreaInsets()
+
+ const keyboardState = useKeyboardState()
const { loading, pickImage, updateProfile } = useUpdateProfile()
// 增加高度以避免被底部 Tab 栏遮挡,考虑底部安全区域
// 使用百分比让 BottomSheet 在键盘弹出时能够自动调整
- const snapPoints = useMemo(() => ['50%'], [])
+ const snapPoints = useMemo(() => ['80%'], [])
useEffect(() => {
if (visible) {
@@ -56,11 +55,21 @@ export default function EditProfileDrawer({
setName(initialName)
setAvatar(initialAvatar)
setSelectedImage(null)
+
} else {
bottomSheetRef.current?.dismiss()
}
}, [visible, initialName, initialAvatar])
+
+ useEffect(() => {
+ if (keyboardState && keyboardState.height) {
+ bottomSheetRef.current?.expand()
+ } else {
+ bottomSheetRef.current?.collapse()
+ }
+ }, [keyboardState])
+
const handleSheetChanges = useCallback((index: number) => {
if (index === -1) {
onClose()
@@ -132,48 +141,42 @@ export default function EditProfileDrawer({
keyboardBlurBehavior="restore"
android_keyboardInputMode="adjustResize"
>
-
-
- {/* 顶部关闭按钮 */}
-
-
-
- {/* 头像区域 */}
-
-
-
-
-
- {loading ? (
-
- ) : (
-
- )}
-
-
-
+
+ {/* 顶部关闭按钮 */}
+
+
+
+ {/* 头像区域 */}
+
+
+
+
+
+ {loading ? (
+
+ ) : (
+
+ )}
+
+
+
- {/* 输入框 */}
-
+ /> */}
- {/* 保存按钮 */}
-
+
+ {/* 保存按钮 */}
+
+
-
- {loading ? (
-
- ) : (
- {t('editProfile.save')}
- )}
-
-
-
-
+ {loading ? (
+
+ ) : (
+ {t('editProfile.save')}
+ )}
+
+
+
+
)
}
@@ -222,15 +233,8 @@ const styles = StyleSheet.create({
flex: 1,
backgroundColor: '#1C1E22',
paddingHorizontal: 16,
- },
- scrollView: {
- flex: 1,
- },
- scrollContent: {
paddingTop: 32,
paddingBottom: Platform.OS === 'ios' ? 25 : 17,
- paddingHorizontal: 0,
- flexGrow: 1,
},
closeButton: {
position: 'absolute',
diff --git a/components/drawer/PointsDrawer.tsx b/components/drawer/PointsDrawer.tsx
index a83c1d1..dd3dcf6 100644
--- a/components/drawer/PointsDrawer.tsx
+++ b/components/drawer/PointsDrawer.tsx
@@ -110,17 +110,6 @@ export default function PointsDrawer({
{totalPoints}
-
- {/* 积分类型细分 */}
-
- {t('pointsDrawer.subscriptionPoints')}
- {subscriptionPoints}
-
-
- {t('pointsDrawer.topUpPoints')}
- {topUpPoints}
-
-
{/* 底部按钮 */}
diff --git a/components/drawer/TopUpDrawer.tsx b/components/drawer/TopUpDrawer.tsx
index e279710..b5b48d9 100644
--- a/components/drawer/TopUpDrawer.tsx
+++ b/components/drawer/TopUpDrawer.tsx
@@ -103,7 +103,7 @@ export default function TopUpDrawer({
// 获取余额 store 的方法
const { load: loadBalance, restartPolling } = useUserBalanceStore()
- const snapPoints = useMemo(() => [420], [])
+ const snapPoints = useMemo(() => [500], [])
useEffect(() => {
if (visible) {
diff --git a/locales/en-US.json b/locales/en-US.json
index 1996b15..2beba43 100644
--- a/locales/en-US.json
+++ b/locales/en-US.json
@@ -222,7 +222,13 @@
"agreementText": "I have read and agree to",
"terms": "Terms of Service",
"privacy": "Privacy Policy",
- "agreementAnd": "and"
+ "agreementAnd": "and",
+ "alipayNotInstalled": "Alipay SDK not installed",
+ "createOrderFailed": "Failed to create order",
+ "paymentSuccess": "Payment successful! Points are being credited...",
+ "paymentCancelled": "Payment cancelled",
+ "paymentFailed": "Payment failed, please try again",
+ "paymentError": "Payment error"
},
"tabs": {
"home": "Home",
diff --git a/locales/zh-CN.json b/locales/zh-CN.json
index 80750a8..ff33dd0 100644
--- a/locales/zh-CN.json
+++ b/locales/zh-CN.json
@@ -222,7 +222,13 @@
"agreementText": "我已阅读并同意",
"terms": "服务条款",
"privacy": "隐私协议",
- "agreementAnd": "与"
+ "agreementAnd": "与",
+ "alipayNotInstalled": "支付宝 SDK 未安装",
+ "createOrderFailed": "创建订单失败",
+ "paymentSuccess": "支付成功!积分正在到账中...",
+ "paymentCancelled": "支付已取消",
+ "paymentFailed": "支付失败,请重试",
+ "paymentError": "支付出错"
},
"tabs": {
"home": "首页",
diff --git a/stores/editProfileStore.ts b/stores/editProfileStore.ts
new file mode 100644
index 0000000..cc06320
--- /dev/null
+++ b/stores/editProfileStore.ts
@@ -0,0 +1,41 @@
+import { create } from 'zustand'
+
+interface EditProfileState {
+ visible: boolean
+ initialName: string
+ initialAvatar?: string
+ onSaveCallback?: (data: { name: string; avatar?: string }) => void
+}
+
+interface EditProfileActions {
+ open: (params: {
+ initialName?: string
+ initialAvatar?: string
+ onSave?: (data: { name: string; avatar?: string }) => void
+ }) => void
+ close: () => void
+}
+
+type EditProfileStore = EditProfileState & EditProfileActions
+
+export const useEditProfileStore = create((set) => ({
+ visible: false,
+ initialName: '',
+ initialAvatar: undefined,
+ onSaveCallback: undefined,
+
+ open: ({ initialName = '', initialAvatar, onSave }) => {
+ set({
+ visible: true,
+ initialName,
+ initialAvatar,
+ onSaveCallback: onSave,
+ })
+ },
+
+ close: () => {
+ set({
+ visible: false,
+ })
+ },
+}))