diff --git a/app.json b/app.json index e4532d8..60ec8b8 100644 --- a/app.json +++ b/app.json @@ -2,7 +2,7 @@ "expo": { "name": "Metanet", "slug": "metanet-mobile", - "version": "0.3.0", + "version": "0.3.2", "orientation": "default", "icon": "./assets/images/icon.png", "scheme": "metanet", diff --git a/app/auth/password.tsx b/app/auth/password.tsx index b41f6e8..dfdd1c8 100644 --- a/app/auth/password.tsx +++ b/app/auth/password.tsx @@ -114,9 +114,9 @@ export default function PasswordScreen() { disabled={!isButtonEnabled() || loading} > {loading ? ( - + ) : ( - {t('continue')} + {t('continue')} )} diff --git a/app/auth/phone.tsx b/app/auth/phone.tsx index da8d575..3aa7cf9 100644 --- a/app/auth/phone.tsx +++ b/app/auth/phone.tsx @@ -199,9 +199,9 @@ export default function PhoneScreen() { disabled={!isValidPhoneNumber() || loading} > {loading ? ( - + ) : ( - {t('continue')} + {t('continue')} )} diff --git a/app/browser.tsx b/app/browser.tsx index 898b478..0076792 100644 --- a/app/browser.tsx +++ b/app/browser.tsx @@ -15,7 +15,6 @@ import { Keyboard, TouchableWithoutFeedback, KeyboardAvoidingView, - LayoutAnimation, ScrollView, Modal as RNModal, BackHandler, @@ -34,7 +33,6 @@ import { } from 'react-native-gesture-handler' import { TabView, SceneMap } from 'react-native-tab-view' import Fuse from 'fuse.js' -import * as Linking from 'expo-linking' import { Ionicons } from '@expo/vector-icons' import { observer } from 'mobx-react-lite' import { router } from 'expo-router' @@ -349,6 +347,7 @@ function Browser() { const { manifest, fetchManifest, getStartUrl, shouldRedirectToStartUrl } = useWebAppManifest() const [showBalance, setShowBalance] = useState(false) const [isFullscreen, setIsFullscreen] = useState(false) + const activeCameraStreams = useRef>(new Set()) // Safety check - if somehow activeTab is null, force create a new tab // This is done after all hooks to avoid violating Rules of Hooks @@ -764,6 +763,59 @@ function Browser() { // === 1. Injected JS ============================================ const injectedJavaScript = useMemo( () => ` + // Camera access polyfill - provides mock streams to prevent WKWebView camera access + (function() { + if (!navigator.mediaDevices) return; + + const originalGetUserMedia = navigator.mediaDevices.getUserMedia?.bind(navigator.mediaDevices); + + function createMockMediaStream() { + const mockTrack = { + id: 'mock-video-' + Date.now(), + kind: 'video', + label: 'React Native Camera', + enabled: true, + muted: false, + readyState: 'live', + stop() { this.readyState = 'ended'; }, + addEventListener() {}, + removeEventListener() {}, + getSettings: () => ({ width: 640, height: 480, frameRate: 30 }) + }; + + return { + id: 'mock-stream-' + Date.now(), + active: true, + getTracks: () => [mockTrack], + getVideoTracks: () => [mockTrack], + getAudioTracks: () => [], + addEventListener() {}, + removeEventListener() {} + }; + } + + navigator.mediaDevices.getUserMedia = function(constraints) { + const hasVideo = constraints?.video === true || + (typeof constraints?.video === 'object' && constraints.video); + + if (hasVideo) { + // Notify React Native of camera request + window.ReactNativeWebView?.postMessage(JSON.stringify({ + type: 'CAMERA_REQUEST', + constraints + })); + + // Return mock stream immediately to prevent native camera + return Promise.resolve(createMockMediaStream()); + } + + // Allow audio-only requests through original implementation + return originalGetUserMedia ? + originalGetUserMedia(constraints) : + Promise.reject(new Error('Media not supported')); + }; + })(); + // Push Notification API polyfill (function() { // Check if Notification API already exists @@ -942,6 +994,103 @@ function Browser() { } catch (e) {} }); + // Completely replace getUserMedia to prevent WKWebView camera access + if (navigator.mediaDevices) { + // Store original for potential fallback, but never use it for video + const originalGetUserMedia = navigator.mediaDevices.getUserMedia?.bind(navigator.mediaDevices); + + // Completely override getUserMedia - never call original for video constraints + navigator.mediaDevices.getUserMedia = function(constraints) { + console.log('[WebView] getUserMedia intercepted:', constraints); + + // Check if requesting video - if so, handle in React Native + const hasVideo = constraints && (constraints.video === true || + (typeof constraints.video === 'object' && constraints.video !== false)); + + if (hasVideo) { + console.log('[WebView] Video requested - handling in React Native'); + // Send request to native - handle camera completely in React Native + window.ReactNativeWebView?.postMessage(JSON.stringify({ + type: 'CAMERA_REQUEST', + constraints: constraints + })); + + return new Promise((resolve, reject) => { + const handler = (event) => { + try { + const data = JSON.parse(event.data); + if (data.type === 'CAMERA_RESPONSE') { + window.removeEventListener('message', handler); + if (data.success) { + // Create a more complete mock MediaStream + const mockVideoTrack = { + id: 'mock-video-track-' + Date.now(), + kind: 'video', + label: 'React Native Camera', + enabled: true, + muted: false, + readyState: 'live', + stop: () => console.log('[WebView] Mock video track stopped'), + addEventListener: () => {}, + removeEventListener: () => {}, + dispatchEvent: () => false, + getSettings: () => ({ width: 640, height: 480, frameRate: 30 }), + getCapabilities: () => ({ width: { min: 320, max: 1920 }, height: { min: 240, max: 1080 } }), + getConstraints: () => constraints.video || {} + }; + + const mockStream = { + id: 'mock-stream-' + Date.now(), + active: true, + getTracks: () => [mockVideoTrack], + getVideoTracks: () => [mockVideoTrack], + getAudioTracks: () => [], + addEventListener: () => {}, + removeEventListener: () => {}, + dispatchEvent: () => false, + addTrack: () => {}, + removeTrack: () => {}, + clone: () => mockStream + }; + console.log('[WebView] Resolving with mock stream:', mockStream); + resolve(mockStream); + } else { + reject(new Error(data.error || 'Camera access denied')); + } + } + } catch (e) { + reject(e); + } + }; + window.addEventListener('message', handler); + + // Timeout after 10 seconds + setTimeout(() => { + window.removeEventListener('message', handler); + reject(new Error('Camera request timeout')); + }, 10000); + }); + } else if (constraints && constraints.audio && !hasVideo) { + // Audio-only requests can use original implementation + console.log('[WebView] Audio-only request - using original getUserMedia'); + return originalGetUserMedia ? originalGetUserMedia(constraints) : + Promise.reject(new Error('Audio not supported')); + } else { + // No media requested + return Promise.reject(new Error('No media constraints specified')); + } + }; + + // Also override the deprecated navigator.getUserMedia if it exists + if (navigator.getUserMedia) { + navigator.getUserMedia = function(constraints, success, error) { + navigator.mediaDevices.getUserMedia(constraints) + .then(success) + .catch(error); + }; + } + } + // Console logging const originalLog = console.log; const originalWarn = console.warn; @@ -1143,6 +1292,47 @@ function Browser() { return } + // Handle camera requests + if (msg.type === 'CAMERA_REQUEST') { + console.log('Camera access requested by website:', msg.constraints) + + // Track active camera stream for this tab + activeCameraStreams.current.add(activeTab.id.toString()) + + // Handle camera request entirely in React Native to avoid WKWebView camera issues + // For now, just grant permission - actual camera implementation would go here + try { + // Here you would implement actual React Native camera handling + // For now, we'll just grant permission and return mock stream + console.log('Granting camera permission - camera handled by React Native') + + if (activeTab.webviewRef?.current) { + activeTab.webviewRef.current.injectJavaScript(` + window.dispatchEvent(new MessageEvent('message', { + data: JSON.stringify({ + type: 'CAMERA_RESPONSE', + success: true + }) + })); + `) + } + } catch (error) { + console.error('Camera permission error:', error) + if (activeTab.webviewRef?.current) { + activeTab.webviewRef.current.injectJavaScript(` + window.dispatchEvent(new MessageEvent('message', { + data: JSON.stringify({ + type: 'CAMERA_RESPONSE', + success: false, + error: 'Camera permission denied' + }) + })); + `) + } + } + return + } + // Handle notification permission request if (msg.type === 'REQUEST_NOTIFICATION_PERMISSION') { const permission = await handleNotificationPermissionRequest(activeTab.url) @@ -1307,6 +1497,24 @@ function Browser() { return } + // Clean up camera streams when navigating away from a page + if (navState.url !== activeTab.url && activeCameraStreams.current.has(activeTab.id.toString())) { + console.log('Cleaning up camera streams for tab navigation') + activeCameraStreams.current.delete(activeTab.id.toString()) + + // Inject script to stop any active media streams + activeTab.webviewRef?.current?.injectJavaScript(` + (function() { + if (window.__activeMediaStreams) { + window.__activeMediaStreams.forEach(stream => { + stream.getTracks().forEach(track => track.stop()); + }); + window.__activeMediaStreams = []; + } + })(); + `) + } + // Log navigation state changes with back/forward capabilities console.log('🌐 Navigation State Change:', { url: navState.url, @@ -1467,29 +1675,7 @@ function Browser() { [updateActiveTab] ) - const BookmarksScene = useMemo(() => { - return () => ( - { - // Filter out invalid URLs to prevent favicon errors - return ( - bookmark.url && - bookmark.url !== kNEW_TAB_URL && - isValidUrl(bookmark.url) && - !bookmark.url.includes('about:blank') - ) - }) - .reverse()} - setStartingUrl={handleSetStartingUrl} - onRemoveBookmark={removeBookmark} - onRemoveDefaultApp={removeDefaultApp} - removedDefaultApps={removedDefaultApps} - hideHeader={true} - showOnlyBookmarks={true} - /> - ) - }, [bookmarkStore.bookmarks, handleSetStartingUrl, removeBookmark, removeDefaultApp, removedDefaultApps]) + // BookmarksScene will be defined after toggleInfoDrawer const HistoryScene = React.useCallback(() => { return ( @@ -1559,10 +1745,40 @@ function Browser() { /* INFO DRAWER NAV */ /* -------------------------------------------------------------------------- */ const toggleInfoDrawer = useCallback((open: boolean, route: typeof infoDrawerRoute = 'root') => { + console.log('toggleInfoDrawer called with:', { open, route, isFullscreen, showInfoDrawer }) setInfoDrawerRoute(route) setShowInfoDrawer(open) + console.log('After setShowInfoDrawer, new value should be:', open) }, []) + const BookmarksScene = useMemo(() => { + return () => ( + { + // Filter out invalid URLs to prevent favicon errors + return ( + bookmark.url && + bookmark.url !== kNEW_TAB_URL && + isValidUrl(bookmark.url) && + !bookmark.url.includes('about:blank') + ) + }) + .reverse()} + setStartingUrl={handleSetStartingUrl} + onRemoveBookmark={removeBookmark} + onRemoveDefaultApp={removeDefaultApp} + removedDefaultApps={removedDefaultApps} + onCloseModal={() => { + // Just close the drawer - the bookmark is already added by the component + toggleInfoDrawer(false) + }} + hideHeader={true} + showOnlyBookmarks={true} + /> + ) + }, [bookmarkStore.bookmarks, handleSetStartingUrl, removeBookmark, removeDefaultApp, removedDefaultApps, toggleInfoDrawer]) + useEffect(() => { Animated.timing(drawerAnim, { toValue: showInfoDrawer ? 1 : 0, @@ -1788,9 +2004,12 @@ function Browser() { injectedJavaScript={injectedJavaScript} onNavigationStateChange={handleNavStateChange} userAgent={isDesktopView ? desktopUserAgent : mobileUserAgent} + mediaPlaybackRequiresUserAction={false} + allowsInlineMediaPlayback={true} + // Deny all WebView permissions to prevent native camera access + onPermissionRequest={() => false} onError={(syntheticEvent: any) => { const { nativeEvent } = syntheticEvent - // Ignore favicon errors for about:blank if (nativeEvent.url?.includes('favicon.ico') && activeTab?.url === kNEW_TAB_URL) { return } @@ -1833,16 +2052,16 @@ function Browser() { > {/* deggen: Back Button unless address bar is active, in which case it's the share button */} {addressFocused ? null - : - - } - + : + + } + {activeTab?.canGoForward && )} - {Platform.OS !== 'ios' && ( { + const handleGetStarted = useCallback(async () => { try { await analytics().logEvent('get_started_tapped', { screen: 'onboarding' @@ -53,7 +53,6 @@ export default function LoginScreen() { throw new Error(`Failed to fetch info: ${res.status}`) } const wabInfo = await res.json() - console.log({ wabInfo, selectedWabUrl, selectedMethod, selectedNetwork, selectedStorageUrl }) const finalConfig = { wabUrl: selectedWabUrl, wabInfo, @@ -85,7 +84,7 @@ export default function LoginScreen() { } finally { setLoading(false) } - } + }, [selectedWabUrl, selectedMethod, selectedNetwork, selectedStorageUrl, finalizeConfig, setItem, getSnap, managers?.walletManager]) // Config modal state const [showConfig, setShowConfig] = useState(false) @@ -102,11 +101,8 @@ export default function LoginScreen() { const handleConfigured = async () => { // After successful config, proceed with auth try { - const finalConfig = JSON.parse((await getItem('finalConfig')) || '') - const success = finalizeConfig(finalConfig) - if (!success) { - return - } + // The ConfigModal has already called finalizeConfig() with the new configuration + // No need to load from storage - the wallet context already has the updated values const snap = await getSnap() if (!snap) { router.push('/auth/phone') @@ -175,7 +171,17 @@ export default function LoginScreen() { marginTop: 12 } ]} - onPress={() => { + onPress={handleConfig} + > + + + {t('configure_providers')} + + + + {t('terms_privacy_agreement')} + + { // Set mode to web2 immediately when button is pressed setWeb2Mode(true) @@ -189,22 +195,18 @@ export default function LoginScreen() { handleGetStarted() } ) - }} - > - Continue without login - - - {t('terms_privacy_agreement')} - - - - - {t('configure_providers')} - + }}> + {t('continue_without_login')} )} + + ) } @@ -226,7 +228,7 @@ const styles = StyleSheet.create({ width: 100, height: 100, borderRadius: 50, - backgroundColor: '#0066cc', + backgroundColor: '#487dbf', justifyContent: 'center', alignItems: 'center' }, @@ -257,12 +259,21 @@ const styles = StyleSheet.create({ fontSize: 16, fontWeight: 'bold' }, + continueButtonText: { + fontSize: 16, + fontWeight: 'bold', + color: '#0066cc' + }, configButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', marginTop: 50, - padding: 10 + padding: 10, + borderWidth: 1, + borderColor: '#487dbf', + borderRadius: 10, + width: '60%' }, configIconContainer: { flexDirection: 'row', @@ -270,7 +281,6 @@ const styles = StyleSheet.create({ marginLeft: 8 }, configButtonText: { - color: '#0066cc', fontSize: 14, marginLeft: 2 }, diff --git a/app/settings.tsx b/app/settings.tsx index 244b5f7..4acb611 100644 --- a/app/settings.tsx +++ b/app/settings.tsx @@ -10,7 +10,7 @@ export default function SettingsScreen() { const { t } = useTranslation() const { colors, mode, setThemeMode } = useTheme() const styles = useThemeStyles() - const { updateSettings, settings, logout } = useWallet() + const { updateSettings, settings, logout, selectedWabUrl, selectedStorageUrl, selectedNetwork } = useWallet() // Handle theme mode change const handleThemeChange = async (newMode: ThemeMode) => { @@ -30,9 +30,95 @@ export default function SettingsScreen() { return ( - - - {t('settings')} + + {/* Account Section */} + + {t('wallet_configuration')} + + {/* Wallet Configuration URLs */} + + {/* WAB URL */} + + + {t('wab_url')} + + + + {selectedWabUrl || 'Not configured'} + + + + + {/* Wallet Storage URL */} + + + {t('wallet_storage_url')} + + + + {selectedStorageUrl || 'Not configured'} + + + + + {/* NETWORK */} + + + {t('bsv_network')} + + + + {selectedNetwork || 'Not configured'} + + + + + {/* Explanation text */} + + {t('logout_to_change_urls')} + + + + + + + {t('logout')} + + + {/* Theme Section */} @@ -103,23 +189,7 @@ export default function SettingsScreen() { - {/* Account Section */} - - {t('account')} - - - - - {t('logout')} - - - - {/* Other Settings Sections can be added here */} - ) diff --git a/components/RecommendedApps.tsx b/components/RecommendedApps.tsx index a668165..9f9e64a 100644 --- a/components/RecommendedApps.tsx +++ b/components/RecommendedApps.tsx @@ -1,4 +1,5 @@ import React, { useState, useMemo, useCallback } from 'react' +import { observer } from 'mobx-react-lite' import { View, Text, @@ -17,6 +18,8 @@ import { useTheme } from '@/context/theme/ThemeContext' import { useWallet } from '@/context/WalletContext' import { useBrowserMode } from '@/context/BrowserModeContext' import { useTranslation } from 'react-i18next' +import bookmarkStore from '@/stores/BookmarkStore' +import tabStore from '@/stores/TabStore' interface App { domain: string @@ -33,6 +36,7 @@ interface RecommendedAppsProps { onRemoveBookmark?: (url: string) => void onRemoveDefaultApp?: (url: string) => void removedDefaultApps?: string[] + onCloseModal?: () => void // Handler to close the modal // Homepage customization props homepageSettings?: { showBookmarks: boolean @@ -74,7 +78,7 @@ const defaultApps: App[] = [ /* RECOMMENDED APPS COMPONENT */ /* -------------------------------------------------------------------------- */ -export const RecommendedApps = ({ +export const RecommendedApps = observer(({ setStartingUrl, includeBookmarks = [], hideHeader = false, @@ -83,6 +87,7 @@ export const RecommendedApps = ({ onRemoveBookmark, onRemoveDefaultApp, removedDefaultApps = [], + onCloseModal, homepageSettings, onUpdateHomepageSettings }: RecommendedAppsProps) => { @@ -92,7 +97,7 @@ export const RecommendedApps = ({ const { t } = useTranslation() const [searchQuery, setSearchQuery] = useState('') const [showCustomizeModal, setShowCustomizeModal] = useState(false) - const [isDesktopView, setIsDesktopView] = useState(false) + const [bookmarkRefresh, setBookmarkRefresh] = useState(0) // Context menu state const [contextMenuVisible, setContextMenuVisible] = useState(false) @@ -140,6 +145,21 @@ export const RecommendedApps = ({ setSelectedApp(null) }, []) + const handleAddBookmark = useCallback(() => { + const activeTab = tabStore.activeTab + if (activeTab && activeTab.url && activeTab.url !== 'about:blank' && !activeTab.url.includes('metanet://')) { + const title = activeTab.title || activeTab.url + bookmarkStore.addBookmark(title, activeTab.url) + // Close the modal after adding bookmark + if (onCloseModal) { + // Use setTimeout to ensure the bookmark is added first, then close + setTimeout(() => { + onCloseModal() + }, 100) + } + } + }, [onCloseModal]) + /* -------------------------- prepare separate data sources -------------------------- */ const filteredDefaultApps = useMemo(() => { if (showOnlyBookmarks) return [] @@ -160,7 +180,11 @@ export const RecommendedApps = ({ }, [recentApps, showOnlyBookmarks, homepageSettings, isWeb2Mode]) const processedBookmarks = useMemo(() => { - const bookmarks = includeBookmarks.map(bm => ({ + // Always use bookmarks directly from the store to ensure reactivity + // This ensures the component updates when bookmarks are added/removed + const storeBookmarks = bookmarkStore.bookmarks || [] + console.log('Processing bookmarks, count:', storeBookmarks.length) + const bookmarks = storeBookmarks.map(bm => ({ domain: bm.url, appName: bm.title || bm.url, appIconImageUrl: `${bm.url.replace(/\/$/, '')}/favicon.ico` @@ -172,7 +196,7 @@ export const RecommendedApps = ({ } return bookmarks - }, [includeBookmarks, showOnlyBookmarks, limitBookmarks]) + }, [bookmarkStore.bookmarks, showOnlyBookmarks, limitBookmarks, bookmarkRefresh]) // Combined for search functionality const allApps = useMemo(() => { @@ -242,6 +266,9 @@ export const RecommendedApps = ({ return ( + + {/* Removed Add Bookmark button from top - now at bottom */} + {showOnlyBookmarks && ( + {/* Add Bookmark Button - Only show in bookmark modal */} + {showOnlyBookmarks && ( + + + + {t('add_bookmark')} + + + )} + {/* Customize Homepage Modal */} {showCustomizeModal && homepageSettings && onUpdateHomepageSettings && ( ) -} +}) /* -------------------------------------------------------------------------- */ /* CSS */ @@ -683,5 +724,23 @@ const componentStyles = StyleSheet.create({ backgroundColor: 'rgba(0,0,0,1)', justifyContent: 'center', alignItems: 'center' + }, + addBookmarkSection: { + padding: 16, + paddingBottom: 36, + borderTopWidth: StyleSheet.hairlineWidth, + borderTopColor: 'rgba(0,0,0,0.1)' + }, + addBookmarkButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + padding: 16, + borderRadius: 8, + gap: 8 + }, + addBookmarkText: { + fontSize: 16, + fontWeight: '600' } }) diff --git a/components/RecoveryKeySaver.tsx b/components/RecoveryKeySaver.tsx index 135a8a6..5fbca6f 100644 --- a/components/RecoveryKeySaver.tsx +++ b/components/RecoveryKeySaver.tsx @@ -127,13 +127,11 @@ const RecoveryKeySaver = () => { - Secure Access Backup and Recovery + Save Your Recovery Key Now {!affirmative1 && ( - Save Your Recovery Key Now: - { color={colors.buttonText} style={styles.buttonIcon} /> - Copy to Clipboard + Copy { setAffirmative1(!affirmative1)} - label="I have saved my recovery key in a secure location" + label="I have saved my recovery key" /> {affirmative1 && ( - Any 2 of 3 factors are required to access your data: - - - - Phone, Password, Recovery Key - - - - When you lose your phone or forget your password, you must use the other factors to re-establish - secure control. This is a perfectly normal and unavoidable fact of life. However - + Any 2 of 3 factors are required to access your data: Phone, Password, Recovery Key - Loss of more than one factor will result in TOTAL LOSS of access to all assets, encrypted data, - and certificates. + Loss of two factors = PERMANENT TOTAL LOSS of all assets, and data. @@ -231,15 +219,15 @@ const RecoveryKeySaver = () => { style={[ styles.buttonPrimary, { - backgroundColor: isAllChecked ? '#006600' : colors.inputBorder, - opacity: isAllChecked ? 1 : 0.5 + backgroundColor: colors.buttonBackground, + opacity: isAllChecked ? 1 : 0.2 } ]} onPress={onKeySaved} disabled={!isAllChecked} > Securely Saved - + @@ -257,7 +245,6 @@ const styles = StyleSheet.create({ alignItems: 'center' }, keyboardAvoid: { - width: '90%', maxWidth: 500 }, modalContent: { @@ -271,7 +258,6 @@ const styles = StyleSheet.create({ shadowOpacity: 0.25, shadowRadius: 3.84, elevation: 5, - maxHeight: '80%' }, scrollView: { maxHeight: 450, @@ -319,7 +305,7 @@ const styles = StyleSheet.create({ flex: 0.48 }, buttonIcon: { - marginRight: 8 + marginLeft: 8 }, warningBox: { borderWidth: 1, @@ -379,7 +365,7 @@ const styles = StyleSheet.create({ }, buttonText: { fontSize: 16, - fontWeight: '500' + fontWeight: '500', }, buttonSecondaryText: { fontSize: 16 diff --git a/components/Web3BenefitsModal.tsx b/components/Web3BenefitsModal.tsx index f668e78..ac718b3 100644 --- a/components/Web3BenefitsModal.tsx +++ b/components/Web3BenefitsModal.tsx @@ -63,36 +63,35 @@ const Web3BenefitsModal: React.FC = ({ {/* Content - Simple approach */} - The benefits of web3 are as follows: + A Web3 identity brings you: + Micropayments - • Never login again - One identity for every Web3 app. No more passwords or sign-ups. + where creators to earn directly. + Private Identity - • Instant everything - Payments, access, verification - all happen in seconds. + with mutual auth means no signups or logins. + Data Sovereignty - • You own your data - No companies tracking you or selling your information. - - - • Works everywhere - Access thousands of Web3 apps with the same identity. - - - • Future-proof - Be early to the next generation of the internet. + you're in control, with no 3rd party tracking. + Become an early adopter and lead your peers to the future of everything. + {/* Action Buttons */} - 🚀 Get My Web3 Identity (30s) + Get Started - Maybe later + Maybe Later @@ -165,10 +164,13 @@ const styles = StyleSheet.create({ paddingHorizontal: 12, alignItems: 'center', alignSelf: 'center', - marginTop: 8 + marginTop: 8, + borderRadius: 12, + borderWidth: 1, + borderColor: '#666' }, secondaryButtonText: { - fontSize: 11, + fontSize: 14, fontWeight: '300', opacity: 0.5 } diff --git a/context/WalletContext.tsx b/context/WalletContext.tsx index f8a0e67..7903958 100644 --- a/context/WalletContext.tsx +++ b/context/WalletContext.tsx @@ -742,7 +742,7 @@ export const WalletContextProvider: React.FC = ({ children = passwordRetriever, recoveryKeySaver, configStatus, - managers.walletManager, + walletBuilt, selectedNetwork, selectedWabUrl, buildWallet, diff --git a/ios/Metanet/Info.plist b/ios/Metanet/Info.plist index 30affe0..39ad9fd 100644 --- a/ios/Metanet/Info.plist +++ b/ios/Metanet/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 0.3.0 + 0.3.2 CFBundleSignature ???? CFBundleURLTypes diff --git a/package-lock.json b/package-lock.json index a17c663..27af7bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "metanet-mobile", - "version": "0.3.0", + "version": "0.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "metanet-mobile", - "version": "0.3.0", + "version": "0.3.2", "license": "Open BSV", "dependencies": { - "@bsv/message-box-client": "^1.2.3", - "@bsv/sdk": "^1.6.20", - "@bsv/wallet-toolbox-mobile": "^1.5.20", + "@bsv/message-box-client": "^1.2.6", + "@bsv/sdk": "^1.6.24", + "@bsv/wallet-toolbox-mobile": "^1.6.4", "@dicebear/core": "^9.2.2", "@dicebear/identicon": "^9.2.2", "@expo/vector-icons": "^14.1.0", @@ -1595,9 +1595,9 @@ } }, "node_modules/@bsv/message-box-client": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@bsv/message-box-client/-/message-box-client-1.2.4.tgz", - "integrity": "sha512-nU7bxo4JHNCZfO+8qF8BgmQBfouhbKq+JoLZCD0Ljh8XqpkvMeAy1mj5etk8aYDkVEJ+qSyGD01NkPfJ6P6shA==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@bsv/message-box-client/-/message-box-client-1.2.6.tgz", + "integrity": "sha512-3Ul7IfwtchJLNRFc6YUxMRD3at+6xoM0Yjhp8XUVCSovMWcBOLz0yrzN9DsTN9DXordBWprkjd96zboSo3klbw==", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@bsv/authsocket-client": "^1.0.11", @@ -1605,18 +1605,18 @@ } }, "node_modules/@bsv/sdk": { - "version": "1.6.23", - "resolved": "https://registry.npmjs.org/@bsv/sdk/-/sdk-1.6.23.tgz", - "integrity": "sha512-l2ElERB49x1yud1kC3kP5E8s1SFw379bLJE+TRXLyaP2Gbje/5FFlvkjg7e78Xni3fYSUHfHHIsxmWhbE/Ls4A==", + "version": "1.6.24", + "resolved": "https://registry.npmjs.org/@bsv/sdk/-/sdk-1.6.24.tgz", + "integrity": "sha512-st8LfNZGyj5EHPedruTfttmMQjw9NeX/TGlX+hq+TG3rAzpw17AxY8SlseDzIa5ZrEcgIxuhtEaeKD+kbphqDA==", "license": "SEE LICENSE IN LICENSE.txt" }, "node_modules/@bsv/wallet-toolbox-mobile": { - "version": "1.5.21", - "resolved": "https://registry.npmjs.org/@bsv/wallet-toolbox-mobile/-/wallet-toolbox-mobile-1.5.21.tgz", - "integrity": "sha512-P7tqwNrrB1ioFPCyBwG/j025ABKdFWfHnQ3iR7IueYXZMon6K2pxveUNMkLgWFCCsSXY7uGZwb+gtudAwBT29w==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@bsv/wallet-toolbox-mobile/-/wallet-toolbox-mobile-1.6.4.tgz", + "integrity": "sha512-OXHkWJ7YElZ1klRXDQWIyCw3njFaplXtxsNhjxaKU1dHfuNJsaBD9kJOHXJ80uBh9exWeFFLNquozM7Sg7D7Rg==", "license": "SEE LICENSE IN license.md", "dependencies": { - "@bsv/sdk": "^1.6.23" + "@bsv/sdk": "^1.6.24" } }, "node_modules/@callstack/react-theme-provider": { diff --git a/package.json b/package.json index 8f3447a..0243434 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "Enabling identity, micropayments, and use of Metanet websites on mobile devices", "author": "BSV Association", "main": "index.js", - "version": "0.3.0", + "version": "0.3.2", "license": "Open BSV", "scripts": { "apk-android": "eas build --profile production --platform android --local --verbose-logs --build-logger-level trace", @@ -25,9 +25,9 @@ "clean": "rm -rf yarn.lock && rm -rf .expo && rm -rf .expo-shared && rm -rf .vscode && rm -rf .idea && rm -rf ios/Pods && rm -rf android/.gradle && rm -rf android/app/build && rm -rf ./ios" }, "dependencies": { - "@bsv/message-box-client": "^1.2.3", - "@bsv/sdk": "^1.6.20", - "@bsv/wallet-toolbox-mobile": "^1.5.20", + "@bsv/message-box-client": "^1.2.6", + "@bsv/sdk": "^1.6.24", + "@bsv/wallet-toolbox-mobile": "^1.6.4", "@dicebear/core": "^9.2.2", "@dicebear/identicon": "^9.2.2", "@expo/vector-icons": "^14.1.0", diff --git a/plugins/with-fcm-manifest-fixes.js b/plugins/with-fcm-manifest-fixes.js index 34d0b9a..c2a6227 100644 --- a/plugins/with-fcm-manifest-fixes.js +++ b/plugins/with-fcm-manifest-fixes.js @@ -2,7 +2,8 @@ const { withAndroidManifest, AndroidConfig } = require('@expo/config-plugins'); const withFcmManifestFixes = (config) => { return withAndroidManifest(config, (cfg) => { - const { manifest } = cfg.modResults; + const androidManifest = cfg.modResults; + const { manifest } = androidManifest; // Ensure tools namespace on manifest.$ = manifest.$ || {}; @@ -11,7 +12,7 @@ const withFcmManifestFixes = (config) => { } // Get - const app = AndroidConfig.Manifest.getMainApplication(manifest); + const app = AndroidConfig.Manifest.getMainApplicationOrThrow(androidManifest); if (!app) { throw new Error('Main application not found in AndroidManifest'); } // Helper to upsert by android:name diff --git a/utils/translations.tsx b/utils/translations.tsx index 09d7aca..42b59ec 100644 --- a/utils/translations.tsx +++ b/utils/translations.tsx @@ -185,6 +185,9 @@ const resources = { system_default: 'System Default', account: 'Account', logout: 'Logout', + wallet_configuration: 'Wallet Configuration', + wallet_storage_url: 'Wallet Storage URL', + logout_to_change_urls: 'You need to logout to change these URLs.', // Security security: 'Security', @@ -411,6 +414,9 @@ const resources = { system_default: '系统默认', account: '账户', logout: '退出登录', + wallet_configuration: '钱包配置', + wallet_storage_url: '钱包存储URL', + logout_to_change_urls: '您需要退出登录以更改这些URL。', // Security security: '安全', @@ -637,6 +643,9 @@ const resources = { system_default: 'सिस्टम डिफ़ॉल्ट', account: 'खाता', logout: 'लॉगआउट', + wallet_configuration: 'वॉलेट कॉन्फ़िगरेशन', + wallet_storage_url: 'वॉलेट स्टोरेज URL', + logout_to_change_urls: 'इन URLs को बदलने के लिए आपको लॉगआउट करना होगा।', // Security security: 'सुरक्षा',