update_260201-3

This commit is contained in:
lusixing
2026-02-01 21:13:15 -08:00
parent 3ffcc60ee8
commit b5373c2d9a
11 changed files with 1327 additions and 396 deletions

View File

@@ -27,6 +27,7 @@ import { useAuth } from '../context/AuthContext';
import { useVaultAssets } from '../hooks/useVaultAssets';
import { getVaultStorageKeys } from '../config';
import { mnemonicToEntropy, splitSecret, serializeShare } from '../utils/sss';
import { SentinelVault } from '@/utils/crypto_core';
// Asset type configuration with nautical theme
const assetTypeConfig: Record<VaultAssetType, { icon: string; iconType: 'ionicons' | 'feather' | 'material' | 'fontawesome5'; label: string }> = {
@@ -210,7 +211,7 @@ export default function VaultScreen() {
return;
}
if (isUnlocked) return;
const timer = setTimeout(() => setShowBiometric(true), 500);
const timer = setTimeout(() => setShowBiometric(true), 100);
return () => clearTimeout(timer);
}, [isUnlocked, hasS0]);
@@ -218,7 +219,7 @@ export default function VaultScreen() {
if (isUnlocked) {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 600,
duration: 200,
useNativeDriver: true,
}).start();
}
@@ -230,12 +231,12 @@ export default function VaultScreen() {
Animated.sequence([
Animated.timing(pulseAnim, {
toValue: 1.05,
duration: 1500,
duration: 500,
useNativeDriver: true,
}),
Animated.timing(pulseAnim, {
toValue: 1,
duration: 1500,
duration: 500,
useNativeDriver: true,
}),
])
@@ -380,9 +381,19 @@ export default function VaultScreen() {
const entropy = mnemonicToEntropy(mnemonicWords, wordList);
const shares = splitSecret(entropy);
const s0 = shares[0]; // device share (S0)
const s1 = shares[1]; // server share (S1)
const s2 = shares[2]; // heir share (S2)
// S0 is stored in AsyncStorage under user-scoped key — app-level storage, not hardware TEE/SE
const vault = new SentinelVault()
const aes_key = await vault.deriveKey(mnemonicWords.join(' '))
await AsyncStorage.setItem(vaultKeys.SHARE_DEVICE, serializeShare(s0));
await AsyncStorage.setItem(vaultKeys.SHARE_SERVER, serializeShare(s1));
await AsyncStorage.setItem(vaultKeys.INITIALIZED, '1');
await AsyncStorage.setItem(vaultKeys.AES_KEY, aes_key.toString('hex'));
await AsyncStorage.setItem(vaultKeys.SHARE_HEIR, serializeShare(s2));
setHasS0(true);
setShowMnemonic(false);
setShowBiometric(true);
@@ -576,327 +587,327 @@ export default function VaultScreen() {
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
>
<View ref={mnemonicRef} collapsable={false}>
<LinearGradient
colors={[colors.sentinel.cardBackground, colors.sentinel.backgroundGradientEnd]}
style={styles.mnemonicCard}
>
<TouchableOpacity
style={styles.mnemonicClose}
onPress={() => setShowMnemonic(false)}
activeOpacity={0.85}
<LinearGradient
colors={[colors.sentinel.cardBackground, colors.sentinel.backgroundGradientEnd]}
style={styles.mnemonicCard}
>
<Ionicons name="close" size={18} color={colors.sentinel.textSecondary} />
</TouchableOpacity>
<View style={styles.mnemonicHeader}>
<MaterialCommunityIcons name="key-variant" size={22} color={colors.sentinel.primary} />
<Text style={styles.mnemonicTitle}>Mnemonic Setup</Text>
</View>
{mnemonicStep === 1 ? (
<>
<Text style={styles.mnemonicSubtitle}>
Review your 12-word mnemonic. Tap any word to replace it.
</Text>
<View style={styles.wordGrid}>
{mnemonicWords.map((word, index) => (
<TouchableOpacity
key={`${word}-${index}`}
style={[
styles.wordChip,
replaceIndex === index && styles.wordChipSelected,
]}
onPress={() => setReplaceIndex(index)}
activeOpacity={0.8}
>
<Text
<TouchableOpacity
style={styles.mnemonicClose}
onPress={() => setShowMnemonic(false)}
activeOpacity={0.85}
>
<Ionicons name="close" size={18} color={colors.sentinel.textSecondary} />
</TouchableOpacity>
<View style={styles.mnemonicHeader}>
<MaterialCommunityIcons name="key-variant" size={22} color={colors.sentinel.primary} />
<Text style={styles.mnemonicTitle}>Mnemonic Setup</Text>
</View>
{mnemonicStep === 1 ? (
<>
<Text style={styles.mnemonicSubtitle}>
Review your 12-word mnemonic. Tap any word to replace it.
</Text>
<View style={styles.wordGrid}>
{mnemonicWords.map((word, index) => (
<TouchableOpacity
key={`${word}-${index}`}
style={[
styles.wordChipIndex,
replaceIndex === index && styles.wordChipTextSelected,
styles.wordChip,
replaceIndex === index && styles.wordChipSelected,
]}
onPress={() => setReplaceIndex(index)}
activeOpacity={0.8}
>
{index + 1}
<Text
style={[
styles.wordChipIndex,
replaceIndex === index && styles.wordChipTextSelected,
]}
>
{index + 1}
</Text>
<Text
style={[
styles.wordChipText,
replaceIndex === index && styles.wordChipTextSelected,
]}
>
{word}
</Text>
</TouchableOpacity>
))}
</View>
{replaceIndex !== null ? (
<View style={styles.replacePanel}>
<Text style={styles.replaceTitle}>
Replace word {replaceIndex + 1}
</Text>
<Text
style={[
styles.wordChipText,
replaceIndex === index && styles.wordChipTextSelected,
]}
>
{word}
</Text>
</TouchableOpacity>
))}
</View>
{replaceIndex !== null ? (
<View style={styles.replacePanel}>
<Text style={styles.replaceTitle}>
Replace word {replaceIndex + 1}
</Text>
<TextInput
style={styles.replaceInput}
value={replaceQuery}
onChangeText={setReplaceQuery}
placeholder="Search a word"
placeholderTextColor={colors.sentinel.textSecondary}
autoCapitalize="none"
autoCorrect={false}
/>
<ScrollView style={styles.replaceList} showsVerticalScrollIndicator={false}>
{(replaceQuery
? bip39.wordlists.english.filter((word) =>
<TextInput
style={styles.replaceInput}
value={replaceQuery}
onChangeText={setReplaceQuery}
placeholder="Search a word"
placeholderTextColor={colors.sentinel.textSecondary}
autoCapitalize="none"
autoCorrect={false}
/>
<ScrollView style={styles.replaceList} showsVerticalScrollIndicator={false}>
{(replaceQuery
? bip39.wordlists.english.filter((word) =>
word.startsWith(replaceQuery.toLowerCase())
)
: bip39.wordlists.english
)
.slice(0, 24)
.map((word) => (
<TouchableOpacity
key={word}
style={styles.replaceOption}
onPress={() => handleReplaceWord(word)}
activeOpacity={0.8}
>
<Text style={styles.replaceOptionText}>{word}</Text>
</TouchableOpacity>
))}
</ScrollView>
<TouchableOpacity
style={styles.replaceCancel}
onPress={() => setReplaceIndex(null)}
activeOpacity={0.85}
>
<Text style={styles.replaceCancelText}>CANCEL</Text>
</TouchableOpacity>
</View>
) : null}
<TouchableOpacity
style={styles.mnemonicPrimaryButton}
onPress={() => setMnemonicStep(2)}
activeOpacity={0.85}
>
<Text style={styles.mnemonicPrimaryText}>NEXT</Text>
</TouchableOpacity>
</>
) : null}
: bip39.wordlists.english
)
.slice(0, 24)
.map((word) => (
<TouchableOpacity
key={word}
style={styles.replaceOption}
onPress={() => handleReplaceWord(word)}
activeOpacity={0.8}
>
<Text style={styles.replaceOptionText}>{word}</Text>
</TouchableOpacity>
))}
</ScrollView>
<TouchableOpacity
style={styles.replaceCancel}
onPress={() => setReplaceIndex(null)}
activeOpacity={0.85}
>
<Text style={styles.replaceCancelText}>CANCEL</Text>
</TouchableOpacity>
</View>
) : null}
<TouchableOpacity
style={styles.mnemonicPrimaryButton}
onPress={() => setMnemonicStep(2)}
activeOpacity={0.85}
>
<Text style={styles.mnemonicPrimaryText}>NEXT</Text>
</TouchableOpacity>
</>
) : null}
{mnemonicStep === 2 ? (
<>
<Text style={styles.mnemonicSubtitle}>
Confirm your 12-word mnemonic.
</Text>
<View style={styles.mnemonicBlock}>
<Text style={styles.mnemonicBlockText}>
{mnemonicWords.join(' ')}
{mnemonicStep === 2 ? (
<>
<Text style={styles.mnemonicSubtitle}>
Confirm your 12-word mnemonic.
</Text>
</View>
<TouchableOpacity
style={styles.mnemonicPrimaryButton}
onPress={() => setMnemonicStep(3)}
activeOpacity={0.85}
>
<Text style={styles.mnemonicPrimaryText}>CONFIRM</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.mnemonicSecondaryButton}
onPress={() => setMnemonicStep(1)}
activeOpacity={0.85}
>
<Text style={styles.mnemonicSecondaryText}>EDIT SELECTION</Text>
</TouchableOpacity>
</>
) : null}
{mnemonicStep === 3 ? (
<>
<Text style={styles.mnemonicSubtitle}>
Back up your mnemonic before entering the Vault.
</Text>
<View style={styles.mnemonicBlock}>
<Text style={styles.mnemonicBlockText}>
{mnemonicWords.join(' ')}
</Text>
</View>
<TouchableOpacity
style={[styles.mnemonicPrimaryButton, isCapturing && styles.mnemonicButtonDisabled]}
onPress={handleScreenshot}
activeOpacity={0.85}
disabled={isCapturing}
>
<Text style={styles.mnemonicPrimaryText}>
{isCapturing ? 'CAPTURING...' : 'PHYSICAL BACKUP (SCREENSHOT)'}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.mnemonicSecondaryButton}
onPress={handleEmailBackup}
activeOpacity={0.85}
>
<Text style={styles.mnemonicSecondaryText}>EMAIL BACKUP</Text>
</TouchableOpacity>
</>
) : null}
{mnemonicStep === 4 ? (
<>
<Text style={styles.mnemonicSubtitle}>
Finalizing your vault protection.
</Text>
<View style={styles.progressContainer}>
<View style={styles.progressTrack}>
<Animated.View
style={[
styles.progressFill,
{
width: progressAnim.interpolate({
inputRange: [0, 1],
outputRange: ['0%', '100%'],
}),
},
]}
/>
</View>
</View>
<View style={styles.progressSteps}>
<Text style={styles.progressText}>
{progressIndex === 0 && '1. Your key is being processed'}
{progressIndex === 1 && '2. Your key has been split'}
{progressIndex === 2 && '3. Part one stored on this device'}
{progressIndex === 3 && '4. Part two uploaded to the cloud'}
{progressIndex >= 4 && '5. Part three inquiry initiated'}
</Text>
</View>
</>
) : null}
{mnemonicStep === 5 ? (
<>
{heirStep === 'decision' ? (
<>
<Text style={styles.mnemonicSubtitle}>
Share Part Three with your legacy handler?
<View style={styles.mnemonicBlock}>
<Text style={styles.mnemonicBlockText}>
{mnemonicWords.join(' ')}
</Text>
<TouchableOpacity
style={styles.mnemonicPrimaryButton}
onPress={handleHeirYes}
activeOpacity={0.85}
>
<Text style={styles.mnemonicPrimaryText}>YES, SEND</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.mnemonicSecondaryButton}
onPress={handleHeirNo}
activeOpacity={0.85}
>
<Text style={styles.mnemonicSecondaryText}>NOT NOW</Text>
</TouchableOpacity>
</>
) : null}
</View>
<TouchableOpacity
style={styles.mnemonicPrimaryButton}
onPress={() => setMnemonicStep(3)}
activeOpacity={0.85}
>
<Text style={styles.mnemonicPrimaryText}>CONFIRM</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.mnemonicSecondaryButton}
onPress={() => setMnemonicStep(1)}
activeOpacity={0.85}
>
<Text style={styles.mnemonicSecondaryText}>EDIT SELECTION</Text>
</TouchableOpacity>
</>
) : null}
{heirStep === 'asset' ? (
<>
<Text style={styles.mnemonicSubtitle}>
Select the vault item to assign.
{mnemonicStep === 3 ? (
<>
<Text style={styles.mnemonicSubtitle}>
Back up your mnemonic before entering the Vault.
</Text>
<View style={styles.mnemonicBlock}>
<Text style={styles.mnemonicBlockText}>
{mnemonicWords.join(' ')}
</Text>
<ScrollView style={styles.selectorList} showsVerticalScrollIndicator={false}>
{assets.map((asset) => {
const config = assetTypeConfig[asset.type];
return (
</View>
<TouchableOpacity
style={[styles.mnemonicPrimaryButton, isCapturing && styles.mnemonicButtonDisabled]}
onPress={handleScreenshot}
activeOpacity={0.85}
disabled={isCapturing}
>
<Text style={styles.mnemonicPrimaryText}>
{isCapturing ? 'CAPTURING...' : 'PHYSICAL BACKUP (SCREENSHOT)'}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.mnemonicSecondaryButton}
onPress={handleEmailBackup}
activeOpacity={0.85}
>
<Text style={styles.mnemonicSecondaryText}>EMAIL BACKUP</Text>
</TouchableOpacity>
</>
) : null}
{mnemonicStep === 4 ? (
<>
<Text style={styles.mnemonicSubtitle}>
Finalizing your vault protection.
</Text>
<View style={styles.progressContainer}>
<View style={styles.progressTrack}>
<Animated.View
style={[
styles.progressFill,
{
width: progressAnim.interpolate({
inputRange: [0, 1],
outputRange: ['0%', '100%'],
}),
},
]}
/>
</View>
</View>
<View style={styles.progressSteps}>
<Text style={styles.progressText}>
{progressIndex === 0 && '1. Your key is being processed'}
{progressIndex === 1 && '2. Your key has been split'}
{progressIndex === 2 && '3. Part one stored on this device'}
{progressIndex === 3 && '4. Part two uploaded to the cloud'}
{progressIndex >= 4 && '5. Part three inquiry initiated'}
</Text>
</View>
</>
) : null}
{mnemonicStep === 5 ? (
<>
{heirStep === 'decision' ? (
<>
<Text style={styles.mnemonicSubtitle}>
Share Part Three with your legacy handler?
</Text>
<TouchableOpacity
style={styles.mnemonicPrimaryButton}
onPress={handleHeirYes}
activeOpacity={0.85}
>
<Text style={styles.mnemonicPrimaryText}>YES, SEND</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.mnemonicSecondaryButton}
onPress={handleHeirNo}
activeOpacity={0.85}
>
<Text style={styles.mnemonicSecondaryText}>NOT NOW</Text>
</TouchableOpacity>
</>
) : null}
{heirStep === 'asset' ? (
<>
<Text style={styles.mnemonicSubtitle}>
Select the vault item to assign.
</Text>
<ScrollView style={styles.selectorList} showsVerticalScrollIndicator={false}>
{assets.map((asset) => {
const config = assetTypeConfig[asset.type];
return (
<TouchableOpacity
key={asset.id}
style={styles.selectorRow}
onPress={() => handleSelectHeirAsset(asset)}
activeOpacity={0.8}
>
<View style={styles.selectorIcon}>
{renderAssetTypeIcon(config, 18, colors.vault.primary)}
</View>
<View style={styles.selectorContent}>
<Text style={styles.selectorTitle}>{asset.label}</Text>
<Text style={styles.selectorSubtitle}>{config.label}</Text>
</View>
</TouchableOpacity>
);
})}
</ScrollView>
<TouchableOpacity
style={styles.mnemonicSecondaryButton}
onPress={() => setHeirStep('decision')}
activeOpacity={0.85}
>
<Text style={styles.mnemonicSecondaryText}>BACK</Text>
</TouchableOpacity>
</>
) : null}
{heirStep === 'heir' ? (
<>
<Text style={styles.mnemonicSubtitle}>
Choose a legacy handler.
</Text>
<ScrollView style={styles.selectorList} showsVerticalScrollIndicator={false}>
{initialHeirs.map((heir) => (
<TouchableOpacity
key={asset.id}
key={heir.id}
style={styles.selectorRow}
onPress={() => handleSelectHeirAsset(asset)}
onPress={() => handleSelectHeir(heir)}
activeOpacity={0.8}
>
<View style={styles.selectorIcon}>
{renderAssetTypeIcon(config, 18, colors.vault.primary)}
<Ionicons name="person-circle" size={20} color={colors.sentinel.primary} />
</View>
<View style={styles.selectorContent}>
<Text style={styles.selectorTitle}>{asset.label}</Text>
<Text style={styles.selectorSubtitle}>{config.label}</Text>
<Text style={styles.selectorTitle}>{heir.name}</Text>
<Text style={styles.selectorSubtitle}>{heir.email}</Text>
</View>
</TouchableOpacity>
);
})}
</ScrollView>
<TouchableOpacity
style={styles.mnemonicSecondaryButton}
onPress={() => setHeirStep('decision')}
activeOpacity={0.85}
>
<Text style={styles.mnemonicSecondaryText}>BACK</Text>
</TouchableOpacity>
</>
) : null}
))}
</ScrollView>
<TouchableOpacity
style={styles.mnemonicSecondaryButton}
onPress={() => setHeirStep('asset')}
activeOpacity={0.85}
>
<Text style={styles.mnemonicSecondaryText}>BACK</Text>
</TouchableOpacity>
</>
) : null}
{heirStep === 'heir' ? (
<>
<Text style={styles.mnemonicSubtitle}>
Choose a legacy handler.
</Text>
<ScrollView style={styles.selectorList} showsVerticalScrollIndicator={false}>
{initialHeirs.map((heir) => (
<TouchableOpacity
key={heir.id}
style={styles.selectorRow}
onPress={() => handleSelectHeir(heir)}
activeOpacity={0.8}
>
<View style={styles.selectorIcon}>
<Ionicons name="person-circle" size={20} color={colors.sentinel.primary} />
</View>
<View style={styles.selectorContent}>
<Text style={styles.selectorTitle}>{heir.name}</Text>
<Text style={styles.selectorSubtitle}>{heir.email}</Text>
</View>
</TouchableOpacity>
))}
</ScrollView>
<TouchableOpacity
style={styles.mnemonicSecondaryButton}
onPress={() => setHeirStep('asset')}
activeOpacity={0.85}
>
<Text style={styles.mnemonicSecondaryText}>BACK</Text>
</TouchableOpacity>
</>
) : null}
{heirStep === 'summary' ? (
<>
<Text style={styles.mnemonicSubtitle}>
Confirm assignment details.
</Text>
<View style={styles.summaryCard}>
<Text style={styles.summaryLabel}>Vault Item</Text>
<Text style={styles.summaryValue}>{selectedHeirAsset?.label}</Text>
<Text style={styles.summaryLabel}>Legacy Handler</Text>
<Text style={styles.summaryValue}>{selectedHeir?.name}</Text>
<Text style={styles.summaryValue}>{selectedHeir?.email}</Text>
<Text style={styles.summaryLabel}>Release Tier</Text>
<Text style={styles.summaryValue}>Tier {selectedHeir?.releaseLevel}</Text>
</View>
<TouchableOpacity
style={styles.mnemonicPrimaryButton}
onPress={handleSubmitAssignment}
activeOpacity={0.85}
>
<Text style={styles.mnemonicPrimaryText}>SUBMIT</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.mnemonicSecondaryButton}
onPress={() => setHeirStep('heir')}
activeOpacity={0.85}
>
<Text style={styles.mnemonicSecondaryText}>EDIT</Text>
</TouchableOpacity>
</>
) : null}
</>
) : null}
<View style={styles.stepDots}>
<View style={[styles.stepDot, mnemonicStep === 1 && styles.stepDotActive]} />
<View style={[styles.stepDot, mnemonicStep !== 1 && styles.stepDotActive]} />
</View>
</LinearGradient>
{heirStep === 'summary' ? (
<>
<Text style={styles.mnemonicSubtitle}>
Confirm assignment details.
</Text>
<View style={styles.summaryCard}>
<Text style={styles.summaryLabel}>Vault Item</Text>
<Text style={styles.summaryValue}>{selectedHeirAsset?.label}</Text>
<Text style={styles.summaryLabel}>Legacy Handler</Text>
<Text style={styles.summaryValue}>{selectedHeir?.name}</Text>
<Text style={styles.summaryValue}>{selectedHeir?.email}</Text>
<Text style={styles.summaryLabel}>Release Tier</Text>
<Text style={styles.summaryValue}>Tier {selectedHeir?.releaseLevel}</Text>
</View>
<TouchableOpacity
style={styles.mnemonicPrimaryButton}
onPress={handleSubmitAssignment}
activeOpacity={0.85}
>
<Text style={styles.mnemonicPrimaryText}>SUBMIT</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.mnemonicSecondaryButton}
onPress={() => setHeirStep('heir')}
activeOpacity={0.85}
>
<Text style={styles.mnemonicSecondaryText}>EDIT</Text>
</TouchableOpacity>
</>
) : null}
</>
) : null}
<View style={styles.stepDots}>
<View style={[styles.stepDot, mnemonicStep === 1 && styles.stepDotActive]} />
<View style={[styles.stepDot, mnemonicStep !== 1 && styles.stepDotActive]} />
</View>
</LinearGradient>
</View>
</KeyboardAvoidingView>
</Modal>
@@ -904,60 +915,60 @@ export default function VaultScreen() {
// Lock screen
const lockScreen = (
<View style={styles.lockContainer}>
<LinearGradient
colors={[colors.vault.backgroundGradientStart, colors.vault.backgroundGradientEnd]}
style={styles.lockGradient}
>
<SafeAreaView style={styles.lockSafeArea}>
<View style={styles.lockContent}>
<Animated.View style={[styles.lockIconContainer, { transform: [{ scale: pulseAnim }] }]}>
<LinearGradient
colors={[colors.nautical.teal, colors.nautical.deepTeal]}
style={styles.lockIconGradient}
>
<MaterialCommunityIcons name="treasure-chest" size={64} color={colors.vault.primary} />
</LinearGradient>
</Animated.View>
<Text style={styles.lockTitle}>THE DEEP VAULT</Text>
<Text style={styles.lockSubtitle}>Where treasures rest in silence</Text>
<View style={styles.waveContainer}>
<MaterialCommunityIcons name="waves" size={48} color={colors.vault.secondary} style={{ opacity: 0.3 }} />
</View>
<TouchableOpacity
style={styles.unlockButton}
onPress={hasS0 ? () => setShowBiometric(true) : handleUnlock}
activeOpacity={0.8}
<View style={styles.lockContainer}>
<LinearGradient
colors={[colors.vault.backgroundGradientStart, colors.vault.backgroundGradientEnd]}
style={styles.lockGradient}
>
<SafeAreaView style={styles.lockSafeArea}>
<View style={styles.lockContent}>
<Animated.View style={[styles.lockIconContainer, { transform: [{ scale: pulseAnim }] }]}>
<LinearGradient
colors={[colors.nautical.teal, colors.nautical.deepTeal]}
style={styles.lockIconGradient}
>
<LinearGradient
colors={[colors.vault.primary, colors.vault.secondary]}
style={styles.unlockButtonGradient}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
>
<Ionicons name={hasS0 ? 'finger-print' : 'key'} size={20} color={colors.vault.background} />
<Text style={styles.unlockButtonText}>
{hasS0 === true ? "Captain's Verification" : hasS0 === false ? 'Enter Vault' : 'Loading…'}
</Text>
</LinearGradient>
</TouchableOpacity>
</View>
</SafeAreaView>
</LinearGradient>
<MaterialCommunityIcons name="treasure-chest" size={64} color={colors.vault.primary} />
</LinearGradient>
</Animated.View>
<BiometricModal
visible={hasS0 === true && showBiometric}
onSuccess={handleBiometricSuccess}
onCancel={() => setShowBiometric(false)}
title="Enter the Vault"
message="Verify your identity to access your treasures"
isDark
/>
</View>
);
<Text style={styles.lockTitle}>THE DEEP VAULT</Text>
<Text style={styles.lockSubtitle}>Where treasures rest in silence</Text>
<View style={styles.waveContainer}>
<MaterialCommunityIcons name="waves" size={48} color={colors.vault.secondary} style={{ opacity: 0.3 }} />
</View>
<TouchableOpacity
style={styles.unlockButton}
onPress={hasS0 ? () => setShowBiometric(true) : handleUnlock}
activeOpacity={0.8}
>
<LinearGradient
colors={[colors.vault.primary, colors.vault.secondary]}
style={styles.unlockButtonGradient}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
>
<Ionicons name={hasS0 ? 'finger-print' : 'key'} size={20} color={colors.vault.background} />
<Text style={styles.unlockButtonText}>
{hasS0 === true ? "Captain's Verification" : hasS0 === false ? 'Enter Vault' : 'Loading…'}
</Text>
</LinearGradient>
</TouchableOpacity>
</View>
</SafeAreaView>
</LinearGradient>
<BiometricModal
visible={hasS0 === true && showBiometric}
onSuccess={handleBiometricSuccess}
onCancel={() => setShowBiometric(false)}
title="Enter the Vault"
message="Verify your identity to access your treasures"
isDark
/>
</View>
);
const vaultScreen = (
<View style={styles.container}>
@@ -1003,7 +1014,7 @@ export default function VaultScreen() {
)}
{/* Asset List */}
<ScrollView
<ScrollView
style={styles.assetList}
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.assetListContent}
@@ -1123,8 +1134,8 @@ export default function VaultScreen() {
onChangeText={setNewLabel}
/>
<Text style={styles.modalLabel}>TREASURE TYPE</Text>
<ScrollView
horizontal
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.typeScroll}
contentContainerStyle={styles.typeScrollContent}