merging back updated cards screen

This commit is contained in:
Roland Osborne 2023-02-23 16:00:49 -08:00
parent 91f6bdb6b5
commit c86f9f9948
15 changed files with 468 additions and 23 deletions

View File

@ -567,7 +567,7 @@
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;
@ -639,7 +639,7 @@
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;

View File

@ -35,6 +35,10 @@
</dict> </dict>
</dict> </dict>
</dict> </dict>
<key>NSMicrophoneUsageDescription</key>
<string>Required for build but not used</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Used to set profile image and post photos</string>
<key>NSLocationWhenInUseUsageDescription</key> <key>NSLocationWhenInUseUsageDescription</key>
<string></string> <string></string>
<key>UIAppFonts</key> <key>UIAppFonts</key>

View File

@ -150,7 +150,7 @@ export function useCardContext() {
else { else {
entryCard.profile = await getCardProfile(server, token, card.id); entryCard.profile = await getCardProfile(server, token, card.id);
} }
await store.actions.setCardItem(guid, card.id, entryCard); await store.actions.setCardItem(guid, entryCard);
entry.card = entryCard; entry.card = entryCard;
cards.current.set(card.id, entry); cards.current.set(card.id, entry);
} }
@ -360,8 +360,9 @@ export function useCardContext() {
return await setCardCloseMessage(server, message); return await setCardCloseMessage(server, message);
}, },
getCardImageUrl: (cardId) => { getCardImageUrl: (cardId) => {
const { profileRevision } = cards.current.get(cardId)?.card || { };
const { server, token } = access.current; const { server, token } = access.current;
return getCardImageUrl(server, token, cardId, revision); return getCardImageUrl(server, token, cardId, profileRevision);
}, },
removeChannel: async (cardId, channelId) => { removeChannel: async (cardId, channelId) => {
const { detail, profile } = cards.current.get(cardId) || {}; const { detail, profile } = cards.current.get(cardId) || {};

View File

@ -1,7 +1,7 @@
import { useEffect, useState, useRef, useContext } from 'react'; import { useEffect, useState, useRef, useContext } from 'react';
import SQLite from "react-native-sqlite-storage"; import SQLite from "react-native-sqlite-storage";
const DATABAG_DB = 'db_v_103.db'; const DATABAG_DB = 'db_v_107.db';
export function useStoreContext() { export function useStoreContext() {
const [state, setState] = useState({}); const [state, setState] = useState({});

View File

@ -10,7 +10,7 @@ import { useSession } from './useSession.hook';
import { styles } from './Session.styled'; import { styles } from './Session.styled';
import Colors from 'constants/Colors'; import Colors from 'constants/Colors';
import { Profile, ProfileHeader, ProfileBody } from './profile/Profile'; import { Profile, ProfileHeader, ProfileBody } from './profile/Profile';
import { CardsTitle, CardsBody, Cards } from './cards/Cards'; import { CardsHeader, CardsBody, Cards } from './cards/Cards';
import { RegistryTitle, RegistryBody, Registry } from './registry/Registry'; import { RegistryTitle, RegistryBody, Registry } from './registry/Registry';
import { Contact, ContactTitle } from './contact/Contact'; import { Contact, ContactTitle } from './contact/Contact';
import { Details, DetailsHeader, DetailsBody } from './details/Details'; import { Details, DetailsHeader, DetailsBody } from './details/Details';
@ -93,6 +93,9 @@ export function Session() {
const ContactStackScreen = () => { const ContactStackScreen = () => {
const [contact, setContact] = useState(null); const [contact, setContact] = useState(null);
const [filter, setFilter] = useState(null);
const [sort, setSort] = useState(false);
const openContact = (navigation, contact) => { const openContact = (navigation, contact) => {
setContact(contact); setContact(contact);
navigation.navigate('contact') navigation.navigate('contact')
@ -104,8 +107,10 @@ export function Session() {
return ( return (
<ContactStack.Navigator screenOptions={({ route }) => (screenParams)} initialRouteName="cards"> <ContactStack.Navigator screenOptions={({ route }) => (screenParams)} initialRouteName="cards">
<ContactStack.Screen name="cards" options={{ ...stackParams, headerTitle: (props) => <CardsTitle openRegistry={props.navigation} /> }}> <ContactStack.Screen name="cards" options={{ ...stackParams, headerTitle: (props) => (
{(props) => <CardsBody openContact={(contact) => openContact(props.navigation, contact)} />} <CardsHeader filter={filter} setFilter={setFilter} sort={sort} setSort={setSort} openRegistry={() => openRegistry(props.navigation)} />
)}}>
{(props) => <CardsBody filter={filter} sort={sort} openContact={(contact) => openContact(props.navigation, contact)} />}
</ContactStack.Screen> </ContactStack.Screen>
<ContactStack.Screen name="contact" options={{ ...stackParams, headerTitle: (props) => <ContactTitle contact={contact} /> }}> <ContactStack.Screen name="contact" options={{ ...stackParams, headerTitle: (props) => <ContactTitle contact={contact} /> }}>
@ -183,8 +188,11 @@ export function Session() {
}; };
return ( return (
<CardDrawer.Navigator screenOptions={{ ...drawerParams, drawerStyle: { width: '50%' } }} <CardDrawer.Navigator screenOptions={{ ...drawerParams, drawerStyle: { width: '50%' } }} drawerContent={(props) => (
drawerContent={(props) => <Cards openContact={openContact} openRegistry={openRegistry} />}> <SafeAreaView edges={['top', 'bottom', 'right']} style={styles.drawer}>
<Cards openContact={openContact} openRegistry={openRegistry} />
</SafeAreaView>
)}>
<CardDrawer.Screen name="home"> <CardDrawer.Screen name="home">
{(props) => <HomeScreen navParams={{...navParams, cardNav: props.navigation}} />} {(props) => <HomeScreen navParams={{...navParams, cardNav: props.navigation}} />}
</CardDrawer.Screen> </CardDrawer.Screen>
@ -271,7 +279,7 @@ export function Session() {
<View style={styles.container}> <View style={styles.container}>
{ state.tabbed === false && ( { state.tabbed === false && (
<ProfileDrawer.Navigator screenOptions={{ ...drawerParams, drawerStyle: { width: '45%' } }} drawerContent={(props) => ( <ProfileDrawer.Navigator screenOptions={{ ...drawerParams, drawerStyle: { width: '45%' } }} drawerContent={(props) => (
<ScrollView><SafeAreaView style={styles.drawer} edges={['top', 'bottom', 'right']}><Profile /></SafeAreaView></ScrollView> <ScrollView style={styles.drawer}><SafeAreaView edges={['top', 'bottom', 'right']}><Profile /></SafeAreaView></ScrollView>
)}> )}>
<ProfileDrawer.Screen name="detail"> <ProfileDrawer.Screen name="detail">
{(props) => <DetailDrawerScreen navParams={{ profileNav: props.navigation }} />} {(props) => <DetailDrawerScreen navParams={{ profileNav: props.navigation }} />}

View File

@ -87,7 +87,9 @@ export const styles = StyleSheet.create({
drawer: { drawer: {
width: '100%', width: '100%',
height: '100%', height: '100%',
paddingTop: 8,
paddingLeft: 8, paddingLeft: 8,
paddingRight: 8,
backgroundColor: Colors.formBackground, backgroundColor: Colors.formBackground,
}, },
options: { options: {

View File

@ -1,14 +1,73 @@
import { Text } from 'react-native'; import { useState } from 'react';
import { FlatList, ScrollView, View, TextInput, TouchableOpacity, Text } from 'react-native';
import { styles } from './Cards.styled';
import { useCards } from './useCards.hook';
import { SafeAreaView } from 'react-native-safe-area-context';
import AntIcons from 'react-native-vector-icons/AntDesign';
import MatIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import { Colors } from 'constants/Colors';
import { CardItem } from './cardItem/CardItem';
export function CardsTitle({ state, actions, openRegistry }) { export function CardsHeader({ filter, setFilter, sort, setSort, openRegistry }) {
return <Text>CardsTitle</Text>; const { state, actions } = useCards(filter, sort);
return (
<View style={styles.title}>
{ sort && (
<TouchableOpacity style={styles.sort} onPress={() => setSort(false)}>
<MatIcons style={styles.icon} name="sort-alphabetical-ascending" size={18} color={Colors.text} />
</TouchableOpacity>
)}
{ !sort && (
<TouchableOpacity style={styles.sort} onPress={() => setSort(true)}>
<MatIcons style={styles.icon} name="sort-alphabetical-ascending" size={18} color={Colors.disabled} />
</TouchableOpacity>
)}
<View style={styles.inputwrapper}>
<AntIcons style={styles.icon} name="search1" size={16} color={Colors.disabled} />
<TextInput style={styles.inputfield} value={filter} onChangeText={setFilter}
autoCapitalize="none" placeholderTextColor={Colors.disabled} placeholder="Contacts" />
<View style={styles.space} />
</View>
<TouchableOpacity style={styles.add} onPress={openRegistry}>
<AntIcons name={'adduser'} size={16} color={Colors.white} style={[styles.box, { transform: [ { rotateY: "180deg" }, ]} ]}/>
<Text style={styles.newtext}>New</Text>
</TouchableOpacity>
</View>
);
} }
export function CardsBody({ state, actions, openContact }) { export function CardsBody({ filter, sort, openContact }) {
return <Text>CardsBody</Text>; const { state, actions } = useCards(filter, sort);
return (
<>
{ state.cards.length == 0 && (
<View style={styles.notfound}>
<Text style={styles.notfoundtext}>No Contacts Found</Text>
</View>
)}
{ state.cards.length != 0 && (
<FlatList style={styles.cards}
data={state.cards}
initialNumToRender={25}
renderItem={({ item }) => <CardItem item={item} openContact={openContact} />}
keyExtractor={item => item.cardId}
/>
)}
</>
);
} }
export function Cards({ openRegistry, openContact }) { export function Cards({ openRegistry, openContact }) {
return <Text>Cards</Text>; const [filter, setFilter] = useState();
const [sort, setSort] = useState(false);
return (
<View>
<CardsHeader filter={filter} setFilter={setFilter} sort={sort} setSort={setSort} openRegistry={openRegistry} />
<CardsBody filter={filter} sort={sort} openContact={openContact} />
</View>
);
} }

View File

@ -0,0 +1,127 @@
import { StyleSheet } from 'react-native';
import { Colors } from 'constants/Colors';
export const styles = StyleSheet.create({
container: {
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
backgroundColor: Colors.formBackground,
},
drawer: {
flexGrow: 1,
},
title: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
topbar: {
borderTopWidth: 1,
borderBottomWidth: 1,
borderColor: Colors.divider,
paddingTop: 32,
paddingBottom: 6,
paddingLeft: 16,
paddingRight: 16,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
cardlist: {
flexGrow: 1,
borderBottomWidth: 1,
borderColor: Colors.divider,
},
searcharea: {
borderBottomWidth: 1,
borderColor: Colors.divider,
},
searchbar: {
display: 'flex',
flexDirection: 'row',
paddingTop: 8,
paddingLeft: 8,
paddingRight: 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,
},
inputfield: {
flex: 1,
textAlign: 'center',
padding: 4,
color: Colors.text,
fontSize: 14,
},
icon: {
paddingLeft: 8,
},
cards: {
width: '100%',
paddingLeft: 16,
paddingRight: 16,
},
addbottom: {
marginRight: 8,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
padding: 8,
borderRadius: 4,
},
bottomText: {
color: Colors.primary,
paddingLeft: 8,
},
add: {
backgroundColor: Colors.primary,
marginLeft: 8,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
padding: 8,
borderRadius: 4,
},
newtext: {
paddingLeft: 8,
color: Colors.white,
},
up: {
marginRight: 8,
},
sort: {
paddingRight: 12,
},
findarea: {
borderTopWidth: 1,
borderColor: Colors.divider,
},
notfound: {
width: '100%',
height: '100%',
alignItems: 'center',
justifyContent: 'center',
},
notfoundtext: {
fontSize: 20,
color: Colors.grey,
}
})

View File

@ -0,0 +1,46 @@
import { Text, TouchableOpacity, View } from 'react-native';
import { Logo } from 'utils/Logo';
import { styles } from './CardItem.styled';
export function CardItem({ item, openContact }) {
const select = () => {
openContact({ card: item.cardId });
};
return (
<View>
{ item.cardId && (
<TouchableOpacity style={styles.container} activeOpacity={1} onPress={select}>
<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' && item.offsync === 1 && (
<View style={styles.offsync} />
)}
{ item.status === 'connected' && item.offsync !== 1 && (
<View style={styles.connected} />
)}
{ 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.cardId && (
<View style={styles.space} />
)}
</View>
);
}

View File

@ -0,0 +1,70 @@
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,
},
detail: {
paddingLeft: 12,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
flexGrow: 1,
flexShrink: 1,
},
space: {
height: 64,
},
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,
},
offsync: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: Colors.error,
},
pending: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: Colors.pending,
},
confirmed: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: Colors.confirmed,
},
})

View File

@ -1,7 +1,83 @@
import { useState } from 'react'; import { useState, useEffect, useRef, useContext } from 'react';
import { CardContext } from 'context/CardContext';
export function useCards() { export function useCards(filter, sort) {
const [state, setState] = useState({});
const [state, setState] = useState({
cards: [],
});
const card = useContext(CardContext);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
}
const setCardItem = (item) => {
const { profile, detail, cardId } = item.card || { profile: {}, detail: {} }
return {
cardId: cardId,
name: profile.name,
handle: `${profile.handle}@${profile.node}`,
status: detail.status,
offsync: item.offsync,
blocked: item.blocked,
offsync: item.offsync,
updated: detail.statusUpdated,
logo: profile.imageSet ? card.actions.getCardImageUrl(cardId) : 'avatar',
}
};
useEffect(() => {
const cards = Array.from(card.state.cards.values());
const items = cards.map(setCardItem);
const filtered = items.filter(item => {
if (item.blocked) {
return false;
}
if (!filter) {
return true;
}
const lower = filter.toLowerCase();
if (item.name) {
if (item.name.toLowerCase().includes(lower)) {
return true;
}
}
if (item.handle) {
if (item.handle.toLowerCase().includes(lower)) {
return true;
}
}
return false;
})
if (sort) {
filtered.sort((a, b) => {
const aName = a?.name?.toLowerCase();
const bName = b?.name?.toLowerCase();
if (aName === bName) {
return 0;
}
if (!aName || (aName < bName)) {
return -1;
}
return 1;
});
}
else {
filtered.sort((a, b) => {
if (a.updated === b.updated) {
return 0;
}
if (!a.updated || (a.updated < b.updated)) {
return 1;
}
return -1;
});
}
updateState({ cards: filtered });
}, [card, filter, sort]);
const actions = { const actions = {
}; };
@ -9,3 +85,4 @@ export function useCards() {
return { state, actions }; return { state, actions };
} }

View File

@ -599,7 +599,7 @@ export function ProfileBody() {
export function Profile() { export function Profile() {
return ( return (
<View> <View style={styles.full}>
<ProfileHeader /> <ProfileHeader />
<ProfileBody /> <ProfileBody />
</View> </View>

View File

@ -1,6 +1,19 @@
import { Text } from 'react-native'; import { View } from 'react-native';
import { useProfileIcon } from './useProfileIcon.hook';
import { styles } from './ProfileIcon.styled';
import Ionicons from 'react-native-vector-icons/AntDesign';
export function ProfileIcon({ size, color }) { export function ProfileIcon({ size, color }) {
return <Text>ProfileIcon</Text>
const { state, actions } = useProfileIcon();
return (
<View>
<Ionicons name={'user'} size={size} color={color} />
{ state.disconnected && (
<View style={styles.disconnected} />
)}
</View>
);
} }

View File

@ -0,0 +1,14 @@
import { StyleSheet } from 'react-native';
import { Colors } from 'constants/Colors';
export const styles = StyleSheet.create({
disconnected: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: Colors.alert,
position: 'absolute',
right: 0,
bottom: 0,
},
});

View File

@ -0,0 +1,24 @@
import { useState, useEffect, useContext } from 'react';
import { AppContext } from 'context/AppContext';
export function useProfileIcon() {
const [state, setState] = useState({
disconnected: false,
});
const app = useContext(AppContext);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
}
useEffect(() => {
const { status } = app.state
updateState({ disconnected: status === 'disconnected' });
}, [app]);
const actions = {};
return { state, actions };
}