diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 770d431..8e27f3c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -16,19 +16,14 @@ android:usesCleartextTraffic="true" android:supportsRtl="true" > - - - - - - - + @@ -56,8 +51,38 @@ - + + + + + + + + + + + + + + + + + + + @drawable/rn_edit_text_material + + #2e2e2e diff --git a/android/link-assets-manifest.json b/android/link-assets-manifest.json index 20fd8ca..1e68bd5 100644 --- a/android/link-assets-manifest.json +++ b/android/link-assets-manifest.json @@ -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", diff --git a/assets/fonts/Gluten-Regular.ttf b/assets/fonts/Gluten-Regular.ttf deleted file mode 100644 index ae2e3c2..0000000 Binary files a/assets/fonts/Gluten-Regular.ttf and /dev/null differ diff --git a/assets/fonts/HammersmithOne-Regular.ttf b/assets/fonts/HammersmithOne-Regular.ttf new file mode 100644 index 0000000..94d0f1d Binary files /dev/null and b/assets/fonts/HammersmithOne-Regular.ttf differ diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 94986a1..46ce969 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -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 diff --git a/ios/link-assets-manifest.json b/ios/link-assets-manifest.json index 20fd8ca..1e68bd5 100644 --- a/ios/link-assets-manifest.json +++ b/ios/link-assets-manifest.json @@ -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", diff --git a/ios/minibits_wallet.xcodeproj/project.pbxproj b/ios/minibits_wallet.xcodeproj/project.pbxproj index 277d8cd..6becfa8 100644 --- a/ios/minibits_wallet.xcodeproj/project.pbxproj +++ b/ios/minibits_wallet.xcodeproj/project.pbxproj @@ -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 = ""; }; 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 = ""; }; - 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 = ""; }; + 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 = ""; }; 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = minibits_wallet/AppDelegate.swift; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = minibits_wallet/LaunchScreen.storyboard; sourceTree = ""; }; 902F38382D92C792005DB998 /* OpenSSL.xcframework */ = {isa = PBXFileReference; expectedSignature = "AppleDeveloperProgram:67RAULRX93:Marcin Krzyzanowski"; lastKnownFileType = wrapper.xcframework; name = OpenSSL.xcframework; path = "Pods/OpenSSL-Universal/OpenSSL.xcframework"; sourceTree = ""; }; @@ -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 */, ); diff --git a/ios/minibits_wallet/Info.plist b/ios/minibits_wallet/Info.plist index a086e09..a095804 100644 --- a/ios/minibits_wallet/Info.plist +++ b/ios/minibits_wallet/Info.plist @@ -51,7 +51,7 @@ UIAppFonts - Gluten-Regular.ttf + HammersmithOne-Regular.ttf UIBackgroundModes @@ -78,5 +78,23 @@ HOT_UPDATER_FINGERPRINT_HASH 98c1f26187f02f38ddef1fcb75384246a1c5fa03 + CFBundleIcons + + CFBundlePrimaryIcon + + CFBundleIconName + AppIcon + + CFBundleAlternateIcons + + Golden + + CFBundleIconName + AppIconGolden + UIPrerenderedIcon + + + + \ No newline at end of file diff --git a/package.json b/package.json index 133c3d8..0c45445 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/patches/react-native-change-icon+3.2.0.patch b/patches/react-native-change-icon+3.2.0.patch new file mode 100644 index 0000000..18df631 --- /dev/null +++ b/patches/react-native-change-icon+3.2.0.patch @@ -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 + } diff --git a/src/App.tsx b/src/App.tsx index e392c80..b09379f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,28 +1,33 @@ import React, { useCallback, useEffect, useRef, useState } from 'react' import FlashMessage from "react-native-flash-message" -import { +import { SafeAreaProvider, } from 'react-native-safe-area-context' import { - setSizeMattersBaseHeight, + setSizeMattersBaseHeight, 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 } @@ -89,18 +99,23 @@ function App() { // User authentication (biometrics / PIN) if (userSettingsStore.isAuthOn) { - // Keep UI in locked mode until authentication succeeds. - setIsAuthLocked(true) - attemptAuth() + // Keep UI in locked mode until authentication succeeds. + 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() }) @@ -108,7 +123,7 @@ function App() { useEffect(() => { if ( isInternetReachable === null || - isDeviceAuthenticated || + isDeviceAuthenticated || !rehydrated ) { log.trace('[App] Not yet ready for device auth check') @@ -130,10 +145,10 @@ function App() { try { const walletKeys: WalletKeys = await walletStore.getCachedWalletKeys() const deviceId = walletProfileStore.device - + await authStore.clearTokens() await authStore.enrollDevice(walletKeys.NOSTR, deviceId) - + log.trace('[App] Device re-enrollment successful') } catch (e: any) { log.error('[App] Device re-enrollment failed', { message: e.message }) @@ -146,7 +161,7 @@ function App() { log.trace('[App] Network is online and refresh token valid → device authenticated') setIsDeviceAuthenticated(true) } - + } else { // Confirmed offline → skip re-enrollment, proceed safely log.trace('[App] Network confirmed offline → skipping refresh token check, device authenticated') @@ -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 ( - - - - - - - - {isAuthLocked && ( - - - Minibits is locked. Authentication is required to continue. - - - Authenticate - - RNExitApp.exitApp()} - > - Close app - - - )} - + + + + + + + + + {isAppLocked && ( + <> + + Minibits is locked. Authentication is required to continue. + +