Skip to content

Commit

Permalink
feat: new add contact navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
Guillaume Louvigny committed Dec 18, 2018
1 parent 494b4a6 commit 5f9c014
Show file tree
Hide file tree
Showing 55 changed files with 1,367 additions and 725 deletions.
10 changes: 10 additions & 0 deletions client/react-native/common/components/App.js
Expand Up @@ -5,6 +5,7 @@ import React, { PureComponent } from 'react'
import { parse as parseUrl } from '../helpers/url'
import LottieView from 'lottie-react-native'
import { Flex } from './Library'
import FlashMessage from 'react-native-flash-message'
import Accounts from './Screens/Accounts'
import { colors } from './../constants'

Expand Down Expand Up @@ -106,6 +107,15 @@ export default class App extends PureComponent {
deepLink,
}}
/> : null }
<Accounts
ref={nav => {
this.navigation = nav
}}
screenProps={{
deepLink,
}}
/>
<FlashMessage position='top' />
{Platform.OS === 'ios' && <KeyboardSpacer />}
</SafeAreaView>
)
Expand Down
6 changes: 4 additions & 2 deletions client/react-native/common/components/App.web.js
@@ -1,8 +1,9 @@
import { Linking, Platform } from 'react-native'
import { Linking } from 'react-native'
import { SafeAreaView } from 'react-navigation'
import KeyboardSpacer from 'react-native-keyboard-spacer'
import React, { PureComponent } from 'react'
import { parse as parseUrl } from '../helpers/url'
import FlashMessage from 'react-native-flash-message'

import { Loader } from './Library'
import Accounts from './Screens/Accounts'
Expand Down Expand Up @@ -75,7 +76,8 @@ export default class App extends PureComponent {
deepLink,
}}
/>
{Platform.OS === 'ios' && <KeyboardSpacer />}
<KeyboardSpacer />
<FlashMessage position='top' />
</SafeAreaView>
)
}
Expand Down
36 changes: 36 additions & 0 deletions client/react-native/common/components/Library/Avatar.js
@@ -0,0 +1,36 @@
import React from 'react'
import { Image } from 'react-native'
import { fingerprint } from '../../helpers/fingerprint'
import { extractPublicKeyFromId } from '../../helpers/contacts'

const Avatar = ({ data, size = 40, margin = 4, uri = null, style = [] }) => {
if (uri !== null) {
return <Image
style={[{ width: size, height: size, borderRadius: size / 2, margin: margin }, ...style]}
source={{
uri: uri,
}}
/>
}

if (!(style instanceof Array)) {
style = [style]
}

if (!data || !data.id) {
console.error(['No id provided', data])
}

const id = extractPublicKeyFromId(data.id) || data.id
const hexCode = fingerprint(id).substring(0, 16)
const retinaMode = 2

return <Image
style={[{ width: size, height: size, borderRadius: size / 2, margin: margin }, ...style]}
source={{
uri: `https://api.adorable.io/avatars/${size * retinaMode}/${hexCode}.png`,
}}
/>
}

export default Avatar
76 changes: 15 additions & 61 deletions client/react-native/common/components/Library/ContactIdentity.js
@@ -1,14 +1,14 @@
import React from 'react'
import { View, Image, Platform, Text as RNText } from 'react-native'
import Text from './Text'
import { View, Platform, Text as RNText } from 'react-native'
import { Avatar, Text } from '.'
import { createMaterialTopTabNavigator } from 'react-navigation'
import QRGenerator from './QRGenerator'
import { extractPublicKeyFromId, makeShareableUrl } from '../../helpers/contacts'
import { makeShareableUrl } from '../../helpers/contacts'
import colors from '../../constants/colors'
import Icon from './Icon'
import { formattedFingerprint } from '../../helpers/fingerprint'
import { padding } from '../../styles'
import { withScreenProps } from '../../helpers/views'
import { tabIcon, withScreenProps } from '../../helpers/views'
import { monospaceFont, tabNavigatorOptions } from '../../constants/styling'

const PublicKey = ({ data: { id } }) => <View
style={[{ flexDirection: 'row', justifyContent: 'center' }, padding]}>
Expand All @@ -20,29 +20,20 @@ const PublicKey = ({ data: { id } }) => <View
color: colors.fakeBlack,
borderRadius: 8,
flexWrap: 'wrap',
fontFamily: Platform.OS === 'ios' ? 'Courier' : 'monospace',
fontFamily: monospaceFont,
padding: 8,
}}>
{extractPublicKeyFromId(id)}
</RNText>
}}>{id}</RNText>
</View>

export const QrCode = ({ data: { id, displayName } }) => <View
const QrCode = ({ data: { id, displayName } }) => <View
style={[{ flexDirection: 'row', justifyContent: 'center' }]}>
<QRGenerator
value={makeShareableUrl({ id: extractPublicKeyFromId(id), displayName })}
value={makeShareableUrl({ id, displayName })}
size={248}
style={[{ marginTop: 16, marginBottom: 16 }]}
/>
</View>

const tabIcon = (iconName) => {
const NamedTabIcon = ({ focused }) => <Icon name={iconName} color={focused ? colors.blue : colors.darkGrey}
size={20} />

return NamedTabIcon
}

const Fingerprint = ({ data: { id } }) => <View
style={[{ flexDirection: 'row', justifyContent: 'center' }, padding]}>
<RNText style={{
Expand All @@ -53,7 +44,7 @@ const Fingerprint = ({ data: { id } }) => <View
borderRadius: 8,
flexWrap: 'wrap',
fontSize: 18,
fontFamily: Platform.OS === 'ios' ? 'Courier' : 'monospace',
fontFamily: monospaceFont,
padding: 8,
}}>
{formattedFingerprint(id)}
Expand Down Expand Up @@ -86,57 +77,20 @@ const ContactIdentityTabbedContent = createMaterialTopTabNavigator(
},
{
initialRouteName: 'qrcode',
swipeEnabled: Platform.OS !== 'android',
animationEnabled: true,
backBehavior: 'none',
tabBarOptions: {
activeTintColor: colors.fakeBlack,
inactiveTintColor: colors.fakeBlack,
showIcon: true,
showLabel: true,
upperCaseLabel: false,
style: {
backgroundColor: colors.white,
marginBottom: 0,
marginTop: 0,
},
tabStyle: {
marginBottom: 0,
marginTop: 0,
},
indicatorStyle: {
backgroundColor: colors.blue,
marginBottom: 0,
marginTop: 0,
},
labelStyle: {
fontSize: 12,
marginBottom: 0,
marginTop: 0,
},
},
...tabNavigatorOptions,
},
)

const ContactIdentity = ({ data }) => <>
<View style={{ flexDirection: 'row', justifyContent: 'center' }}>
<Image
style={{
width: 78,
height: 78,
borderRadius: 39,
marginBottom: 4,
marginTop: 0,
}}
source={{
uri: 'https://api.adorable.io/avatars/120/' + data.id + '.png',
}}
/>
<Avatar data={data} size={78} style={{ marginTop: 0 }} />
</View>
<Text large color={colors.fakeBlack} center padding>{data.displayName}</Text>
<View style={{ marginLeft: 15, marginRight: 15, marginBottom: 8, height: Platform.OS === 'android' ? 330 : undefined }}>
<View
style={{ marginLeft: 15, marginRight: 15, marginBottom: 8, height: Platform.OS === 'android' ? 330 : undefined }}>
{<ContactIdentityTabbedContent screenProps={{ data }} />}
</View>
</>

export default ContactIdentity
ContactIdentity.QrCode = QrCode
@@ -0,0 +1,12 @@
import React from 'react'
import { Text, TouchableOpacity } from 'react-native'
import { Button, Icon } from '..'
import colors from '../../../constants/colors'

const ActionButton = ({ icon, title, onPress }) => <TouchableOpacity style={{ flex: 1 }} onPress={onPress}>
<Button rounded={'circle'} background={colors.blue} color={colors.white}
icon={<Icon name={icon} color={colors.white} />} />
<Text style={{ color: colors.white }}>{title}</Text>
</TouchableOpacity>

export default ActionButton
@@ -0,0 +1,48 @@
import React from 'react'
import { withNavigation } from 'react-navigation'
import RelayContext from '../../../relay/RelayContext'
import defaultValuesContact from '../../../utils/contact'
import ActionButton from './ActionButton'
import { showMessage } from 'react-native-flash-message'
import { btoa } from 'b64-lite'

const ActionsAdd = ({ data, self, navigation }) => <>
<RelayContext.Consumer>{({ mutations }) =>
<ActionButton icon={'plus'} title={'Add contact'}
onPress={async () => {
const input = {
contact: {
...defaultValuesContact,
...data,
id: btoa(`contact:${data.id}`),
},
introText: '',
}

try {
await mutations.contactRequest(input)

showMessage({
message: 'A request has been sent to this contact',
type: 'info',
position: 'center',
})

const beforeDismiss = navigation.getParam('beforeDismiss')
beforeDismiss()

navigation.goBack(null)
} catch (err) {
showMessage({
message: 'An error occurred while sending a request to this contact',
type: 'danger',
icon: 'danger',
position: 'center',
})
}
}}
/>
}</RelayContext.Consumer>
</>

export default withNavigation(ActionsAdd)
@@ -0,0 +1,46 @@
import React from 'react'
import { withNavigation } from 'react-navigation'
import { Clipboard } from 'react-native'
import { makeShareableUrl, shareLinkOther, shareLinkSelf } from '../../../helpers/contacts'
import saveViewToCamera from '../../../helpers/saveViewToCamera'
import QRCodeExport from '../QRExport'
import ActionButton from './ActionButton'
import { showMessage } from 'react-native-flash-message'

const ActionsShare = ({ data, self, navigation }) => {
const { id, displayName } = data

return <>
<ActionButton icon={'share'} title={'Share'}
onPress={() =>
self
? shareLinkSelf({ id, displayName })
: shareLinkOther({ id, displayName })
}
/>
<ActionButton icon={'image'} title={'Save QR code'}
onPress={async () => {
try {
await saveViewToCamera({ view: <QRCodeExport data={data} />, navigation })
showMessage({
message: 'The QR Code has been added to your Camera Roll',
type: 'info',
icon: 'info',
position: 'center',
})
} catch (e) {
showMessage({
message: String(e),
type: 'danger',
icon: 'danger',
position: 'center',
})
}
}} />
<ActionButton icon={'link'} title={'Copy link'}
onPress={() => Clipboard.setString(makeShareableUrl({ id, displayName }))} />
<ActionButton icon={'copy'} title={'Copy public key'} onPress={() => Clipboard.setString(id)} />
</>
}

export default withNavigation(ActionsShare)
@@ -0,0 +1,42 @@
import React from 'react'
import { WithContact } from '../../../utils/contact'
import { enums } from '../../../graphql'
import { Text, View } from 'react-native'
import { withNavigation } from 'react-navigation'
import ActionsAdd from './ActionsAdd'
import ActionsShare from './ActionsShare'

const ContactIdentityActions = ({ data, navigation, modalWidth }) => <View
style={{ width: modalWidth, flexDirection: 'row', marginTop: 12 }}>
<WithContact id={data.id}>{(user, state) => {
if (state.type === state.success && (user === null || user.status === enums.BertyEntityContactInputStatus.Unknown)) {
return <ActionsAdd data={data} navigation={navigation} />
} else if (user === null) {
return <View><Text>LOADING (or error)</Text></View>
} else {
switch (user.status) {
case enums.BertyEntityContactInputStatus.Myself:
return <ActionsShare data={data} self navigation={navigation} />

case enums.BertyEntityContactInputStatus.IsFriend:
case enums.BertyEntityContactInputStatus.IsTrustedFriend:
return <ActionsShare data={data} navigation={navigation} />

case enums.BertyEntityContactInputStatus.IsRequested:
return <Text>Is requested</Text>

case enums.BertyEntityContactInputStatus.RequestedMe:
return <Text>Requested me</Text>

case enums.BertyEntityContactInputStatus.IsBlocked:
return <Text>Is blocked</Text>

case enums.BertyEntityContactInputStatus.Unknown:
default:
return <Text>Unknown state</Text>
}
}
}}</WithContact>
</View>

export default withNavigation(ContactIdentityActions)
10 changes: 9 additions & 1 deletion client/react-native/common/components/Library/Header.js
Expand Up @@ -17,6 +17,7 @@ export default class Header extends PureComponent {
title,
titleIcon,
backBtn,
rightBtn,
rightBtnIcon,
onPressRightBtn,
searchBar,
Expand Down Expand Up @@ -83,7 +84,12 @@ export default class Header extends PureComponent {
>
{title}
</Text>
{rightBtnIcon !== null && (
{rightBtn
? <View>
{rightBtn}
</View>
: null}
{!rightBtn && rightBtnIcon !== null && (
<HeaderButton
icon={rightBtnIcon}
color={colorBtnRight}
Expand All @@ -100,3 +106,5 @@ export default class Header extends PureComponent {
)
}
}

Header.HeaderButton = HeaderButton

0 comments on commit 5f9c014

Please sign in to comment.