/** * WiFi Password Input Component * * Provides real-time validation feedback as user types password. * Shows password strength indicator and validation errors. */ import React, { useState, useCallback, useEffect } from 'react'; import { View, TextInput, Text, TouchableOpacity, StyleSheet, Animated, } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { AppColors, BorderRadius, FontSizes, FontWeights, Spacing, } from '@/constants/theme'; import { validateRealTime, type RealTimeValidationState, } from '@/utils/wifiValidation'; interface WiFiPasswordInputProps { value: string; onChangeText: (text: string) => void; ssid: string; authType?: string; onValidationChange?: (isValid: boolean) => void; editable?: boolean; placeholder?: string; autoFocus?: boolean; } export function WiFiPasswordInput({ value, onChangeText, ssid, authType, onValidationChange, editable = true, placeholder = 'Enter WiFi password', autoFocus = false, }: WiFiPasswordInputProps) { const [showPassword, setShowPassword] = useState(false); const [validation, setValidation] = useState(null); const [isFocused, setIsFocused] = useState(false); // Animated value for password strength bar const strengthAnim = useState(new Animated.Value(0))[0]; // Perform real-time validation as user types const handleTextChange = useCallback((text: string) => { onChangeText(text); // Only validate if there's input if (text.length > 0 || ssid.length > 0) { const result = validateRealTime(ssid, text, authType); setValidation(result); // Animate strength bar let strengthValue = 0; if (result.passwordStrength === 'weak') strengthValue = 0.33; else if (result.passwordStrength === 'medium') strengthValue = 0.66; else if (result.passwordStrength === 'strong') strengthValue = 1; Animated.timing(strengthAnim, { toValue: strengthValue, duration: 200, useNativeDriver: false, }).start(); // Notify parent of validation state onValidationChange?.(result.canSubmit); } else { setValidation(null); onValidationChange?.(false); } }, [ssid, authType, onValidationChange, strengthAnim, onChangeText]); // Re-validate when ssid or authType changes useEffect(() => { if (value.length > 0) { const result = validateRealTime(ssid, value, authType); setValidation(result); onValidationChange?.(result.canSubmit); } }, [ssid, authType]); // Get strength bar color const getStrengthColor = () => { if (!validation?.passwordStrength) return AppColors.border; switch (validation.passwordStrength) { case 'weak': return AppColors.error; case 'medium': return AppColors.warning; case 'strong': return AppColors.success; default: return AppColors.border; } }; // Get strength label const getStrengthLabel = () => { if (!validation?.passwordStrength) return null; switch (validation.passwordStrength) { case 'weak': return 'Weak'; case 'medium': return 'Medium'; case 'strong': return 'Strong'; default: return null; } }; const isOpenNetwork = authType === 'Open'; return ( {/* Password Input */} setIsFocused(true)} onBlur={() => setIsFocused(false)} /> setShowPassword(!showPassword)} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} > {/* Password Strength Bar (only for secured networks) */} {!isOpenNetwork && value.length > 0 && ( {getStrengthLabel() && ( {getStrengthLabel()} )} )} {/* Validation Error */} {validation?.passwordError && ( {validation.passwordError} )} {/* Validation Warning (first warning only) */} {!validation?.passwordError && validation?.warnings && validation.warnings.length > 0 && ( {validation.warnings[0]} )} {/* Password Requirements Hint */} {!isOpenNetwork && value.length === 0 && ( Password must be 8-63 characters )} {/* Character Count */} {!isOpenNetwork && value.length > 0 && value.length < 8 && ( {value.length}/8 characters minimum )} ); } const styles = StyleSheet.create({ container: { width: '100%', }, inputContainer: { flexDirection: 'row', alignItems: 'center', backgroundColor: AppColors.background, borderRadius: BorderRadius.md, borderWidth: 1, borderColor: AppColors.border, }, inputContainerFocused: { borderColor: AppColors.primary, borderWidth: 2, }, inputContainerError: { borderColor: AppColors.error, }, inputIcon: { marginLeft: Spacing.md, }, input: { flex: 1, paddingVertical: Spacing.sm, paddingHorizontal: Spacing.sm, fontSize: FontSizes.base, color: AppColors.textPrimary, }, toggleButton: { padding: Spacing.md, }, strengthContainer: { flexDirection: 'row', alignItems: 'center', marginTop: Spacing.xs, gap: Spacing.sm, }, strengthBarBackground: { flex: 1, height: 4, backgroundColor: AppColors.border, borderRadius: 2, overflow: 'hidden', }, strengthBar: { height: '100%', borderRadius: 2, }, strengthLabel: { fontSize: FontSizes.xs, fontWeight: FontWeights.medium, minWidth: 50, }, errorContainer: { flexDirection: 'row', alignItems: 'center', gap: Spacing.xs, marginTop: Spacing.xs, }, errorText: { fontSize: FontSizes.sm, color: AppColors.error, flex: 1, }, warningContainer: { flexDirection: 'row', alignItems: 'center', gap: Spacing.xs, marginTop: Spacing.xs, }, warningText: { fontSize: FontSizes.sm, color: AppColors.warning, flex: 1, }, hintText: { fontSize: FontSizes.xs, color: AppColors.textMuted, marginTop: Spacing.xs, fontStyle: 'italic', }, charCount: { fontSize: FontSizes.xs, color: AppColors.textMuted, marginTop: Spacing.xs, }, }); export default WiFiPasswordInput;