Bug fixes and UI tunings for smaller screens

This commit is contained in:
minibits-cash
2023-08-29 01:17:05 +02:00
parent 5b4787536a
commit a648bb8510
12 changed files with 173 additions and 113 deletions
+15
View File
@@ -0,0 +1,15 @@
> Why do I have a folder named ".expo" in my project?
The ".expo" folder is created when an Expo project is started using "expo start" command.
> What do the files contain?
- "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds.
- "packager-info.json": contains port numbers and process PIDs that are used to serve the application to the mobile device/simulator.
- "settings.json": contains the server configuration that is used to serve the application manifest.
> Should I commit the ".expo" folder?
No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine.
Upon project creation, the ".expo" folder is already added to your ".gitignore" file.
+8
View File
@@ -0,0 +1,8 @@
{
"hostType": "lan",
"lanType": "ip",
"dev": true,
"minify": false,
"urlRandomness": null,
"https": false
}
+3 -3
View File
@@ -1,4 +1,4 @@
<img src="https://minibits.cash/img/minibits_preview.png">
<img src="https://www.minibits.cash/img/minibits_preview.png">
# Disclaimer
@@ -103,7 +103,7 @@ As a result, the following architectural constraints are in place:
- Use fast storage for most wallet operations and separate local database storage to store data that incrementally grows.
- Leverage local database as an append-only coins backup independent from fast storage.
<img src="https://minibits.cash/img/minibits_architecture.png">
<img src="https://www.minibits.cash/img/minibits_architecture_v2.png">
Open architectural concepts worth wider discussion
- [ ] Contacts management - identities, sharing contacts, send coins with the UX of tradfi instant payment while keeping privacy towards mints
@@ -114,7 +114,7 @@ Open architectural concepts worth wider discussion
## Download and test
Minibits wallet is in alpha and available as of now only for Android devices. You have the following options to try it out:
- [x] Join testing program on Google Play (Closed testing ongoing, submit your email to get an invite on [Minibits.cash](https://minibits.cash))
- [x] Join testing program on Google Play (Closed testing ongoing, submit your email to get an invite on [Minibits.cash](https://www.minibits.cash))
- [X] Download .apk file from Releases page and install it on your phone
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "minibits_wallet",
"version": "0.0.8-alpha",
"version": "0.0.8-alpha.2",
"private": true,
"scripts": {
"android:clean": "cd android && ./gradlew clean",
+8 -7
View File
@@ -1,3 +1,4 @@
import { moderateVerticalScale } from "@gocodingnow/rn-size-matters"
import { I18nOptions } from "i18n-js"
import React from "react"
import { StyleProp, Text as RNText, TextProps as RNTextProps, TextStyle } from "react-native"
@@ -75,13 +76,13 @@ export function Text(props: TextProps) {
}
export const $sizeStyles = {
xxl: { fontSize: 36, lineHeight: 44 } as TextStyle,
xl: { fontSize: 24, lineHeight: 34 } as TextStyle,
lg: { fontSize: 20, lineHeight: 32 } as TextStyle,
md: { fontSize: 18, lineHeight: 26 } as TextStyle,
sm: { fontSize: 16, lineHeight: 24 } as TextStyle,
xs: { fontSize: 14, lineHeight: 21 } as TextStyle,
xxs: { fontSize: 12, lineHeight: 18 } as TextStyle,
xxl: { fontSize: moderateVerticalScale(36), lineHeight: moderateVerticalScale(44) } as TextStyle,
xl: { fontSize: moderateVerticalScale(24), lineHeight: moderateVerticalScale(34) } as TextStyle,
lg: { fontSize: moderateVerticalScale(20), lineHeight: moderateVerticalScale(32) } as TextStyle,
md: { fontSize: moderateVerticalScale(18), lineHeight: moderateVerticalScale(26) } as TextStyle,
sm: { fontSize: moderateVerticalScale(16), lineHeight: moderateVerticalScale(24) } as TextStyle,
xs: { fontSize: moderateVerticalScale(14), lineHeight: moderateVerticalScale(21) } as TextStyle,
xxs: { fontSize: moderateVerticalScale(12), lineHeight: moderateVerticalScale(18) } as TextStyle,
}
+29 -12
View File
@@ -6,9 +6,11 @@ import {ContactsStackScreenProps} from '../navigation'
import {Icon, Screen, Text, Card, BottomModal, Button, InfoModal, ErrorModal, ListItem} from '../components'
import {useHeader} from '../utils/useHeader'
import {useStores} from '../models'
import AppError from '../utils/AppError'
import AppError, { Err } from '../utils/AppError'
import Clipboard from '@react-native-clipboard/clipboard'
import { ContactType } from '../models/Contact'
import { WalletProfileRecord } from '../models/WalletProfileStore'
import { MinibitsClient } from '../services'
interface ContactDetailScreenProps extends ContactsStackScreenProps<'ContactDetail'> {}
@@ -73,7 +75,7 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
}
}
const onSendCoins = function () {
const onSendCoins = async function () {
if(parseInt(amountToSend) > availableBalance) {
setInfo('Amount to send is higher than your available balance.')
return
@@ -85,15 +87,30 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
}
toggleSendModal()
try {
// check before payment that contact name is still linked to the same pubkey
const profileRecord: WalletProfileRecord =
await MinibitsClient.getWalletProfileByWalletId(contact.name as string)
if(!profileRecord || profileRecord.pubkey !== contact.pubkey) {
throw new AppError(Err.VALIDATION_ERROR, `${contact.name} is no longer linked to the public key stored in your contacts. Please get in touch with the payee and update your information.`)
}
return navigation.navigate('WalletNavigator', {
screen: 'Send',
params: {
amountToSend,
contact,
relays
},
})
navigation.navigate('WalletNavigator', {
screen: 'Send',
params: {
amountToSend,
contact,
relays
},
})
return
} catch (e: any) {
handleError(e)
}
}
@@ -194,8 +211,8 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
top={spacing.screenHeight * 0.26}
ContentComponent={
<View style={$payContainer}>
<Text text={`Tip or donate to ${contact.name}`} preset="subheading" />
<Text text={`Available balance is ${availableBalance.toLocaleString()}`} size='xs' style={{color: balanceColor}} />
<Text text={`Send to ${contact.name}`} preset="subheading" />
<Text text={`Available balance is ${availableBalance.toLocaleString()} sats`} size='xs' style={{color: balanceColor}} />
<View style={{alignItems: 'center'}}>
<TextInput
ref={amountToSendInputRef}
+23 -23
View File
@@ -14,6 +14,7 @@ import { ResultModalInfo } from '../Wallet/ResultModalInfo'
import { MINIBITS_NIP05_DOMAIN } from '@env'
import { useFocusEffect } from '@react-navigation/native'
const DEFAULT_DONATION_AMOUNT = 100
export const OwnName = observer(function (props: {navigation: any, pubkey: string}) {
// const navigation = useNavigation()
@@ -24,12 +25,13 @@ export const OwnName = observer(function (props: {navigation: any, pubkey: strin
const [ownName, setOwnName] = useState<string>('')
const [info, setInfo] = useState('')
const [availableBalance, setAvailableBalance] = useState(0)
const [donationAmount, setDonationAmount] = useState(100)
const [donationAmount, setDonationAmount] = useState(DEFAULT_DONATION_AMOUNT)
const [donationInvoice, setDonationInvoice] = useState<{payment_hash: string, payment_request: string} | undefined>(undefined)
const [isLoading, setIsLoading] = useState(false)
const [isPaymentModalVisible, setIsPaymentModalVisible] = useState(false)
const [isQRcodeVisible, setIsQRCodeVisible] = useState(false)
const [isChecked, setIsChecked] = useState(false)
// const [isNameInputEnabled, setIsNameInputEnabled] = useState(true)
const [isPaidFromWallet, setIsPaidFromWallet] = useState(false)
const [isResultModalVisible, setIsResultModalVisible] = useState<boolean>(false)
const [resultModalInfo, setResultModalInfo] = useState<
@@ -49,10 +51,15 @@ export const OwnName = observer(function (props: {navigation: any, pubkey: strin
}, [])
useLayoutEffect(() => {
ownNameInputRef && ownNameInputRef.current
? ownNameInputRef.current.focus()
: false
useLayoutEffect(() => { // not working
const focus = () => {
ownNameInputRef && ownNameInputRef.current
? ownNameInputRef.current.focus()
: false
}
const timer = setTimeout(() => focus(), 100)
})
@@ -62,7 +69,7 @@ export const OwnName = observer(function (props: {navigation: any, pubkey: strin
return
}
poller('checkDonationPaidPoller', checkDonationPaid, 6 * 1000, 50, 5) // 5 min
poller('checkDonationPaidPoller', checkDonationPaid, 2 * 1000, 120, 10) // every 2s to make it responsive. Total 4 min
.then(() => log.trace('Polling completed', {}, 'checkDonationPaid'))
.catch(error =>
log.trace(error.message, {}, 'checkPendingTopups'),
@@ -83,14 +90,15 @@ export const OwnName = observer(function (props: {navigation: any, pubkey: strin
setIsResultModalVisible(previousState => !previousState)
const resetState = function () {
setOwnName('')
setInfo('')
setIsLoading(false)
const resetState = function () {
// setIsNameInputEnabled(true)
setIsChecked(false)
setOwnName('')
setInfo('')
setIsLoading(false)
setIsPaymentModalVisible(false)
setDonationInvoice(undefined)
setDonationAmount(1000)
setDonationAmount(DEFAULT_DONATION_AMOUNT)
setIsResultModalVisible(false)
setIsQRCodeVisible(false)
setIsPaidFromWallet(false)
@@ -119,6 +127,7 @@ export const OwnName = observer(function (props: {navigation: any, pubkey: strin
return
}
setIsChecked(true)
// setIsNameInputEnabled(false)
setIsPaymentModalVisible(true)
} catch (e: any) {
handleError(e)
@@ -234,14 +243,9 @@ export const OwnName = observer(function (props: {navigation: any, pubkey: strin
style={[$ownNameInput, {backgroundColor: inputBg}]}
maxLength={16}
keyboardType="default"
selectTextOnFocus={true}
// placeholder="Write your wallet name"
selectTextOnFocus={true}
autoCapitalize="none"
editable={
isChecked
? false
: true
}
// editable={isNameInputEnabled}
/>
<View style={[$ownNameDomain, { backgroundColor: inputBg}]}>
<Text size='xxs' style={{color: domainText}} text={MINIBITS_NIP05_DOMAIN}/>
@@ -251,11 +255,7 @@ export const OwnName = observer(function (props: {navigation: any, pubkey: strin
style={$ownNameButton}
text="Check"
onPress={onOwnNameCheck}
disabled={
isChecked
? true
: false
}
// disabled={!isNameInputEnabled}
/>
</View>
}
+30 -18
View File
@@ -7,7 +7,7 @@ import {BottomModal, Button, Card, ErrorModal, Header, Icon, InfoModal, ListItem
import {useStores} from '../../models'
import {ContactsStackParamList, ContactsStackScreenProps} from '../../navigation'
import { MinibitsClient, NostrClient, KeyPair, Database } from '../../services'
import AppError from '../../utils/AppError'
import AppError, { Err } from '../../utils/AppError'
import {MINIBITS_NIP05_DOMAIN} from '@env'
import { log } from '../../utils/logger'
import { useFocusEffect } from '@react-navigation/native'
@@ -82,21 +82,39 @@ export const PrivateContacts = observer(function (props: {
toggleNewContactModal()
}
const gotoContactDetail = function (contact: Contact) {
const gotoContactDetail = async function (contact: Contact) {
const {amountToSend} = props
log.trace('amountToSend', amountToSend)
if(amountToSend) {
navigation.navigate('WalletNavigator', {
screen: 'Send',
params: {
amountToSend,
contact,
relays: NostrClient.getMinibitsRelays()
},
})
if(amountToSend) { // Send tx contact selection
try {
// check before payment that contact name is still linked to the same pubkey
const profileRecord: WalletProfileRecord =
await MinibitsClient.getWalletProfileByWalletId(contact.name as string)
return
if(!profileRecord || profileRecord.pubkey !== contact.pubkey) {
throw new AppError(Err.VALIDATION_ERROR, `${contact.name} is no longer linked to the public key stored in your contacts. Please get in touch with the payee and update your information.`)
}
navigation.navigate('WalletNavigator', {
screen: 'Send',
params: {
amountToSend,
contact,
relays: NostrClient.getMinibitsRelays()
},
})
//reset
navigation.setParams({
amountToSend: '',
})
return
} catch (e: any) {
handleError(e)
}
}
navigation.navigate('ContactDetail', {
@@ -294,12 +312,6 @@ const $buttonContainer: ViewStyle = {
flexDirection: 'row',
alignSelf: 'center',
}
const $qrCodeContainer: ViewStyle = {
backgroundColor: 'white',
padding: spacing.small,
margin: spacing.small,
}
const $bottomContainer: ViewStyle = {
position: 'absolute',
+48 -44
View File
@@ -1,12 +1,13 @@
import {observer} from 'mobx-react-lite'
import React, {useEffect, useState} from 'react'
import {TextStyle, View, ViewStyle, InteractionManager } from 'react-native'
import {TextStyle, View, ViewStyle, InteractionManager, FlatList } from 'react-native'
import {spacing} from '../../theme'
import {Button, Card, ErrorModal, InfoModal, Loading, Screen, Text} from '../../components'
import {useStores} from '../../models'
import AppError, { Err } from '../../utils/AppError'
import {$sizeStyles} from '../../components/Text'
import {getRandomUsername} from '../../utils/usernames'
import { scale } from '@gocodingnow/rn-size-matters'
export const RandomName = observer(function (props: {navigation: any, pubkey: string}) {
@@ -65,43 +66,45 @@ export const RandomName = observer(function (props: {navigation: any, pubkey: st
setIsLoading(false)
setError(e)
}
//
return (
<Screen style={$screen} preset='auto'>
<Screen contentContainerStyle={$screen}>
<View style={$contentContainer}>
<Card
style={$card}
heading='Choose a random name'
headingStyle={{textAlign: 'center'}}
ContentComponent={
<View style={$namesContainer}>
{randomNames.map((name, index) => (
<Button
key={index}
preset={selectedName === name ? 'default' : 'secondary'}
onPress={() => setSelectedName(name)}
text={`${name}`}
style={{minWidth: 150, margin: spacing.extraSmall}}
textStyle={$sizeStyles.xs}
/>)
)}
{isLoading && <Loading />}
</View>
}
FooterComponent={
<View style={{alignItems: 'center'}}>
<Button
onPress={() => confirmSelectedName()}
text={'Confirm'}
style={{margin: spacing.extraSmall, minWidth: 120}}
<>
<FlatList
data={randomNames}
numColumns={2}
renderItem={({ item, index }) => {
return(
<Button
key={index}
preset={selectedName === item ? 'default' : 'secondary'}
onPress={() => setSelectedName(item)}
text={`${item}`}
style={{minWidth: scale(150), margin: spacing.extraSmall}}
textStyle={$sizeStyles.xs}
/>
)
}}
keyExtractor={(item) => item}
style={{ flexGrow: 0 }}
/>
</View>
}
/>
</View>
</>
}
style={$card}
/>
<View style={$buttonContainer}>
<Button
onPress={() => confirmSelectedName()}
text={'Confirm'}
style={{marginTop: spacing.medium, minWidth: 120}}
/>
</View>
</View>
{error && <ErrorModal error={error} />}
{info && <InfoModal message={info} />}
</Screen>
)
})
@@ -110,27 +113,28 @@ export const RandomName = observer(function (props: {navigation: any, pubkey: st
const $screen: ViewStyle = {}
const $headerContainer: TextStyle = {
alignItems: 'center',
paddingHorizontal: spacing.medium,
height: spacing.screenHeight * 0.08,
justifyContent: 'space-around',
}
const $contentContainer: TextStyle = {
padding: spacing.extraSmall,
}
const $namesContainer: ViewStyle = {
// flex: 1,
height: spacing.screenHeight * 0.35,
const $buttonContainer: ViewStyle = {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
paddingHorizontal: spacing.medium,
paddingTop: spacing.medium,
alignSelf: 'center',
}
const $bottomContainer: ViewStyle = {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
flex: 1,
justifyContent: 'flex-end',
marginBottom: spacing.medium,
alignSelf: 'stretch',
// opacity: 0,
}
const $card: ViewStyle = {
marginBottom: 0,
}
+2 -1
View File
@@ -10,6 +10,7 @@ import {ContactsStackScreenProps} from '../navigation'
import { MinibitsClient} from '../services'
import AppError from '../utils/AppError'
import { ProfileHeader } from './Contacts/ProfileHeader'
import { scale } from '@gocodingnow/rn-size-matters'
interface PictureScreenProps extends ContactsStackScreenProps<'Picture'> {}
@@ -93,7 +94,7 @@ export const PictureScreen: FC<PictureScreenProps> = observer(function PictureSc
onPress={() => onPictureSelect(png)}
style={(png === selectedPicture) ? [$unselected, {borderColor: selectedColor}] : $unselected}
>
<Image style={{width: 90, height: 96}} source={{uri: getImageSource(png)}} />
<Image style={{width: scale(80), height: scale(85)}} source={{uri: getImageSource(png)}} />
</Pressable>
)
})}
+4 -4
View File
@@ -130,7 +130,7 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
}, [])
// Send tip to public contact
// Send to contact
useFocusEffect(
useCallback(() => {
const prepareSendAsNostrDM = () => {
@@ -291,8 +291,8 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
setIsAmountEndEditing(true)
// Skip memo focus if it is filled already
if(!memo) {
// Skip memo focus if it is filled / has been done already
if(!memo && !isMemoEndEditing) {
setTimeout(() => {memoInputRef && memoInputRef.current
? memoInputRef.current.focus()
: false}, 200)
@@ -387,7 +387,7 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
return
}
// Skip mint modal and send immediately if:
// Skip mint selector and send immediately if:
// 1. only one mint is available or
// 2. we did coin selection in offline mode
if (availableMintBalances.length === 1 || selectedProofs.length > 0) {
+2
View File
@@ -3,6 +3,8 @@ import * as Sentry from '@sentry/react-native'
import {Err} from './AppError'
import {APP_ENV, LOG_LEVEL, SENTRY_ACTIVE} from '@env'
// refresh
export enum LogLevel {
TRACE = 'TRACE',
DEBUG = 'DEBUG',