From 5b1195b32555ba2334d262b550bcc78db090ba90 Mon Sep 17 00:00:00 2001 From: chaitika Date: Wed, 11 Mar 2026 14:17:01 +0530 Subject: [PATCH 1/2] UI fixes: for dark mode theme, custom alert styling, spinner colors, and import screen layout --- App.tsx | 16 +++- components/Alert.ts | 11 ++- components/CustomAlert.tsx | 117 ++++++++++++++++++++++++++++ components/themes.ts | 4 +- loc/en.json | 6 +- navigation/LazyLoadingIndicator.tsx | 2 +- screen/UnlockWith.tsx | 2 +- screen/settings/DeleteWallet.tsx | 16 ++-- screen/wallets/ImportWallet.tsx | 1 + 9 files changed, 158 insertions(+), 17 deletions(-) create mode 100644 components/CustomAlert.tsx diff --git a/App.tsx b/App.tsx index 80bd0e57f..c1d23b5e6 100644 --- a/App.tsx +++ b/App.tsx @@ -1,26 +1,37 @@ 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 { 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 = () => { + const customAlertRef = React.useRef(null); + + React.useEffect(() => { + setCustomAlertRef(customAlertRef.current); + return () => setCustomAlertRef(null); + }, []); initializeIndexer({ baseUrl: 'https://cushionlike-isabel-retrievable.ngrok-free.dev/', timeout: 100000, // 100 seconds for blockchain scanning operations (increased for slower connections) }); + const colorScheme = useColorScheme(); + useLogger(navigationRef); return ( - + @@ -29,6 +40,7 @@ const App = () => { + ); }; diff --git a/components/Alert.ts b/components/Alert.ts index 8c2bbc0f2..df3f7a864 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,7 +32,9 @@ 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 { + } else if (Platform.OS === 'android' && customAlertRef) { + customAlertRef.show(title, message, 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..ed7ad6546 --- /dev/null +++ b/components/CustomAlert.tsx @@ -0,0 +1,117 @@ +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, 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.mainColor }; + } + }; + + 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..f448f2a62 100644 --- a/loc/en.json +++ b/loc/en.json @@ -410,12 +410,12 @@ "clear_clipboard_on_import": "Clear clipboard on import", "details_address": "Address", "details_advanced": "Advanced", - "details_are_you_sure": "Are you sure?", + "details_are_you_sure": "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 +427,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", 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..5e0e0de73 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: 'No wallet available 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.details_are_you_sure, + 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, From 0dde54626b191880ba5cb07b1f9281916d8d92d7 Mon Sep 17 00:00:00 2001 From: chaitika Date: Mon, 16 Mar 2026 14:47:03 +0530 Subject: [PATCH 2/2] address copilot reviews on PR --- App.tsx | 14 +++++++++----- components/Alert.ts | 4 ++-- components/CustomAlert.tsx | 8 +++++--- loc/en.json | 6 ++++-- screen/settings/DeleteWallet.tsx | 4 ++-- screen/wallets/WalletDetails.tsx | 2 +- 6 files changed, 23 insertions(+), 15 deletions(-) diff --git a/App.tsx b/App.tsx index c1d23b5e6..37a208c35 100644 --- a/App.tsx +++ b/App.tsx @@ -4,7 +4,7 @@ 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 { BlueDarkTheme, 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'; @@ -18,15 +18,19 @@ const App = () => { 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); }, []); - initializeIndexer({ - baseUrl: 'https://cushionlike-isabel-retrievable.ngrok-free.dev/', - timeout: 100000, // 100 seconds for blockchain scanning operations (increased for slower connections) - }); const colorScheme = useColorScheme(); + React.useEffect(() => { + BlueCurrentTheme.updateColorScheme(); + }, [colorScheme]); + useLogger(navigationRef); return ( diff --git a/components/Alert.ts b/components/Alert.ts index df3f7a864..0147cf0fc 100644 --- a/components/Alert.ts +++ b/components/Alert.ts @@ -33,8 +33,8 @@ const presentAlert = (() => { 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, buttons, options); - } else { + 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 index ed7ad6546..20e046b6f 100644 --- a/components/CustomAlert.tsx +++ b/components/CustomAlert.tsx @@ -3,7 +3,7 @@ import { Modal, View, Text, TouchableOpacity, StyleSheet, AlertButton, AlertOpti import { BlueDefaultTheme, BlueDarkTheme } from './themes'; export interface CustomAlertHandle { - show: (title: string | undefined, message: string, buttons: AlertButton[], options: AlertOptions) => void; + show: (title: string | undefined, message: string | undefined, buttons: AlertButton[], options: AlertOptions) => void; } const CustomAlert = forwardRef((_, ref) => { @@ -21,7 +21,7 @@ const CustomAlert = forwardRef((_, ref) => { useImperativeHandle(ref, () => ({ show: (t, m, b, o) => { setTitle(t); - setMessage(m); + setMessage(m ?? ''); setButtons(b); setOptions(o); setVisible(true); @@ -43,7 +43,7 @@ const CustomAlert = forwardRef((_, ref) => { case 'cancel': return { color: colors.foregroundColor, fontWeight: '600' as const }; default: - return { color: colors.mainColor }; + return { color: colors.primary }; } }; @@ -57,6 +57,8 @@ const CustomAlert = forwardRef((_, ref) => { {buttons.map((button, index) => ( 0 && { borderLeftWidth: StyleSheet.hairlineWidth, borderLeftColor: colors.lightBorder }, diff --git a/loc/en.json b/loc/en.json index f448f2a62..9109d4bda 100644 --- a/loc/en.json +++ b/loc/en.json @@ -410,12 +410,13 @@ "clear_clipboard_on_import": "Clear clipboard on import", "details_address": "Address", "details_advanced": "Advanced", - "details_are_you_sure": "You'll need your seed phrase to restore access.", + "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", @@ -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/screen/settings/DeleteWallet.tsx b/screen/settings/DeleteWallet.tsx index 5e0e0de73..5f0a26334 100644 --- a/screen/settings/DeleteWallet.tsx +++ b/screen/settings/DeleteWallet.tsx @@ -18,13 +18,13 @@ const DeleteWallet: React.FC = () => { const handleDeleteWallet = useCallback(async () => { const wallet = wallets.length > 0 ? wallets[0] : null; if (!wallet) { - presentAlert({ title: loc.wallets.list_empty_txs1, message: 'No wallet available to delete' }); + presentAlert({ title: loc.wallets.list_empty_txs1, message: loc.wallets.no_wallet_to_delete }); return; } presentAlert({ title: loc.wallets.details_delete_wallet, - message: loc.wallets.details_are_you_sure, + message: loc.wallets.delete_wallet_warning, buttons: [ { text: loc._.cancel, 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,