diff --git a/App.tsx b/App.tsx index 80bd0e57f..37a208c35 100644 --- a/App.tsx +++ b/App.tsx @@ -1,26 +1,41 @@ import { NavigationContainer } from '@react-navigation/native'; import React from 'react'; +import { useColorScheme } from 'react-native'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { SizeClassProvider } from './components/Context/SizeClassProvider'; import { SettingsProvider } from './components/Context/SettingsProvider'; -import { BlueDefaultTheme } from './components/themes'; +import { BlueCurrentTheme, BlueDarkTheme, BlueDefaultTheme } from './components/themes'; import MasterView from './navigation/MasterView'; import { navigationRef } from './NavigationService'; import { useLogger } from '@react-navigation/devtools'; import { StorageProvider } from './components/Context/StorageProvider'; import { initializeIndexer } from './blue_modules/SilentPaymentIndexer'; +import CustomAlert, { CustomAlertHandle } from './components/CustomAlert'; +import { setCustomAlertRef } from './components/Alert'; const App = () => { - initializeIndexer({ - baseUrl: 'https://cushionlike-isabel-retrievable.ngrok-free.dev/', - timeout: 100000, // 100 seconds for blockchain scanning operations (increased for slower connections) - }); + const customAlertRef = React.useRef(null); + + React.useEffect(() => { + setCustomAlertRef(customAlertRef.current); + initializeIndexer({ + baseUrl: 'https://cushionlike-isabel-retrievable.ngrok-free.dev/', + timeout: 100000, // 100 seconds for blockchain scanning operations (increased for slower connections) + }); + return () => setCustomAlertRef(null); + }, []); + + const colorScheme = useColorScheme(); + + React.useEffect(() => { + BlueCurrentTheme.updateColorScheme(); + }, [colorScheme]); useLogger(navigationRef); return ( - + @@ -29,6 +44,7 @@ const App = () => { + ); }; diff --git a/components/Alert.ts b/components/Alert.ts index 8c2bbc0f2..0147cf0fc 100644 --- a/components/Alert.ts +++ b/components/Alert.ts @@ -2,12 +2,19 @@ import { Alert as RNAlert, Platform, ToastAndroid, AlertButton, AlertOptions } f import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback'; import loc from '../loc'; import { navigationRef } from '../NavigationService'; +import type { CustomAlertHandle } from './CustomAlert'; export enum AlertType { Alert, Toast, } +let customAlertRef: CustomAlertHandle | null = null; + +export function setCustomAlertRef(ref: CustomAlertHandle | null): void { + customAlertRef = ref; +} + const presentAlert = (() => { let lastAlertParams: { title?: string; @@ -25,6 +32,8 @@ const presentAlert = (() => { const showAlert = (title: string | undefined, message: string, buttons: AlertButton[], options: AlertOptions) => { if (Platform.OS === 'ios' && navigationRef.isReady()) { RNAlert.alert(title ?? message, title && message ? message : undefined, buttons, options); + } else if (Platform.OS === 'android' && customAlertRef) { + customAlertRef.show(title ?? message, title && message ? message : undefined, buttons, options); } else { RNAlert.alert(title ?? '', message, buttons, options); } diff --git a/components/CustomAlert.tsx b/components/CustomAlert.tsx new file mode 100644 index 000000000..20e046b6f --- /dev/null +++ b/components/CustomAlert.tsx @@ -0,0 +1,119 @@ +import React, { useCallback, useImperativeHandle, useState, forwardRef } from 'react'; +import { Modal, View, Text, TouchableOpacity, StyleSheet, AlertButton, AlertOptions, Appearance } from 'react-native'; +import { BlueDefaultTheme, BlueDarkTheme } from './themes'; + +export interface CustomAlertHandle { + show: (title: string | undefined, message: string | undefined, buttons: AlertButton[], options: AlertOptions) => void; +} + +const CustomAlert = forwardRef((_, ref) => { + const [visible, setVisible] = useState(false); + const [title, setTitle] = useState(); + const [message, setMessage] = useState(''); + const [buttons, setButtons] = useState([]); + const [options, setOptions] = useState({}); + + const isDark = Appearance.getColorScheme() === 'dark'; + const colors = isDark ? BlueDarkTheme.colors : BlueDefaultTheme.colors; + + const dismiss = useCallback(() => setVisible(false), []); + + useImperativeHandle(ref, () => ({ + show: (t, m, b, o) => { + setTitle(t); + setMessage(m ?? ''); + setButtons(b); + setOptions(o); + setVisible(true); + }, + })); + + const handlePress = useCallback( + (onPress?: () => void) => { + dismiss(); + onPress?.(); + }, + [dismiss], + ); + + const getButtonTextStyle = (style?: string) => { + switch (style) { + case 'destructive': + return { color: colors.redText, fontWeight: '600' as const }; + case 'cancel': + return { color: colors.foregroundColor, fontWeight: '600' as const }; + default: + return { color: colors.primary }; + } + }; + + return ( + + + + {title ? {title} : null} + {message} + + {buttons.map((button, index) => ( + 0 && { borderLeftWidth: StyleSheet.hairlineWidth, borderLeftColor: colors.lightBorder }, + ]} + onPress={() => handlePress(button.onPress ?? undefined)} + > + {button.text} + + ))} + + + + + ); +}); + +const styles = StyleSheet.create({ + overlay: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + justifyContent: 'center', + alignItems: 'center', + }, + container: { + width: '80%', + borderRadius: 14, + paddingTop: 20, + overflow: 'hidden', + }, + title: { + fontSize: 17, + fontWeight: '600', + textAlign: 'center', + marginBottom: 4, + paddingHorizontal: 20, + }, + message: { + fontSize: 14, + textAlign: 'center', + paddingHorizontal: 20, + paddingBottom: 20, + }, + buttonRow: { + flexDirection: 'row', + borderTopWidth: StyleSheet.hairlineWidth, + }, + button: { + flex: 1, + paddingVertical: 12, + alignItems: 'center', + justifyContent: 'center', + }, + buttonText: { + fontSize: 16, + }, +}); + +export default CustomAlert; diff --git a/components/themes.ts b/components/themes.ts index de9f2bbd8..483e21540 100644 --- a/components/themes.ts +++ b/components/themes.ts @@ -90,7 +90,7 @@ export const BlueDarkTheme: Theme = { background: '#000000', foregroundColor: '#ffffff', buttonDisabledBackgroundColor: '#3A3A3C', - buttonBackgroundColor: '#FF9500', + buttonBackgroundColor: '#754CE8', buttonTextColor: '#ffffff', lightButton: 'rgba(255,255,255,.1)', buttonAlternativeTextColor: '#ffffff', @@ -129,6 +129,8 @@ export const BlueDarkTheme: Theme = { receiveText: '#37C0A1', navigationBarColor: '#3A3A3C', androidRippleColor: '#444444', + primary: '#754CE8', + secondary: '#472EBF', }, }; diff --git a/loc/en.json b/loc/en.json index f8a980b96..9109d4bda 100644 --- a/loc/en.json +++ b/loc/en.json @@ -411,11 +411,12 @@ "details_address": "Address", "details_advanced": "Advanced", "details_are_you_sure": "Are you sure?", + "delete_wallet_warning": "You'll need your seed phrase to restore access.", "details_connected_to": "Connected to", "details_del_wb_err": "The provided balance amount does not match this wallet’s balance. Please try again.", "details_del_wb_q": "This wallet has a balance. Before proceeding, please be aware that you will not be able to recover the funds without this wallet’s seed phrase. In order to avoid accidental removal, please enter your wallet’s balance of {balance} satoshis.", "details_delete": "Delete", - "details_delete_wallet": "Delete Wallet", + "details_delete_wallet": "⚠️ Delete Wallet?", "details_derivation_path": "derivation path", "details_display": "Display in Home Screen", "details_export_backup": "Export/Backup", @@ -427,7 +428,7 @@ "details_title": "Wallet", "details_type": "Type", "details_use_with_hardware_wallet": "Use with Hardware Wallet", - "details_yes_delete": "Yes, delete", + "details_yes_delete": "Delete", "enter_bip38_password": "Enter password to decrypt", "export_title": "Wallet Export", "import_do_import": "Import", @@ -465,6 +466,7 @@ "list_title": "Wallets", "list_tryagain": "Try again", "no_ln_wallet_error": "Before paying a Lightning invoice, you must first add a Lightning wallet.", + "no_wallet_to_delete": "No wallet available to delete", "looks_like_bip38": "This looks like a password-protected private key (BIP38).", "manage_title": "Manage Wallets", "no_results_found": "No results found.", diff --git a/navigation/LazyLoadingIndicator.tsx b/navigation/LazyLoadingIndicator.tsx index 177df8631..34d222fc6 100644 --- a/navigation/LazyLoadingIndicator.tsx +++ b/navigation/LazyLoadingIndicator.tsx @@ -3,7 +3,7 @@ import { ActivityIndicator, StyleSheet, View } from 'react-native'; export const LazyLoadingIndicator = () => ( - + ); diff --git a/screen/UnlockWith.tsx b/screen/UnlockWith.tsx index 77c7829bc..5b02ae4b4 100644 --- a/screen/UnlockWith.tsx +++ b/screen/UnlockWith.tsx @@ -130,7 +130,7 @@ const UnlockWith: React.FC = () => { const renderUnlockOptions = () => { if (state.isAuthenticating) { - return ; + return ; } else { switch (state.auth.type) { case AuthType.Biometrics: diff --git a/screen/settings/DeleteWallet.tsx b/screen/settings/DeleteWallet.tsx index 79ddfa717..5f0a26334 100644 --- a/screen/settings/DeleteWallet.tsx +++ b/screen/settings/DeleteWallet.tsx @@ -1,5 +1,4 @@ import React, { useCallback } from 'react'; -import { Alert } from 'react-native'; import ListItem from '../../components/ListItem'; import { useStorage } from '../../hooks/context/useStorage'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; @@ -8,6 +7,7 @@ import { useExtendedNavigation } from '../../hooks/useExtendedNavigation'; import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { CommonActions } from '@react-navigation/native'; +import presentAlert from '../../components/Alert'; type NavigationProps = NativeStackNavigationProp; @@ -18,14 +18,14 @@ const DeleteWallet: React.FC = () => { const handleDeleteWallet = useCallback(async () => { const wallet = wallets.length > 0 ? wallets[0] : null; if (!wallet) { - Alert.alert(loc.wallets.list_empty_txs1, 'No wallet available to delete'); + presentAlert({ title: loc.wallets.list_empty_txs1, message: loc.wallets.no_wallet_to_delete }); return; } - Alert.alert( - loc.wallets.details_delete_wallet, - loc.wallets.details_are_you_sure, - [ + presentAlert({ + title: loc.wallets.details_delete_wallet, + message: loc.wallets.delete_wallet_warning, + buttons: [ { text: loc._.cancel, style: 'cancel', @@ -47,8 +47,8 @@ const DeleteWallet: React.FC = () => { }, }, ], - { cancelable: false }, - ); + options: { cancelable: false }, + }); }, [wallets, handleWalletDeletion, dispatch]); if (wallets.length === 0) { diff --git a/screen/wallets/ImportWallet.tsx b/screen/wallets/ImportWallet.tsx index 4ae497766..0c54766af 100644 --- a/screen/wallets/ImportWallet.tsx +++ b/screen/wallets/ImportWallet.tsx @@ -308,6 +308,7 @@ const ImportWallet = () => { const styles = StyleSheet.create({ root: { paddingTop: 10, + flexGrow: 1, }, center: { flex: 1, diff --git a/screen/wallets/WalletDetails.tsx b/screen/wallets/WalletDetails.tsx index 120a2cbae..b32f9d370 100644 --- a/screen/wallets/WalletDetails.tsx +++ b/screen/wallets/WalletDetails.tsx @@ -129,7 +129,7 @@ const WalletDetails: React.FC = () => { triggerHapticFeedback(HapticFeedbackTypes.NotificationWarning); presentAlert({ title: loc.wallets.details_delete_wallet, - message: loc.wallets.details_are_you_sure, + message: loc.wallets.delete_wallet_warning, buttons: [ { text: loc.wallets.details_yes_delete,