update_260201-3
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user