Theme-colored app icon and splash, new font, tuning of auth splash screen

This commit is contained in:
minibits-cash
2026-03-11 17:10:51 +01:00
parent c1494d8a85
commit 99a16c1c56
24 changed files with 279 additions and 116 deletions

View File

@@ -23,11 +23,6 @@
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@@ -58,6 +53,36 @@
</intent-filter>
</activity>
<!-- Default launcher icon alias (enabled by default) -->
<activity-alias
android:name=".MainActivityDefault"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:targetActivity=".MainActivity"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity-alias>
<!-- Golden theme launcher icon alias (disabled by default) -->
<activity-alias
android:name=".MainActivityGolden"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher_golden"
android:targetActivity=".MainActivity"
android:enabled="false"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity-alias>
<service
android:name="com.reactnativehce.services.CardService"
android:exported="true"

View File

@@ -4,6 +4,10 @@
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
<!-- Fixed dark window background shown before React Native renders its first frame.
Matches the dark app themes (DEFAULT/DARK/GOLDEN). For LIGHT theme, the JS
splash view overrides this immediately on first render. -->
<item name="android:windowBackground">#2e2e2e</item>
</style>
</resources>

View File

@@ -2,8 +2,8 @@
"migIndex": 1,
"data": [
{
"path": "assets/fonts/Gluten-Regular.ttf",
"sha1": "96270757924e60e26763e8814aaa2000f470d352"
"path": "assets/fonts/HammersmithOne-Regular.ttf",
"sha1": "132e0c6ad83dff514d1f6d2719aae6c90255d92f"
},
{
"path": "assets/icons/nostr.png",

Binary file not shown.

Binary file not shown.

View File

@@ -1434,6 +1434,8 @@ PODS:
- React-RCTFBReactNativeSpec
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- react-native-change-icon (3.2.0):
- React
- react-native-hce (0.3.0):
- React
- react-native-mmkv (3.3.3):
@@ -2444,6 +2446,7 @@ DEPENDENCIES:
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
- React-Mapbuffer (from `../node_modules/react-native/ReactCommon`)
- React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)
- react-native-change-icon (from `../node_modules/react-native-change-icon`)
- react-native-hce (from `../node_modules/react-native-hce`)
- react-native-mmkv (from `../node_modules/react-native-mmkv`)
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
@@ -2614,6 +2617,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon"
React-microtasksnativemodule:
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks"
react-native-change-icon:
:path: "../node_modules/react-native-change-icon"
react-native-hce:
:path: "../node_modules/react-native-hce"
react-native-mmkv:
@@ -2790,6 +2795,7 @@ SPEC CHECKSUMS:
React-logger: dd3e5f78709d5b029336be395a67bbcdc775e2ed
React-Mapbuffer: 2bdeed9ba58c94ac99a2711a3a722d2d5b3a8a81
React-microtasksnativemodule: d1acdc86b1b2ee5622af4950f3908f2155eddfcf
react-native-change-icon: af3d0eb3ffee2ab3c584d12a68c29f89b7108cf8
react-native-hce: ad5462e792d84e87924308dda1ee516757f1c0eb
react-native-mmkv: 07f0ae0bd7e8d458077e1750c502ba67975b3702
react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187

View File

@@ -2,8 +2,8 @@
"migIndex": 1,
"data": [
{
"path": "assets/fonts/Gluten-Regular.ttf",
"sha1": "96270757924e60e26763e8814aaa2000f470d352"
"path": "assets/fonts/HammersmithOne-Regular.ttf",
"sha1": "132e0c6ad83dff514d1f6d2719aae6c90255d92f"
},
{
"path": "assets/icons/nostr.png",

View File

@@ -13,7 +13,7 @@
902F38392D92C792005DB998 /* OpenSSL.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 902F38382D92C792005DB998 /* OpenSSL.xcframework */; };
9044DC252D95937D00F918A6 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9044DC242D95937D00F918A6 /* GoogleService-Info.plist */; };
A1B9A6F7058C4F12AB677FD4 /* nostr.png in Resources */ = {isa = PBXBuildFile; fileRef = AB8A46AD84194C2583FF9193 /* nostr.png */; };
C3AB98FDBCF446F99A4EA648 /* Gluten-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6DA8A3418B7A4B348B67B9E7 /* Gluten-Regular.ttf */; };
A2E4F8C1D3B547A890123456 /* HammersmithOne-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B3D7A2E5F1C648B9A0234567 /* HammersmithOne-Regular.ttf */; };
CD7DC745CFA9F47C2D6FFA1D /* Pods_minibits_wallet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4ACC08249CA13DFDBBBD7A8B /* Pods_minibits_wallet.framework */; };
D610B97F2318E8AB9D0962D2 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
/* End PBXBuildFile section */
@@ -26,7 +26,7 @@
3B4392A12AC88292D35C810B /* Pods-minibits_wallet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-minibits_wallet.debug.xcconfig"; path = "Target Support Files/Pods-minibits_wallet/Pods-minibits_wallet.debug.xcconfig"; sourceTree = "<group>"; };
4ACC08249CA13DFDBBBD7A8B /* Pods_minibits_wallet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_minibits_wallet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5709B34CF0A7D63546082F79 /* Pods-minibits_wallet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-minibits_wallet.release.xcconfig"; path = "Target Support Files/Pods-minibits_wallet/Pods-minibits_wallet.release.xcconfig"; sourceTree = "<group>"; };
6DA8A3418B7A4B348B67B9E7 /* Gluten-Regular.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Gluten-Regular.ttf"; path = "../assets/fonts/Gluten-Regular.ttf"; sourceTree = "<group>"; };
B3D7A2E5F1C648B9A0234567 /* HammersmithOne-Regular.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "HammersmithOne-Regular.ttf"; path = "../assets/fonts/HammersmithOne-Regular.ttf"; sourceTree = "<group>"; };
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = minibits_wallet/AppDelegate.swift; sourceTree = "<group>"; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = minibits_wallet/LaunchScreen.storyboard; sourceTree = "<group>"; };
902F38382D92C792005DB998 /* OpenSSL.xcframework */ = {isa = PBXFileReference; expectedSignature = "AppleDeveloperProgram:67RAULRX93:Marcin Krzyzanowski"; lastKnownFileType = wrapper.xcframework; name = OpenSSL.xcframework; path = "Pods/OpenSSL-Universal/OpenSSL.xcframework"; sourceTree = "<group>"; };
@@ -75,7 +75,7 @@
4B24E47762034A51AEE63594 /* Resources */ = {
isa = PBXGroup;
children = (
6DA8A3418B7A4B348B67B9E7 /* Gluten-Regular.ttf */,
B3D7A2E5F1C648B9A0234567 /* HammersmithOne-Regular.ttf */,
AB8A46AD84194C2583FF9193 /* nostr.png */,
);
name = Resources;
@@ -186,7 +186,7 @@
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
D610B97F2318E8AB9D0962D2 /* PrivacyInfo.xcprivacy in Resources */,
C3AB98FDBCF446F99A4EA648 /* Gluten-Regular.ttf in Resources */,
A2E4F8C1D3B547A890123456 /* HammersmithOne-Regular.ttf in Resources */,
A1B9A6F7058C4F12AB677FD4 /* nostr.png in Resources */,
9044DC252D95937D00F918A6 /* GoogleService-Info.plist in Resources */,
);

View File

@@ -51,7 +51,7 @@
<true/>
<key>UIAppFonts</key>
<array>
<string>Gluten-Regular.ttf</string>
<string>HammersmithOne-Regular.ttf</string>
</array>
<key>UIBackgroundModes</key>
<array>
@@ -78,5 +78,23 @@
<false/>
<key>HOT_UPDATER_FINGERPRINT_HASH</key>
<string>98c1f26187f02f38ddef1fcb75384246a1c5fa03</string>
<key>CFBundleIcons</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconName</key>
<string>AppIcon</string>
</dict>
<key>CFBundleAlternateIcons</key>
<dict>
<key>Golden</key>
<dict>
<key>CFBundleIconName</key>
<string>AppIconGolden</string>
<key>UIPrerenderedIcon</key>
<false/>
</dict>
</dict>
</dict>
</dict>
</plist>

View File

@@ -4,8 +4,9 @@
"private": true,
"scripts": {
"android:clean": "cd android && ./gradlew clean",
"android:dev": "react-native run-android --active-arch-only",
"android:test": "react-native run-android --mode=release --active-arch-only",
"android:reinstall": "$HOME/Library/Android/sdk/platform-tools/adb uninstall com.minibits_wallet; yarn android:dev",
"android:dev": "react-native run-android --active-arch-only --main-activity .MainActivityDefault",
"android:test": "react-native run-android --mode=release --active-arch-only --main-activity .MainActivityDefault",
"android:bundle": "react-native build-android --mode=release",
"android:apk": "cd android && ./gradlew assembleRelease",
"ios:dev": "npx react-native run-ios --scheme \"minibits_wallet\"",
@@ -59,6 +60,7 @@
"react-native": "0.84.0",
"react-native-animated-pagination-dots": "^0.1.73",
"react-native-camera-kit": "17.0.1",
"react-native-change-icon": "^3.0.0",
"react-native-exit-app": "^2.0.0",
"react-native-fast-image": "^8.6.3",
"react-native-flash-message": "^0.4.2",

View File

@@ -0,0 +1,43 @@
diff --git a/node_modules/react-native-change-icon/android/build.gradle b/node_modules/react-native-change-icon/android/build.gradle
index aa5226c..bf54025 100644
--- a/node_modules/react-native-change-icon/android/build.gradle
+++ b/node_modules/react-native-change-icon/android/build.gradle
@@ -4,7 +4,7 @@ buildscript {
repositories {
google()
- jcenter()
+ mavenCentral()
}
dependencies {
@@ -52,7 +52,6 @@ android {
repositories {
mavenCentral()
- jcenter()
google()
def found = false
diff --git a/node_modules/react-native-change-icon/android/src/main/java/com/reactnativechangeicon/ChangeIconModule.kt b/node_modules/react-native-change-icon/android/src/main/java/com/reactnativechangeicon/ChangeIconModule.kt
index 70c3e2c..724b3fe 100644
--- a/node_modules/react-native-change-icon/android/src/main/java/com/reactnativechangeicon/ChangeIconModule.kt
+++ b/node_modules/react-native-change-icon/android/src/main/java/com/reactnativechangeicon/ChangeIconModule.kt
@@ -21,7 +21,7 @@ class ChangeIconModule(reactContext: ReactApplicationContext, private val packag
@ReactMethod
fun changeIcon(enableIcon: String, promise: Promise) {
- val activity: Activity? = currentActivity
+ val activity: Activity? = reactApplicationContext.currentActivity
if (activity == null || enableIcon.isEmpty()) {
promise.reject("Icon string is empty.")
return
@@ -46,7 +46,7 @@ class ChangeIconModule(reactContext: ReactApplicationContext, private val packag
private fun completeIconChange() {
if (iconChanged) {
- val activity: Activity? = currentActivity
+ val activity: Activity? = reactApplicationContext.currentActivity
if (activity == null) {
return
}

View File

@@ -8,21 +8,26 @@ import {
setSizeMattersBaseWidth
} from '@gocodingnow/rn-size-matters'
import RNExitApp from 'react-native-exit-app'
import { changeIcon } from 'react-native-change-icon'
import {AppNavigator} from './navigation'
import {useInitialRootStore, useStores} from './models'
import {KeyChain, WalletKeys} from './services'
import {ErrorBoundary} from './screens/ErrorScreen/ErrorBoundary'
import Config from './config'
import {log} from './services'
import { Image, Pressable, Text as RNText, TextStyle, View } from 'react-native'
import { colors, spacing, typography } from './theme'
import { Image, TextStyle, View } from 'react-native'
import { ThemeCode, colors, spacing, typography } from './theme'
import useColorScheme from './theme/useThemeColor'
import { displayName } from '../app.json'
import { Text } from './components/Text'
import useIsInternetReachable from './utils/useIsInternetReachable'
import { MMKVStorage } from './services'
import { Button, Screen } from './components'
setSizeMattersBaseWidth(375)
setSizeMattersBaseHeight(812)
function App() {
const {
userSettingsStore,
@@ -32,9 +37,11 @@ function App() {
walletProfileStore
} = useStores()
const colorScheme = useColorScheme()
const [isUserAuthenticated, setIsUserAuthenticated] = useState(false)
const [isDeviceAuthenticated, setIsDeviceAuthenticated] = useState(false)
const [isAuthLocked, setIsAuthLocked] = useState(false)
const [isAppLocked, setIsAppLocked] = useState(false)
const isAuthInProgressRef = useRef(false)
const isInternetReachable = useIsInternetReachable() // boolean | null
@@ -47,37 +54,40 @@ function App() {
}
}, [isInternetReachable])
const attemptAuth = useCallback(async () => {
const attemptUserAuthentication = useCallback(async () => {
if (isAuthInProgressRef.current) {
log.trace('[App] attemptAuth skipped, auth already in progress')
log.trace('[App] attemptUserAuthentication skipped, auth already in progress')
return
}
isAuthInProgressRef.current = true
try {
const isAuthEnabled = userSettingsStore.isAuthOn
log.trace('[App] attemptAuth called')
const {isAuthOn} = userSettingsStore
log.trace('[App] attemptUserAuthentication called', {isAuthOn})
const result = await KeyChain.authenticateOnAppStart(isAuthEnabled)
log.trace('[App] attemptAuth result:', { success: result.success, shouldExitApp: result.shouldExitApp })
const result = await KeyChain.authenticateOnAppStart(isAuthOn)
log.trace('[App] attemptUserAuthentication result:', { success: result.success, shouldExitApp: result.shouldExitApp })
if (result.success) {
setIsUserAuthenticated(true)
setIsAuthLocked(false)
setIsAppLocked(false)
return
}
if (result.shouldExitApp) {
// Fix: show locked UI before exiting so the user isn't stuck on a
// blank splash if exitApp() has any delay or fails silently.
setIsAppLocked(true)
RNExitApp.exitApp()
return
}
log.trace('[App] attemptAuth failed, locking app')
setIsAuthLocked(true)
log.trace('[App] attemptUserAuthentication failed, locking app')
setIsAppLocked(true)
} catch (e: any) {
log.error('[App] attemptAuth caught error', { message: e.message })
setIsAuthLocked(true)
log.error('[App] attemptUserAuthentication caught error', { message: e.message })
setIsAppLocked(true)
} finally {
isAuthInProgressRef.current = false
}
@@ -90,17 +100,22 @@ function App() {
// User authentication (biometrics / PIN)
if (userSettingsStore.isAuthOn) {
// Keep UI in locked mode until authentication succeeds.
setIsAuthLocked(true)
attemptAuth()
attemptUserAuthentication()
} else {
setIsUserAuthenticated(true)
}
// Theme sync
if (userSettingsStore.theme !== userSettingsStore.nextTheme) {
userSettingsStore.setTheme(userSettingsStore.nextTheme)
// Theme sync — apply queued theme on startup and persist to dedicated MMKV key
const appliedTheme = MMKVStorage.loadTheme()
if (appliedTheme !== userSettingsStore.nextTheme) {
MMKVStorage.saveTheme(userSettingsStore.nextTheme)
}
// Icon sync — keeps launcher icon in sync with the applied theme.
// Handles upgrades from versions before icon switching existed.
const targetIcon = MMKVStorage.loadTheme() === ThemeCode.GOLDEN ? 'Golden' : 'Default'
changeIcon(targetIcon).catch(() => {})
// Reset relay statuses
relaysStore.resetStatuses()
})
@@ -162,36 +177,53 @@ function App() {
!rehydrated ||
!isUserAuthenticated ||
!isDeviceAuthenticated ||
isAuthLocked
isAppLocked
) {
const splashTheme = MMKVStorage.loadTheme()
const isLight = splashTheme === ThemeCode.LIGHT ||
(splashTheme === ThemeCode.DEFAULT && colorScheme === 'light')
const isGolden = splashTheme === ThemeCode.GOLDEN
const splashBg = isLight ? colors.palette.neutral200 : colors.palette.neutral700
const splashTextColor = isLight ? colors.palette.neutral800 : colors.palette.neutral100
log.trace('[App] Rendering splash/auth screen', {isAppLocked})
return (
<View style={{flex: 1}}>
<ErrorBoundary catchErrors={Config.catchErrors}>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text text={displayName} style={$title} />
<Image source={require('../android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png')} />
</View>
</ErrorBoundary>
{isAuthLocked && (
<View style={{alignItems: 'center', paddingHorizontal: spacing.large, paddingBottom: 60}}>
<RNText style={{color: colors.palette.neutral400, fontSize: 14, textAlign: 'center', marginBottom: spacing.large}}>
Minibits is locked. Authentication is required to continue.
</RNText>
<Pressable
style={{backgroundColor: colors.palette.accent400, paddingVertical: spacing.small, paddingHorizontal: spacing.extraLarge, borderRadius: spacing.small, marginBottom: spacing.medium, minWidth: 200, alignItems: 'center'}}
onPress={attemptAuth}
>
<RNText style={{color: 'white', fontSize: 16, fontWeight: '600'}}>Authenticate</RNText>
</Pressable>
<Pressable
style={{paddingVertical: spacing.small, paddingHorizontal: spacing.extraLarge, minWidth: 200, alignItems: 'center'}}
onPress={() => RNExitApp.exitApp()}
>
<RNText style={{color: colors.palette.neutral400, fontSize: 16}}>Close app</RNText>
</Pressable>
</View>
)}
</View>
<SafeAreaProvider>
<Screen preset='fixed' backgroundColor={splashBg} safeAreaEdges={['top', 'bottom']}>
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Image source={isGolden
? require('../android/app/src/main/res/mipmap-xxhdpi/ic_launcher_golden.png')
: require('../android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png')} />
</View>
<Text text={displayName} style={[$title, {color: splashTextColor}]} />
<View style={{
paddingHorizontal: spacing.large,
marginBottom: spacing.extraLarge,
}}>
{isAppLocked && (
<>
<Text size='md' style={{textAlign: 'center', color: splashTextColor}}>
Minibits is locked. Authentication is required to continue.
</Text>
<Button
preset='default'
text='Authenticate'
onPress={attemptUserAuthentication}
style={{marginVertical: spacing.large, alignSelf: 'center'}}
/>
<Button
preset='tertiary'
text='Exit'
onPress={() => RNExitApp.exitApp()}
style={{alignSelf: 'center'}}
/>
</>
)}
</View>
</Screen>
</SafeAreaProvider>
)
}
@@ -209,7 +241,6 @@ const $title: TextStyle = {
textAlign: "center",
fontFamily: typography.logo.normal,
fontSize: spacing.large,
color: 'white'
}
export default App

View File

@@ -7,7 +7,6 @@ import { ThemeCode } from '../theme'
export type UserSettings = {
preferredUnit: MintUnit
exchangeCurrency: CurrencyCode
theme: ThemeCode
nextTheme: ThemeCode
isOnboarded: boolean
isAuthOn: boolean
@@ -24,7 +23,6 @@ export const UserSettingsStoreModel = types
.props({
preferredUnit: types.optional(types.frozen<MintUnit>(), 'sat'),
exchangeCurrency: types.optional(types.frozen<CurrencyCode | null>(), CurrencyCode.USD),
theme: types.optional(types.frozen<ThemeCode>(), ThemeCode.DEFAULT),
nextTheme: types.optional(types.frozen<ThemeCode>(), ThemeCode.DEFAULT),
isOnboarded: types.optional(types.boolean, false),
isAuthOn: types.optional(types.boolean, false),
@@ -43,10 +41,6 @@ export const UserSettingsStoreModel = types
self.exchangeCurrency = exchangeCurrency
return exchangeCurrency
},
setTheme: (theme: ThemeCode) => {
self.theme = theme
return theme
},
setNextTheme: (theme: ThemeCode) => {
self.nextTheme = theme
return theme

View File

@@ -349,7 +349,7 @@ export const OwnName = observer(function (props: {pubkey: string}) {
<RNText style={$supportText}>
<Text
text='Minibits'
style={{fontFamily: 'Gluten-Regular', fontSize: 18}}
style={{fontFamily: 'HammersmithOne-Regular', fontSize: 18}}
/>{' '}
<Text tx="contactsScreen_ownName_donationSubtext" />
</RNText>

View File

@@ -18,6 +18,7 @@ import {useStores} from '../models'
import AppError from '../utils/AppError'
import {ResultModalInfo} from './Wallet/ResultModalInfo'
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
import { KeyChain } from '../services'
type Props = StaticScreenProps<undefined>
@@ -42,6 +43,9 @@ export const SecurityScreen = observer(function SecurityScreen({ route }: Props)
try {
setIsLoading(true)
// require auth to turn on or off
await KeyChain.getOrCreateAuthToken(isBiometricAuthOn)
const result = await userSettingsStore.setIsAuthOn(
!isBiometricAuthOn,
)
@@ -229,8 +233,8 @@ const $card: ViewStyle = {
}
const $bottomModal: ViewStyle = {
flex: 1,
alignItems: 'center',
// flex: 1,
// alignItems: 'center',
paddingVertical: spacing.large,
paddingHorizontal: spacing.small,
}

View File

@@ -13,10 +13,11 @@ import {
HOT_UPDATER_URL,
} from '@env'
import {ThemeCode, Themes, colors, spacing, useThemeColor} from '../theme'
import { changeIcon } from 'react-native-change-icon'
import {ListItem, Screen, Text, Card, NwcIcon, Button, BottomModal, InfoModal, Icon, Header, AnimatedHeader} from '../components'
import {useStores} from '../models'
import {translate} from '../i18n'
import { log } from '../services'
import { MMKVStorage, log } from '../services'
import { Currencies, CurrencyCode, availableExchangeCurrencies } from '../services/wallet/currency'
import { NotificationService } from '../services/notificationService'
import { SvgXml } from 'react-native-svg'
@@ -57,7 +58,7 @@ export const SettingsScreen = observer(function SettingsScreen({ route }: Props)
const [isUpdateAvailable, setIsUpdateAvailable] = useState<boolean>(false)
const [updateDescription, setUpdateDescription] = useState<string>('')
const [currentTheme, setCurrentTheme] = useState<ThemeCode>(userSettingsStore.theme)
const [currentTheme, setCurrentTheme] = useState<ThemeCode>(MMKVStorage.loadTheme())
const [updateSize, setUpdateSize] = useState<string>('')
const [isCurrencyModalVisible, setIsCurrencyModalVisible] = useState<boolean>(false)
@@ -292,6 +293,8 @@ export const SettingsScreen = observer(function SettingsScreen({ route }: Props)
userSettingsStore.setNextTheme(theme)
setCurrentTheme(theme)
setInfo(translate('settingsScreen_restartTheme'))
const targetIcon = theme === ThemeCode.GOLDEN ? 'Golden' : 'Default'
await changeIcon(targetIcon)
} catch (e: any) {
log.warn('[onSelectTheme]', e.message)
}

View File

@@ -446,14 +446,7 @@ const authenticateOnAppStart = async function (isAuthOn: boolean): Promise<AuthR
// Handle specific error codes for user actions
const authErrorContext = getAuthErrorContext(e)
log.warn('[authenticateOnAppStart]', 'Auth error caught', {
message: e.message,
code: e.code,
name: e.name,
params: e.params,
stringified: authErrorContext.serialized,
codeCandidates: authErrorContext.codeCandidates,
})
log.warn('[authenticateOnAppStart]', 'Auth error caught',{authErrorContext})
if (authErrorContext.isBackPressed) {
log.trace('[authenticateOnAppStart]', 'User pressed back button')

View File

@@ -1,6 +1,7 @@
import {MMKV} from 'react-native-mmkv'
import {log} from './logService'
import AppError, {Err} from '../utils/AppError'
import { ThemeCode } from '../theme'
const STORAGE_KEY = 'storage-v1'
let _storage: MMKV | undefined
@@ -154,6 +155,31 @@ const clearAll = function (): void {
}
}
const THEME_STORAGE_KEY = 'theme'
let _cachedTheme: ThemeCode | undefined
const loadTheme = function (): ThemeCode {
if (_cachedTheme !== undefined) return _cachedTheme
try {
const storage = getInstance()
_cachedTheme = (storage.getString(THEME_STORAGE_KEY) as ThemeCode) ?? ThemeCode.DEFAULT
return _cachedTheme
} catch (e: any) {
return ThemeCode.DEFAULT
}
}
const saveTheme = function (theme: ThemeCode): boolean {
try {
_cachedTheme = theme
const storage = getInstance()
storage.set(THEME_STORAGE_KEY, theme)
return true
} catch (e: any) {
throw new AppError(Err.DATABASE_ERROR, e.message)
}
}
export const MMKVStorage = {
loadString,
saveString,
@@ -163,4 +189,6 @@ export const MMKVStorage = {
save,
remove,
clearAll,
loadTheme,
saveTheme,
}

View File

@@ -26,8 +26,8 @@ const fonts = {
// Android only font.
normal: 'monospace',
},
gluten: {
normal: 'Gluten-Regular',
hammersmithOne: {
normal: 'HammersmithOne-Regular',
}
}
@@ -54,5 +54,5 @@ export const typography = {
* Lets get fancy with a monospace font!
*/
code: Platform.select({ios: fonts.courier, android: fonts.monospace}),
logo: fonts.gluten,
logo: fonts.hammersmithOne,
}

View File

@@ -1,5 +1,5 @@
import { rootStoreInstance, useStores } from '../models'
import {ThemeCode, colors} from '../theme'
import { MMKVStorage } from '../services'
import {
ColorSchemeName,
useColorScheme as _useColorScheme,
@@ -16,12 +16,13 @@ export default function useColorScheme(): NonNullable<ColorSchemeName> {
export function useThemeColor(
colorName: keyof typeof colors.light & keyof typeof colors.dark & keyof typeof colors.golden,
) {
const { userSettingsStore } = rootStoreInstance
// loadTheme() returns an in-memory cached value after first read — O(1), no MMKV overhead
const theme = MMKVStorage.loadTheme()
if(userSettingsStore.theme === ThemeCode.DEFAULT) {
if(theme === ThemeCode.DEFAULT) {
const colorScheme = useColorScheme()
return colors[colorScheme][colorName] as ColorValue
}
return colors[userSettingsStore.theme][colorName] as ColorValue
return colors[theme][colorName] as ColorValue
}

View File

@@ -14669,6 +14669,7 @@ __metadata:
react-native: 0.84.0
react-native-animated-pagination-dots: ^0.1.73
react-native-camera-kit: 17.0.1
react-native-change-icon: ^3.0.0
react-native-clean-project: ^4.0.3
react-native-dotenv: ^3.4.11
react-native-exit-app: ^2.0.0
@@ -16499,6 +16500,16 @@ __metadata:
languageName: node
linkType: hard
"react-native-change-icon@npm:^3.0.0":
version: 3.2.0
resolution: "react-native-change-icon@npm:3.2.0"
peerDependencies:
react: "*"
react-native: "*"
checksum: d361e9e06fbd2969d85373b5e07531aa4af6a6472ede16928cd3afb0cf6bad6871a9bfa5022b5959ce50787aeb6664eea13e09cd8471ce97067cf7735ed6a20b
languageName: node
linkType: hard
"react-native-clean-project@npm:^4.0.3":
version: 4.0.3
resolution: "react-native-clean-project@npm:4.0.3"