mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-07-22 07:48:28 +00:00
Theme-colored app icon and splash, new font, tuning of auth splash screen
This commit is contained in:
@@ -16,19 +16,14 @@
|
||||
android:usesCleartextTraffic="true"
|
||||
android:supportsRtl="true"
|
||||
>
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
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" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
@@ -56,8 +51,38 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="cashu" />
|
||||
</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"
|
||||
|
||||
Binary file not shown.
BIN
android/app/src/main/assets/fonts/HammersmithOne-Regular.ttf
Normal file
BIN
android/app/src/main/assets/fonts/HammersmithOne-Regular.ttf
Normal file
Binary file not shown.
@@ -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>
|
||||
|
||||
@@ -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.
BIN
assets/fonts/HammersmithOne-Regular.ttf
Normal file
BIN
assets/fonts/HammersmithOne-Regular.ttf
Normal file
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 */,
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
@@ -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",
|
||||
|
||||
43
patches/react-native-change-icon+3.2.0.patch
Normal file
43
patches/react-native-change-icon+3.2.0.patch
Normal 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
|
||||
}
|
||||
145
src/App.tsx
145
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 (
|
||||
<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
|
||||
export default App
|
||||
|
||||
@@ -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,11 +41,7 @@ export const UserSettingsStoreModel = types
|
||||
self.exchangeCurrency = exchangeCurrency
|
||||
return exchangeCurrency
|
||||
},
|
||||
setTheme: (theme: ThemeCode) => {
|
||||
self.theme = theme
|
||||
return theme
|
||||
},
|
||||
setNextTheme: (theme: ThemeCode) => {
|
||||
setNextTheme: (theme: ThemeCode) => {
|
||||
self.nextTheme = theme
|
||||
return theme
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
@@ -198,8 +202,8 @@ export const SecurityScreen = observer(function SecurityScreen({ route }: Props)
|
||||
title={
|
||||
isBiometricAuthOn ? 'Authentication is on' : 'Authentication is off'
|
||||
}
|
||||
message={resultMessage as string}
|
||||
/>
|
||||
message={resultMessage as string}
|
||||
/>
|
||||
}
|
||||
onBackButtonPress={toggleAuthModal}
|
||||
onBackdropPress={toggleAuthModal}
|
||||
@@ -229,8 +233,8 @@ const $card: ViewStyle = {
|
||||
}
|
||||
|
||||
const $bottomModal: ViewStyle = {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
// flex: 1,
|
||||
// alignItems: 'center',
|
||||
paddingVertical: spacing.large,
|
||||
paddingHorizontal: spacing.small,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -286,17 +287,19 @@ export const SettingsScreen = observer(function SettingsScreen({ route }: Props)
|
||||
toggleCurrencyModal()
|
||||
}
|
||||
|
||||
const onSelectTheme = async function(theme: ThemeCode) {
|
||||
const onSelectTheme = async function(theme: ThemeCode) {
|
||||
if(currentTheme !== theme) {
|
||||
try {
|
||||
try {
|
||||
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)
|
||||
}
|
||||
}
|
||||
toggleThemeModal()
|
||||
toggleThemeModal()
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ export const ResultModalInfo = function(props: {
|
||||
|
||||
const $iconContainer: ViewStyle = {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
justifyContent: 'center',
|
||||
width: 120,
|
||||
height: 120,
|
||||
}
|
||||
@@ -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')
|
||||
|
||||
@@ -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,7 +155,32 @@ const clearAll = function (): void {
|
||||
}
|
||||
}
|
||||
|
||||
export const MMKVStorage = {
|
||||
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,
|
||||
saveNumber,
|
||||
@@ -163,4 +189,6 @@ export const MMKVStorage = {
|
||||
save,
|
||||
remove,
|
||||
clearAll,
|
||||
loadTheme,
|
||||
saveTheme,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
if(userSettingsStore.theme === ThemeCode.DEFAULT) {
|
||||
// loadTheme() returns an in-memory cached value after first read — O(1), no MMKV overhead
|
||||
const theme = MMKVStorage.loadTheme()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
11
yarn.lock
11
yarn.lock
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user