rendering registry

This commit is contained in:
balzack 2022-09-23 23:31:46 -07:00
parent 6d316d563e
commit 822d333401
11 changed files with 441 additions and 49 deletions

View File

@ -84,15 +84,24 @@ export function Session() {
const clearCardStack = (navigation) => {
navigation.goBack();
}
const setRegistryStack = (navigation) => {
navigation.navigate('registry');
}
const clearRegistryStack = (navigation) => {
navigation.goBack();
}
return (
<ContactStack.Navigator screenOptions={({ route }) => ({ headerShown: false })}>
<ContactStack.Screen name="cards">
{(props) => <Cards openContact={(cardId) => setCardStack(props.navigation, cardId)} />}
{(props) => <Cards openRegistry={() => setRegistryStack(props.navigation)} openContact={(cardId) => setCardStack(props.navigation, cardId)} />}
</ContactStack.Screen>
<ContactStack.Screen name="card">
{(props) => <Contact cardId={cardId} closeContact={() => clearCardStack(props.navigation)} />}
</ContactStack.Screen>
<ContactStack.Screen name="registry">
{(props) => <Registry closeRegistry={() => clearRegistryStack(props.navigation)} />}
</ContactStack.Screen>
</ContactStack.Navigator>
);
}

View File

@ -12,28 +12,35 @@ export function Cards({ openRegistry }) {
return (
<View style={styles.container}>
{ state.tabbed && (
<View style={styles.topbar}>
{ state.sorting && (
<TouchableOpacity style={styles.sort} onPress={actions.unsort}>
<Ionicons style={styles.icon} name="menufold" size={18} color={Colors.text} />
<>
<View style={styles.topbar}>
{ state.sorting && (
<TouchableOpacity style={styles.sort} onPress={actions.unsort}>
<Ionicons style={styles.icon} name="menufold" size={18} color={Colors.text} />
</TouchableOpacity>
)}
{ !state.sorting && (
<TouchableOpacity style={styles.sort} onPress={actions.sort}>
<Ionicons style={styles.icon} name="menufold" size={18} color={Colors.disabled} />
</TouchableOpacity>
)}
<View style={styles.inputwrapper}>
<Ionicons style={styles.icon} name="search1" size={16} color={Colors.disabled} />
<TextInput style={styles.inputfield} value={state.filter} onChangeText={actions.setFilter}
autoCapitalize="none" placeholderTextColor={Colors.disabled} placeholder="Contacts" />
<View style={styles.space} />
</View>
<TouchableOpacity style={styles.add} onPress={openRegistry}>
<Ionicons name={'adduser'} size={16} color={Colors.white} style={[styles.box, { transform: [ { rotateY: "180deg" }, ]} ]}/>
<Text style={styles.newtext}>New</Text>
</TouchableOpacity>
)}
{ !state.sorting && (
<TouchableOpacity style={styles.sort} onPress={actions.sort}>
<Ionicons style={styles.icon} name="menufold" size={18} color={Colors.disabled} />
</TouchableOpacity>
)}
<View style={styles.inputwrapper}>
<Ionicons style={styles.icon} name="search1" size={16} color={Colors.disabled} />
<TextInput style={styles.inputfield} value={state.filter} onChangeText={actions.setFilter}
autoCapitalize="none" placeholderTextColor={Colors.disabled} placeholder="Contacts" />
<View style={styles.space} />
</View>
<TouchableOpacity style={styles.add} onPress={openRegistry}>
<Ionicons name={'adduser'} size={16} color={Colors.white} style={[styles.box, { transform: [ { rotateY: "180deg" }, ]} ]}/>
<Text style={styles.newtext}>New</Text>
</TouchableOpacity>
</View>
<FlatList style={styles.cards}
data={state.cards}
renderItem={({ item }) => <CardItem item={item} />}
keyExtractor={item => item.cardId}
/>
</>
)}
{ !state.tabbed && (
<SafeAreaView edges={['right']} style={styles.searcharea}>
@ -50,20 +57,20 @@ export function Cards({ openRegistry }) {
)}
<View style={styles.inputwrapper}>
<Ionicons style={styles.icon} name="search1" size={16} color={Colors.disabled} />
<TextInput style={styles.inputfield} value={state.topic} onChangeText={actions.setTopic}
<TextInput style={styles.inputfield} value={state.filter} onChangeText={actions.setFilter}
autoCapitalize="none" placeholderTextColor={Colors.disabled} placeholder="Contacts" />
</View>
<TouchableOpacity style={styles.add} onPress={openRegistry}>
<Ionicons name={'adduser'} size={16} color={Colors.white} style={[styles.box, { transform: [ { rotateY: "180deg" }, ]} ]}/>
</TouchableOpacity>
</View>
<FlatList style={styles.cards}
data={state.cards}
renderItem={({ item }) => <CardItem item={item} />}
keyExtractor={item => item.cardId}
/>
</SafeAreaView>
)}
<FlatList style={styles.cards}
data={state.cards}
renderItem={({ item }) => <CardItem item={item} />}
keyExtractor={item => item.cardId}
/>
</View>
);
}

View File

@ -8,28 +8,35 @@ export function CardItem({ item }) {
const { state, actions } = useCardItem(item);
return (
<TouchableOpacity style={styles.container} activeOpacity={1}>
<Logo src={item.logo} width={32} height={32} radius={6} />
<View style={styles.detail}>
<Text style={styles.name} numberOfLines={1} ellipsizeMode={'tail'}>{ item.name }</Text>
<Text style={styles.handle} numberOfLines={1} ellipsizeMode={'tail'}>{ item.handle }</Text>
</View>
{ item.status === 'connected' && (
<View style={styles.confirmed} />
<View>
{ item.cardId && (
<TouchableOpacity style={styles.container} activeOpacity={1}>
<Logo src={item.logo} width={32} height={32} radius={6} />
<View style={styles.detail}>
<Text style={styles.name} numberOfLines={1} ellipsizeMode={'tail'}>{ item.name }</Text>
<Text style={styles.handle} numberOfLines={1} ellipsizeMode={'tail'}>{ item.handle }</Text>
</View>
{ item.status === 'connected' && (
<View style={styles.confirmed} />
)}
{ item.status === 'requested' && (
<View style={styles.requested} />
)}
{ item.status === 'connecting' && (
<View style={styles.connecting} />
)}
{ item.status === 'pending' && (
<View style={styles.pending} />
)}
{ item.status === 'confirmed' && (
<View style={styles.confirmed} />
)}
</TouchableOpacity>
)}
{ item.status === 'requested' && (
<View style={styles.requested} />
{ !item.cardId && (
<View style={styles.space} />
)}
{ item.status === 'connecting' && (
<View style={styles.connecting} />
)}
{ item.status === 'pending' && (
<View style={styles.pending} />
)}
{ item.status === 'confirmed' && (
<View style={styles.confirmed} />
)}
</TouchableOpacity>
</View>
);
}

View File

@ -19,6 +19,9 @@ export const styles = StyleSheet.create({
flexGrow: 1,
flexShrink: 1,
},
space: {
height: 64,
},
name: {
color: Colors.text,
fontSize: 14,

View File

@ -84,6 +84,7 @@ export function useCards() {
return -1;
});
}
filtered.push({cardId:''});
updateState({ cards: filtered });
}, [card, state.filter, state.sorting]);

View File

@ -1,4 +1,73 @@
export function Registry() {
return <></>
import { useContext } from 'react';
import { ActivityIndicator, FlatList, ScrollView, View, TextInput, TouchableOpacity, Text } from 'react-native';
import { styles } from './Registry.styled';
import { useRegistry } from './useRegistry.hook';
import { SafeAreaView } from 'react-native-safe-area-context';
import Ionicons from '@expo/vector-icons/AntDesign';
import { RegistryItem } from './registryItem/RegistryItem';
import Colors from 'constants/Colors';
export function Registry({ closeRegistry, openContact }) {
const { state, actions } = useRegistry();
return (
<View style={styles.container}>
{ state.tabbed && (
<>
<View style={styles.topbar}>
{ state.busy && (
<View style={styles.search}>
<ActivityIndicator />
</View>
)}
{ !state.busy && (
<TouchableOpacity style={styles.search} onPress={actions.search}>
<Ionicons name={'search1'} size={16} color={Colors.white} />
</TouchableOpacity>
)}
<View style={styles.inputwrapper}>
<TextInput style={styles.inputfield} value={state.server} onChangeText={actions.setServer}
autoCapitalize="none" placeholderTextColor={Colors.disabled} placeholder="Server" />
<View style={styles.space} />
</View>
<TouchableOpacity style={styles.close} onPress={closeRegistry}>
<Ionicons name={'close'} size={24} color={Colors.text} />
</TouchableOpacity>
</View>
<FlatList style={styles.accounts}
data={state.accounts}
renderItem={({ item }) => <RegistryItem item={item} />}
keyExtractor={item => item.guid}
/>
</>
)}
{ !state.tabbed && (
<SafeAreaView edges={['right']}>
<View style={styles.searcharea}>
<View style={styles.searchbar}>
{ state.busy && (
<View style={styles.search}>
<ActivityIndicator />
</View>
)}
{ !state.busy && (
<TouchableOpacity style={styles.search} onPress={actions.search}>
<Ionicons name={'search1'} size={16} color={Colors.white} />
</TouchableOpacity>
)}
<View style={styles.inputwrapper}>
<TextInput style={styles.inputfield} value={state.server} onChangeText={actions.setServer}
autoCapitalize="none" placeholderTextColor={Colors.disabled} placeholder="Server" />
</View>
</View>
</View>
<FlatList style={styles.accounts}
data={state.accounts}
renderItem={({ item }) => <RegistryItem item={item} />}
keyExtractor={item => item.guid}
/>
</SafeAreaView>
)}
</View>
);
}

View File

@ -0,0 +1,106 @@
import { StyleSheet } from 'react-native';
import { Colors } from 'constants/Colors';
export const styles = StyleSheet.create({
container: {
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
},
topbar: {
borderTopWidth: 1,
borderBottomWidth: 1,
borderColor: Colors.divider,
paddingTop: 6,
paddingBottom: 6,
paddingLeft: 16,
paddingRight: 16,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
searcharea: {
borderBottomWidth: 1,
borderColor: Colors.divider,
},
searchbar: {
display: 'flex',
flexDirection: 'row',
paddingTop: 16,
paddingLeft: 8,
paddingBottom: 8,
alignItems: 'center',
},
inputwrapper: {
display: 'flex',
flexDirection: 'row',
borderRadius: 4,
backgroundColor: Colors.white,
alignItems: 'center',
flexGrow: 1,
flexShrink: 1,
marginRight: 8,
paddingTop: 4,
paddingBottom: 4,
marginLeft: 8,
},
inputfield: {
flex: 1,
textAlign: 'center',
padding: 4,
color: Colors.text,
fontSize: 14,
},
icon: {
paddingLeft: 8,
},
accounts: {
flexGrow: 1,
flexShrink: 1,
width: '100%',
paddingLeft: 16,
paddingRight: 16,
minHeight: 0,
},
addbottom: {
marginRight: 8,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
padding: 8,
borderRadius: 4,
},
bottomText: {
color: Colors.primary,
paddingLeft: 8,
},
search: {
backgroundColor: Colors.primary,
marginLeft: 8,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
padding: 8,
borderRadius: 4,
},
close: {
marginRight: 8,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
padding: 8,
borderRadius: 4,
},
newtext: {
paddingLeft: 8,
color: Colors.white,
},
findarea: {
borderTopWidth: 1,
borderColor: Colors.divider,
}
})

View File

@ -0,0 +1,27 @@
import { Text, TouchableOpacity, View } from 'react-native';
import { Logo } from 'utils/Logo';
import { styles } from './RegistryItem.styled';
import { useRegistryItem } from './useRegistryItem.hook';
export function RegistryItem({ item }) {
const { state, actions } = useRegistryItem(item);
return (
<View>
{ item.guid && (
<TouchableOpacity style={styles.container} activeOpacity={1}>
<Logo src={item.logo} width={32} height={32} radius={6} />
<View style={styles.detail}>
<Text style={styles.name} numberOfLines={1} ellipsizeMode={'tail'}>{ item.name }</Text>
<Text style={styles.handle} numberOfLines={1} ellipsizeMode={'tail'}>{ item.handle }</Text>
</View>
</TouchableOpacity>
)}
{ !item.guid && (
<View style={styles.space} />
)}
</View>
);
}

View File

@ -0,0 +1,64 @@
import { StyleSheet } from 'react-native';
import { Colors } from 'constants/Colors';
export const styles = StyleSheet.create({
container: {
width: '100%',
display: 'flex',
flexDirection: 'row',
height: 48,
alignItems: 'center',
borderBottomWidth: 1,
borderColor: Colors.itemDivider,
},
space: {
height: 64
},
detail: {
paddingLeft: 12,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
flexGrow: 1,
flexShrink: 1,
},
name: {
color: Colors.text,
fontSize: 14,
},
handle: {
color: Colors.text,
fontSize: 12,
},
connected: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: Colors.connected,
},
requested: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: Colors.requested,
},
connecting: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: Colors.connecting,
},
pending: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: Colors.pending,
},
confirmed: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: Colors.confirmed,
},
})

View File

@ -0,0 +1,17 @@
import { useState, useEffect, useRef, useContext } from 'react';
import { useWindowDimensions } from 'react-native';
export function useRegistryItem(item) {
const [state, setState] = useState({});
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
}
const actions = {
};
return { state, actions };
}

View File

@ -0,0 +1,82 @@
import { useState, useEffect, useRef, useContext } from 'react';
import { useWindowDimensions } from 'react-native';
import { useNavigate } from 'react-router-dom';
import { ProfileContext } from 'context/ProfileContext';
import { getListing } from 'api/getListing';
import { getListingImageUrl } from 'api/getListingImageUrl';
import config from 'constants/Config';
export function useRegistry() {
const [state, setState] = useState({
tabbed: null,
accounts: [],
server: null,
busy: false,
});
const dimensions = useWindowDimensions();
const profile = useContext(ProfileContext);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
}
useEffect(() => {
if (dimensions.width > config.tabbedWidth) {
updateState({ tabbed: false });
}
else {
updateState({ tabbed: true });
}
}, [dimensions]);
useEffect(() => {
const server = profile.state.profile.node;
updateState({ server });
getAccounts(server);
}, [profile]);
const setAccountItem = (item) => {
return {
guid: item.guid,
name: item.name,
handle: `${item.handle}@${item.node}`,
logo: item.imageSet ? getListingImageUrl(item.node, item.guid) : 'avatar',
}
};
const getAccounts = async (server, ignore) => {
if (!state.busy) {
try {
updateState({ busy: true });
const accounts = await getListing(server, true);
const filtered = accounts.filter(item => {
if (item.guid === profile.state.profile.guid) {
return false;
}
return true;
});
const items = filtered.map(setAccountItem);
items.push({guid:''});
updateState({ busy: false, accounts: items });
}
catch (err) {
console.log(err);
updateState({ busy: false, accounts: [] });
if (!ignore) {
throw new Error('failed list accounts');
}
}
}
};
const actions = {
setServer: (filter) => {
updateState({ filter });
},
};
return { state, actions };
}