From c86f9f9948a88c61e9368a437fb823f16c45f958 Mon Sep 17 00:00:00 2001 From: Roland Osborne Date: Thu, 23 Feb 2023 16:00:49 -0800 Subject: [PATCH] merging back updated cards screen --- .../ios/Databag.xcodeproj/project.pbxproj | 4 +- app/mobile/ios/Databag/Info.plist | 4 + app/mobile/src/context/useCardContext.hook.js | 5 +- .../src/context/useStoreContext.hook.js | 2 +- app/mobile/src/session/Session.jsx | 20 ++- app/mobile/src/session/Session.styled.js | 2 + app/mobile/src/session/cards/Cards.jsx | 71 +++++++++- app/mobile/src/session/cards/Cards.styled.js | 127 ++++++++++++++++++ .../src/session/cards/cardItem/CardItem.jsx | 46 +++++++ .../session/cards/cardItem/CardItem.styled.js | 70 ++++++++++ app/mobile/src/session/cards/useCards.hook.js | 83 +++++++++++- app/mobile/src/session/profile/Profile.jsx | 2 +- .../src/session/profileIcon/ProfileIcon.jsx | 17 ++- .../session/profileIcon/ProfileIcon.styled.js | 14 ++ .../profileIcon/useProfileIcon.hook.js | 24 ++++ 15 files changed, 468 insertions(+), 23 deletions(-) create mode 100644 app/mobile/src/session/cards/Cards.styled.js create mode 100644 app/mobile/src/session/cards/cardItem/CardItem.jsx create mode 100644 app/mobile/src/session/cards/cardItem/CardItem.styled.js create mode 100644 app/mobile/src/session/profileIcon/ProfileIcon.styled.js create mode 100644 app/mobile/src/session/profileIcon/useProfileIcon.hook.js diff --git a/app/mobile/ios/Databag.xcodeproj/project.pbxproj b/app/mobile/ios/Databag.xcodeproj/project.pbxproj index d6343f17..e18215b7 100644 --- a/app/mobile/ios/Databag.xcodeproj/project.pbxproj +++ b/app/mobile/ios/Databag.xcodeproj/project.pbxproj @@ -567,7 +567,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; @@ -639,7 +639,7 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_CXX_LANGUAGE_STANDARD = "c++14"; + CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; diff --git a/app/mobile/ios/Databag/Info.plist b/app/mobile/ios/Databag/Info.plist index 00053d18..80c5039b 100644 --- a/app/mobile/ios/Databag/Info.plist +++ b/app/mobile/ios/Databag/Info.plist @@ -35,6 +35,10 @@ + NSMicrophoneUsageDescription + Required for build but not used + NSPhotoLibraryUsageDescription + Used to set profile image and post photos NSLocationWhenInUseUsageDescription UIAppFonts diff --git a/app/mobile/src/context/useCardContext.hook.js b/app/mobile/src/context/useCardContext.hook.js index ac719ea9..8379eb3e 100644 --- a/app/mobile/src/context/useCardContext.hook.js +++ b/app/mobile/src/context/useCardContext.hook.js @@ -150,7 +150,7 @@ export function useCardContext() { else { 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; cards.current.set(card.id, entry); } @@ -360,8 +360,9 @@ export function useCardContext() { return await setCardCloseMessage(server, message); }, getCardImageUrl: (cardId) => { + const { profileRevision } = cards.current.get(cardId)?.card || { }; const { server, token } = access.current; - return getCardImageUrl(server, token, cardId, revision); + return getCardImageUrl(server, token, cardId, profileRevision); }, removeChannel: async (cardId, channelId) => { const { detail, profile } = cards.current.get(cardId) || {}; diff --git a/app/mobile/src/context/useStoreContext.hook.js b/app/mobile/src/context/useStoreContext.hook.js index 496f3548..fcf5f71b 100644 --- a/app/mobile/src/context/useStoreContext.hook.js +++ b/app/mobile/src/context/useStoreContext.hook.js @@ -1,7 +1,7 @@ import { useEffect, useState, useRef, useContext } from 'react'; import SQLite from "react-native-sqlite-storage"; -const DATABAG_DB = 'db_v_103.db'; +const DATABAG_DB = 'db_v_107.db'; export function useStoreContext() { const [state, setState] = useState({}); diff --git a/app/mobile/src/session/Session.jsx b/app/mobile/src/session/Session.jsx index 14d05d5a..b669d096 100644 --- a/app/mobile/src/session/Session.jsx +++ b/app/mobile/src/session/Session.jsx @@ -10,7 +10,7 @@ import { useSession } from './useSession.hook'; import { styles } from './Session.styled'; import Colors from 'constants/Colors'; 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 { Contact, ContactTitle } from './contact/Contact'; import { Details, DetailsHeader, DetailsBody } from './details/Details'; @@ -93,6 +93,9 @@ export function Session() { const ContactStackScreen = () => { const [contact, setContact] = useState(null); + const [filter, setFilter] = useState(null); + const [sort, setSort] = useState(false); + const openContact = (navigation, contact) => { setContact(contact); navigation.navigate('contact') @@ -104,8 +107,10 @@ export function Session() { return ( (screenParams)} initialRouteName="cards"> - }}> - {(props) => openContact(props.navigation, contact)} />} + ( + openRegistry(props.navigation)} /> + )}}> + {(props) => openContact(props.navigation, contact)} />} }}> @@ -183,8 +188,11 @@ export function Session() { }; return ( - }> + ( + + + + )}> {(props) => } @@ -271,7 +279,7 @@ export function Session() { { state.tabbed === false && ( ( - + )}> {(props) => } diff --git a/app/mobile/src/session/Session.styled.js b/app/mobile/src/session/Session.styled.js index 33c4d4d1..e682bb19 100644 --- a/app/mobile/src/session/Session.styled.js +++ b/app/mobile/src/session/Session.styled.js @@ -87,7 +87,9 @@ export const styles = StyleSheet.create({ drawer: { width: '100%', height: '100%', + paddingTop: 8, paddingLeft: 8, + paddingRight: 8, backgroundColor: Colors.formBackground, }, options: { diff --git a/app/mobile/src/session/cards/Cards.jsx b/app/mobile/src/session/cards/Cards.jsx index b2daa096..15eda4fe 100644 --- a/app/mobile/src/session/cards/Cards.jsx +++ b/app/mobile/src/session/cards/Cards.jsx @@ -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 }) { - return CardsTitle; +export function CardsHeader({ filter, setFilter, sort, setSort, openRegistry }) { + const { state, actions } = useCards(filter, sort); + + return ( + + { sort && ( + setSort(false)}> + + + )} + { !sort && ( + setSort(true)}> + + + )} + + + + + + + + New + + + ); } -export function CardsBody({ state, actions, openContact }) { - return CardsBody; +export function CardsBody({ filter, sort, openContact }) { + const { state, actions } = useCards(filter, sort); + + return ( + <> + { state.cards.length == 0 && ( + + No Contacts Found + + )} + { state.cards.length != 0 && ( + } + keyExtractor={item => item.cardId} + /> + )} + + ); } export function Cards({ openRegistry, openContact }) { - return Cards; + const [filter, setFilter] = useState(); + const [sort, setSort] = useState(false); + + return ( + + + + + ); } diff --git a/app/mobile/src/session/cards/Cards.styled.js b/app/mobile/src/session/cards/Cards.styled.js new file mode 100644 index 00000000..4c7bf915 --- /dev/null +++ b/app/mobile/src/session/cards/Cards.styled.js @@ -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, + } +}) + + diff --git a/app/mobile/src/session/cards/cardItem/CardItem.jsx b/app/mobile/src/session/cards/cardItem/CardItem.jsx new file mode 100644 index 00000000..5d46d8e9 --- /dev/null +++ b/app/mobile/src/session/cards/cardItem/CardItem.jsx @@ -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 ( + + { item.cardId && ( + + + + { item.name } + { item.handle } + + { item.status === 'connected' && item.offsync === 1 && ( + + )} + { item.status === 'connected' && item.offsync !== 1 && ( + + )} + { item.status === 'requested' && ( + + )} + { item.status === 'connecting' && ( + + )} + { item.status === 'pending' && ( + + )} + { item.status === 'confirmed' && ( + + )} + + )} + { !item.cardId && ( + + )} + + ); +} + diff --git a/app/mobile/src/session/cards/cardItem/CardItem.styled.js b/app/mobile/src/session/cards/cardItem/CardItem.styled.js new file mode 100644 index 00000000..b316c21a --- /dev/null +++ b/app/mobile/src/session/cards/cardItem/CardItem.styled.js @@ -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, + }, +}) + diff --git a/app/mobile/src/session/cards/useCards.hook.js b/app/mobile/src/session/cards/useCards.hook.js index e75d7cad..a3258d9b 100644 --- a/app/mobile/src/session/cards/useCards.hook.js +++ b/app/mobile/src/session/cards/useCards.hook.js @@ -1,7 +1,83 @@ -import { useState } from 'react'; +import { useState, useEffect, useRef, useContext } from 'react'; +import { CardContext } from 'context/CardContext'; -export function useCards() { - const [state, setState] = useState({}); +export function useCards(filter, sort) { + + 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 = { }; @@ -9,3 +85,4 @@ export function useCards() { return { state, actions }; } + diff --git a/app/mobile/src/session/profile/Profile.jsx b/app/mobile/src/session/profile/Profile.jsx index acf46d83..3efaf4e9 100644 --- a/app/mobile/src/session/profile/Profile.jsx +++ b/app/mobile/src/session/profile/Profile.jsx @@ -599,7 +599,7 @@ export function ProfileBody() { export function Profile() { return ( - + diff --git a/app/mobile/src/session/profileIcon/ProfileIcon.jsx b/app/mobile/src/session/profileIcon/ProfileIcon.jsx index 4506b9ee..714b8cf1 100644 --- a/app/mobile/src/session/profileIcon/ProfileIcon.jsx +++ b/app/mobile/src/session/profileIcon/ProfileIcon.jsx @@ -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 }) { - return ProfileIcon + + const { state, actions } = useProfileIcon(); + + return ( + + + { state.disconnected && ( + + )} + + ); } diff --git a/app/mobile/src/session/profileIcon/ProfileIcon.styled.js b/app/mobile/src/session/profileIcon/ProfileIcon.styled.js new file mode 100644 index 00000000..53e757a7 --- /dev/null +++ b/app/mobile/src/session/profileIcon/ProfileIcon.styled.js @@ -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, + }, +}); diff --git a/app/mobile/src/session/profileIcon/useProfileIcon.hook.js b/app/mobile/src/session/profileIcon/useProfileIcon.hook.js new file mode 100644 index 00000000..081a3ea9 --- /dev/null +++ b/app/mobile/src/session/profileIcon/useProfileIcon.hook.js @@ -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 }; +}