Fix error and info modal on Android

This commit is contained in:
minibits-cash
2026-03-24 15:20:39 +01:00
parent 9aca87f66a
commit 26aac2dee8
12 changed files with 71 additions and 55 deletions

2
.gitignore vendored
View File

@@ -5,7 +5,7 @@ minibits-upload-key.keystore
GoogleService-Info.plist
minibits-prod-firebase-adminsdk.json
dist/
zapstore
zsp
# OSX
#

View File

@@ -31,7 +31,7 @@
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"

View File

@@ -1,6 +1,6 @@
{
"name": "minibits_wallet",
"version": "0.4.0-beta.2",
"version": "0.4.0-beta.3",
"private": true,
"scripts": {
"android:clean": "cd android && ./gradlew clean",

View File

@@ -59,7 +59,7 @@ export const AmountInput = forwardRef<TextInput, AmountInputProps>(
const [hasBottomAmountFocusedOnce, setHasBottomAmountFocusedOnce] = useState(false)
const [hasBeenFirstTimeConverted, setHasBeenFirstTimeConverted] = useState<boolean>(false)
const amountInputColor = useThemeColor("amountInput")
const focusedInputColor = useThemeColor("amountInput")
const convertedAmountColor = useThemeColor("headerSubTitle")
const symbolColor = useThemeColor("headerSubTitle")
@@ -220,7 +220,7 @@ export const AmountInput = forwardRef<TextInput, AmountInputProps>(
fontFamily: typography.primary?.bold,
fontWeight: 'bold', // android
textAlign: "center",
color: amountInputColor,
color: focusedInputColor,
}
const animatedTopStyle = useAnimatedStyle(() => ({
@@ -273,7 +273,12 @@ export const AmountInput = forwardRef<TextInput, AmountInputProps>(
onEndEditing={onAmountEndEditing}
onFocus={handleTopFocus}
onBlur={handleTopBlur}
style={[defaultTopStyle, style, animatedTopStyle]}
style={[
defaultTopStyle,
style,
animatedTopStyle,
{ color: focused === 'top' ? focusedInputColor : convertedAmountColor }
]}
maxLength={9}
keyboardType="decimal-pad"
returnKeyType="done"
@@ -307,6 +312,7 @@ export const AmountInput = forwardRef<TextInput, AmountInputProps>(
style,
{ color: convertedAmountColor },
animatedBottomStyle,
{ color: focused === 'bottom' ? focusedInputColor : convertedAmountColor }
]}
maxLength={9}
keyboardType="decimal-pad"

View File

@@ -23,7 +23,8 @@ interface ModalProps extends ViewProps {
top?: number
onBackdropPress?: any
onBackButtonPress?: any
backdropOpacity?: number
onModalHide?: () => void
backdropOpacity?: number
/**
* The heading text to display if not using `headingTx`.
*/
@@ -109,11 +110,12 @@ interface ModalProps extends ViewProps {
*/
export function BottomModal(props: ModalProps) {
const {
isVisible = true,
isVisible = true,
onBackdropPress,
onBackButtonPress,
onModalHide,
//backdropOpacity = Platform.OS === 'ios' ? 0.25 : 0,
backdropOpacity = 0.25,
backdropOpacity = 0.25,
content,
contentTx,
contentTxOptions,
@@ -175,6 +177,7 @@ export function BottomModal(props: ModalProps) {
avoidKeyboard={Platform.OS === 'ios'}
onBackdropPress={onBackdropPress}
onBackButtonPress={onBackButtonPress}
onModalHide={onModalHide}
backdropOpacity={backdropOpacity}
useNativeDriverForBackdrop={true}
hideModalContentWhileAnimating={true}

View File

@@ -8,6 +8,7 @@ import JSONTree from 'react-native-json-tree'
import Clipboard from '@react-native-clipboard/clipboard'
import { useStores } from '../models'
import useIsInternetReachable from '../utils/useIsInternetReachable'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
type ErrorModalProps = {
error: AppError
@@ -58,22 +59,23 @@ export const ErrorModal: FC<ErrorModalProps> = function ({ error }) {
}
const bg = useThemeColor('error')
const insets = useSafeAreaInsets()
return (
<BottomModal
isVisible={isErrorVisible}
onBackdropPress={onClose}
onBackButtonPress={onClose}
onBackButtonPress={onClose}
style={{ marginBottom: Platform.OS === 'android' ? -(insets.top + insets.bottom) : 0 }}
ContentComponent={
<>
<View>
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: spacing.small }}>
<Icon icon="faTriangleExclamation" size={spacing.large} color={bg} />
<Text style={{ marginLeft: spacing.small }}>{error.name}</Text>
</View>
<ScrollView
style={{
//height: spacing.screenHeight * 0.1,
maxHeight: spacing.screenHeight * 0.07,
style={{
maxHeight: spacing.screenHeight * 0.1,
}}>
<Text style={{ marginBottom: spacing.small }}>{error.message}</Text>
</ScrollView>
@@ -147,7 +149,7 @@ export const ErrorModal: FC<ErrorModalProps> = function ({ error }) {
</>
)}
</>
</View>
}
/>
)

View File

@@ -1,7 +1,8 @@
import React, { FC, useState, useEffect } from 'react'
import { View } from 'react-native'
import { Platform, View } from 'react-native'
import { BottomModal, Icon, Text } from '../components'
import { spacing, useThemeColor } from '../theme'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
type InfoModalProps = {
@@ -25,12 +26,14 @@ export const InfoModal: FC<InfoModalProps> = function ({ message }) {
const backgroundColor = useThemeColor('info')
const iconColor = useThemeColor('textDim')
const insets = useSafeAreaInsets()
return (
<BottomModal
isVisible={isInfoVisible}
onBackdropPress={() => onClose()}
onBackButtonPress={() => onClose()}
style={{ marginBottom: Platform.OS === 'android' ? -(insets.top + insets.bottom) : 0 }}
ContentComponent={
<View style={{ padding: spacing.small, flexDirection: 'row', alignItems: 'center', marginRight: spacing.medium}}>
<Icon icon="faInfoCircle" size={spacing.large} color={iconColor} />

View File

@@ -1,23 +1,21 @@
import {observer} from 'mobx-react-lite'
import React, {useEffect, useRef, useState} from 'react'
import {FlatList, TextInput, TextStyle, View, ViewStyle} from 'react-native'
import {FlatList, Platform, TextInput, TextStyle, View, ViewStyle} from 'react-native'
import {verticalScale} from '@gocodingnow/rn-size-matters'
import {colors, spacing, useThemeColor} from '../../theme'
import {BottomModal, Button, Card, ErrorModal, Icon, InfoModal, ListItem, Loading, Screen, Text} from '../../components'
import {useStores} from '../../models'
import { MinibitsClient, NostrClient, NostrProfile } from '../../services'
import { NostrClient, NostrProfile } from '../../services'
import AppError, { Err } from '../../utils/AppError'
import {MINIBITS_NIP05_DOMAIN} from '@env'
import { log } from '../../services/logService'
import { ContactListItem } from './ContactListItem'
import { Contact, ContactType } from '../../models/Contact'
import { StackNavigationProp } from '@react-navigation/stack'
import { ReceiveOption } from '../ReceiveScreen'
import { SendOption } from '../SendScreen'
import { infoMessage, warningMessage } from '../../utils/utils'
import { IncomingDataType, IncomingParser } from '../../services/incomingParser'
import { translate } from '../../i18n'
import { RouteProp, useNavigation } from '@react-navigation/native'
import { useNavigation } from '@react-navigation/native'
import { toJS } from 'mobx'
import { TransferOption } from '../TransferScreen'
@@ -32,11 +30,12 @@ export const PrivateContacts = observer(function (props: {
const contactNameInputRef = useRef<TextInput>(null)
const [info, setInfo] = useState('')
const [newContactName, setNewContactName] = useState<string>('')
const [isLoading, setIsLoading] = useState(false)
const [isExternalDomain, setIsExternalDomain] = useState(false)
const [isNewContactModalVisible, setIsNewContactModalVisible] = useState(false)
const [newContactName, setNewContactName] = useState<string>('')
const [isLoading, setIsLoading] = useState(false)
const [isExternalDomain, setIsExternalDomain] = useState(false)
const [isNewContactModalVisible, setIsNewContactModalVisible] = useState(false)
const [error, setError] = useState<AppError | undefined>()
const [pendingError, setPendingError] = useState<AppError | undefined>()
useEffect(() => {
const { paymentOption } = props
@@ -127,7 +126,11 @@ export const PrivateContacts = observer(function (props: {
} catch(e: any) {
setNewContactName('')
setIsExternalDomain(false)
handleError(e)
if (Platform.OS === 'ios') {
setPendingError(e)
} else {
handleError(e)
}
}
}
@@ -308,6 +311,12 @@ export const PrivateContacts = observer(function (props: {
</View>
<BottomModal
isVisible={isNewContactModalVisible ? true : false}
onModalHide={() => {
if (pendingError) {
handleError(pendingError)
setPendingError(undefined)
}
}}
ContentComponent={
<View style={$newContainer}>
<Text tx="contactsScreen_newTitle" preset="subheading" />

View File

@@ -219,11 +219,8 @@ export const PublicContactsNew = observer(function (props: {
setFollowingProfiles(following)
setIsLoading(false)
}
InteractionManager.runAfterInteractions(async () => {
loadProfiles()
})
loadProfiles()
}, [followingPubkeys])

View File

@@ -49,6 +49,7 @@ export const MintsScreen = observer(function MintsScreen({ route }: Props) {
const [selectedMint, setSelectedMint] = useState<Mint | undefined>()
const [info, setInfo] = useState('')
const [error, setError] = useState<AppError | undefined>()
const [pendingError, setPendingError] = useState<AppError | undefined>()
const [isLoading, setIsLoading] = useState(false)
const [isAddMintVisible, setIsAddMintVisible] = useState(false)
const [isMintMenuVisible, setIsMintMenuVisible] = useState(false)
@@ -119,7 +120,11 @@ export const MintsScreen = observer(function MintsScreen({ route }: Props) {
setInfo(translate('mintsScreen_mintAdded'))
} catch (e: any) {
setMintUrl('')
handleError(e)
if (Platform.OS === 'ios') {
setPendingError(e)
} else {
handleError(e)
}
} finally {
setMintUrl('')
setIsLoading(false)
@@ -425,6 +430,12 @@ export const MintsScreen = observer(function MintsScreen({ route }: Props) {
/>
<BottomModal
isVisible={isAddMintVisible ? true : false}
onModalHide={() => {
if (pendingError) {
handleError(pendingError)
setPendingError(undefined)
}
}}
ContentComponent={
<View style={$bottomModal}>
<Text

View File

@@ -27,40 +27,25 @@ export const getImageSource = function(img: string) {
export const infoMessage = function(message: string, description?: string) {
const backgroundColor = colors.palette.success300
const backgroundColor = colors.palette.neutral500
const textColor = 'white'
return showMessage({
message,
description,
description,
duration: 3000,
backgroundColor,
color: textColor,
style: {
minHeight: spacing.screenHeight * 0.15,
borderTopLeftRadius: spacing.medium,
borderTopRightRadius: spacing.medium
minHeight: spacing.screenHeight * 0.05,
borderRadius: spacing.medium,
margin: spacing.large,
marginBottom: spacing.large * 4,
},
})
}
export const warningMessage = function(message: string, description?: string) {
const backgroundColor = colors.palette.accent500
return showMessage({
message,
description,
duration: 3000,
backgroundColor,
color: 'white',
style: {
minHeight: spacing.screenHeight * 0.15,
borderTopLeftRadius: spacing.medium,
borderTopRightRadius: spacing.medium
},
})
}
export const generateId = function (lengthInBytes: number) {
const random = QuickCrypto.randomBytes(lengthInBytes)
const uint8Array = new Uint8Array(random)

View File

@@ -1,5 +1,5 @@
name: Minibits Wallet
summary: Minibits is an ecash and Lightning wallet exploring how ₿-backed ecash can enable instant, cheap, and private value transfer.
summary: Minibits is a Bitcoin Lightning and ecash wallet that deliver instant, low-cost, and private value transfers — even when the payer is offline.
repository: https://github.com/minibits-cash/minibits_wallet
homepage: https://minibits.cash/
assets: