template_0205

This commit is contained in:
Sofio
2026-02-05 13:16:05 +08:00
commit d93e4d9c9f
197 changed files with 52810 additions and 0 deletions

View File

@@ -0,0 +1,216 @@
import { FC, useCallback, useState } from "react"
import { TextStyle, View, ViewStyle } from "react-native"
import { KeyboardAwareScrollView } from "react-native-keyboard-controller"
import { Button } from "@/components/Button"
import { Dialog } from "@/components/Dialog"
import { PressableIcon } from "@/components/Icon"
import { Screen } from "@/components/Screen"
import { Text } from "@/components/Text"
import { TextField } from "@/components/TextField"
import { Switch } from "@/components/Toggle/Switch"
import { useAuth } from "@/context/AuthContext"
import { translate } from "@/i18n/translate"
import { AppStackScreenProps } from "@/navigators/navigationTypes"
import { useAppTheme } from "@/theme/context"
import { $styles } from "@/theme/styles"
import type { ThemedStyle } from "@/theme/types"
import { s } from "@/utils/responsive"
import { useHeader } from "@/utils/useHeader"
export const ChangePasswordScreen: FC<AppStackScreenProps<"ChangePassword">> =
function ChangePasswordScreen({ navigation }) {
const { changePassword, isLoading, error, clearError } = useAuth()
const {
themed,
theme: { colors },
} = useAppTheme()
const [oldPassword, setOldPassword] = useState("")
const [newPassword, setNewPassword] = useState("")
const [confirmPassword, setConfirmPassword] = useState("")
const [logoutOtherDevices, setLogoutOtherDevices] = useState(false)
const [isOldPasswordHidden, setIsOldPasswordHidden] = useState(true)
const [isNewPasswordHidden, setIsNewPasswordHidden] = useState(true)
const [isConfirmPasswordHidden, setIsConfirmPasswordHidden] = useState(true)
const [localError, setLocalError] = useState("")
const [successDialogVisible, setSuccessDialogVisible] = useState(false)
const handleChangePassword = useCallback(async () => {
clearError()
setLocalError("")
// Validate
if (!oldPassword) {
setLocalError(translate("changePasswordScreen:oldPasswordRequired"))
return
}
if (!newPassword) {
setLocalError(translate("changePasswordScreen:newPasswordRequired"))
return
}
if (newPassword.length < 6) {
setLocalError(translate("changePasswordScreen:passwordTooShort"))
return
}
if (newPassword !== confirmPassword) {
setLocalError(translate("changePasswordScreen:passwordMismatch"))
return
}
if (oldPassword === newPassword) {
setLocalError(translate("changePasswordScreen:samePassword"))
return
}
const success = await changePassword(oldPassword, newPassword, logoutOtherDevices)
if (success) {
setSuccessDialogVisible(true)
}
}, [oldPassword, newPassword, confirmPassword, logoutOtherDevices, changePassword, clearError])
const displayError = localError || error
useHeader(
{
title: translate("changePasswordScreen:title"),
leftIcon: "back",
onLeftPress: () => navigation.goBack(),
},
[],
)
return (
<Screen preset="fixed" safeAreaEdges={["bottom"]} contentContainerStyle={$styles.flex1}>
<KeyboardAwareScrollView
contentContainerStyle={[$styles.container, themed($container)]}
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={false}
>
<Text tx="changePasswordScreen:description" style={themed($description)} />
<TextField
labelTx="changePasswordScreen:oldPassword"
value={oldPassword}
onChangeText={setOldPassword}
containerStyle={themed($inputContainer)}
secureTextEntry={isOldPasswordHidden}
RightAccessory={(props) => (
<PressableIcon
icon={isOldPasswordHidden ? "view" : "hidden"}
color={colors.palette.neutral800}
containerStyle={props.style}
size={s(20)}
onPress={() => setIsOldPasswordHidden(!isOldPasswordHidden)}
/>
)}
/>
<TextField
labelTx="changePasswordScreen:newPassword"
value={newPassword}
onChangeText={setNewPassword}
containerStyle={themed($inputContainer)}
secureTextEntry={isNewPasswordHidden}
RightAccessory={(props) => (
<PressableIcon
icon={isNewPasswordHidden ? "view" : "hidden"}
color={colors.palette.neutral800}
containerStyle={props.style}
size={s(20)}
onPress={() => setIsNewPasswordHidden(!isNewPasswordHidden)}
/>
)}
/>
<TextField
labelTx="changePasswordScreen:confirmPassword"
value={confirmPassword}
onChangeText={setConfirmPassword}
containerStyle={themed($inputContainer)}
secureTextEntry={isConfirmPasswordHidden}
RightAccessory={(props) => (
<PressableIcon
icon={isConfirmPasswordHidden ? "view" : "hidden"}
color={colors.palette.neutral800}
containerStyle={props.style}
size={s(20)}
onPress={() => setIsConfirmPasswordHidden(!isConfirmPasswordHidden)}
/>
)}
/>
<View style={themed($switchContainer)}>
<Text tx="changePasswordScreen:logoutOtherDevices" style={themed($switchLabel)} />
<Switch value={logoutOtherDevices} onValueChange={setLogoutOtherDevices} />
</View>
{displayError ? (
<Text size="sm" style={themed($errorText)}>
{displayError}
</Text>
) : null}
</KeyboardAwareScrollView>
{/* Fixed Bottom Button */}
<View style={themed($bottomContainer)}>
<Button
tx="changePasswordScreen:submit"
preset="reversed"
onPress={handleChangePassword}
disabled={isLoading}
loading={isLoading}
/>
</View>
{/* Success Dialog */}
<Dialog
visible={successDialogVisible}
onClose={() => {
setSuccessDialogVisible(false)
navigation.goBack()
}}
titleTx="changePasswordScreen:success"
messageTx="changePasswordScreen:successMessage"
onConfirm={() => {
setSuccessDialogVisible(false)
navigation.goBack()
}}
/>
</Screen>
)
}
const $container: ThemedStyle<ViewStyle> = ({ spacing }) => ({
paddingTop: spacing.lg, // 覆盖 $styles.container 的 56px使用 useHeader 时只需 24px
})
const $description: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({
color: colors.textDim,
marginBottom: spacing.lg,
})
const $inputContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({
marginBottom: spacing.md,
})
const $switchContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: spacing.md,
paddingVertical: spacing.sm,
})
const $switchLabel: ThemedStyle<TextStyle> = () => ({
flex: 1,
})
const $errorText: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({
color: colors.error,
marginBottom: spacing.md,
})
const $bottomContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({
paddingHorizontal: spacing.lg,
paddingBottom: spacing.md,
})