mirror of
https://github.com/KoalaSat/nostros.git
synced 2024-09-29 06:30:47 +00:00
New UI Main Feed and notifications
This commit is contained in:
parent
b16e96c753
commit
0e0485c9bf
@ -12,7 +12,7 @@ import { populateRelay } from '../../Functions/RelayFunctions'
|
||||
import { NostrosAvatar } from '../Avatar'
|
||||
import { searchRelays } from '../../Functions/DatabaseFunctions/Relays'
|
||||
import TextContent from '../../Components/TextContent'
|
||||
import { usernamePubKey } from '../../Functions/RelayFunctions/Users'
|
||||
import { formatPubKey, usernamePubKey } from '../../Functions/RelayFunctions/Users'
|
||||
import { getReactionsCount, getUserReaction } from '../../Functions/DatabaseFunctions/Reactions'
|
||||
import { UserContext } from '../../Contexts/UserContext'
|
||||
import {
|
||||
@ -32,9 +32,10 @@ import { push } from '../../lib/Navigation'
|
||||
interface NoteCardProps {
|
||||
note: Note
|
||||
onPressOptions?: () => void
|
||||
showAnswerData?: boolean
|
||||
}
|
||||
|
||||
export const NoteCard: React.FC<NoteCardProps> = ({ note, onPressOptions = () => {} }) => {
|
||||
export const NoteCard: React.FC<NoteCardProps> = ({ note, showAnswerData = true, onPressOptions = () => {} }) => {
|
||||
const theme = useTheme()
|
||||
const { publicKey, privateKey } = React.useContext(UserContext)
|
||||
const { relayPool, lastEventId } = useContext(RelayPoolContext)
|
||||
@ -88,12 +89,43 @@ export const NoteCard: React.FC<NoteCardProps> = ({ note, onPressOptions = () =>
|
||||
}
|
||||
|
||||
const textNote: () => JSX.Element = () => {
|
||||
return hide ? (
|
||||
<Button mode='outlined' onPress={() => setHide(false)}>
|
||||
{t('noteCard.contentWarning')}
|
||||
</Button>
|
||||
) : (
|
||||
<TextContent event={note} />
|
||||
return (
|
||||
<>
|
||||
{note.reply_event_id && showAnswerData && (
|
||||
<TouchableRipple
|
||||
onPress={() =>
|
||||
note.kind !== EventKind.recommendServer && push('Note', { noteId: note.reply_event_id })
|
||||
}
|
||||
>
|
||||
<Card.Content style={[styles.answerContent, { borderColor: theme.colors.onSecondary }]}>
|
||||
<View style={styles.answerData}>
|
||||
<MaterialCommunityIcons name='arrow-left-top' size={16} />
|
||||
<Text>
|
||||
{t('noteCard.answering', { username: formatPubKey(note.reply_event_id) })}
|
||||
</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Text style={styles.link}>{t('noteCard.seeParent')}</Text>
|
||||
</View>
|
||||
</Card.Content>
|
||||
</TouchableRipple>
|
||||
)}
|
||||
<TouchableRipple
|
||||
onPress={() =>
|
||||
note.kind !== EventKind.recommendServer && push('Note', { noteId: note.id })
|
||||
}
|
||||
>
|
||||
<Card.Content style={[styles.content, { borderColor: theme.colors.onSecondary }]}>
|
||||
{hide ? (
|
||||
<Button mode='outlined' onPress={() => setHide(false)}>
|
||||
{t('noteCard.contentWarning')}
|
||||
</Button>
|
||||
) : (
|
||||
<TextContent event={note} />
|
||||
)}
|
||||
</Card.Content>
|
||||
</TouchableRipple>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -110,28 +142,34 @@ export const NoteCard: React.FC<NoteCardProps> = ({ note, onPressOptions = () =>
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Card.Content style={styles.title}>
|
||||
<View>
|
||||
<Avatar.Icon
|
||||
size={54}
|
||||
icon='chart-timeline-variant'
|
||||
style={{
|
||||
backgroundColor: theme.colors.tertiaryContainer,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text>{t('noteCard.recommendation')}</Text>
|
||||
<Text>{relayName}</Text>
|
||||
</View>
|
||||
<TouchableRipple
|
||||
onPress={() => note.kind !== EventKind.recommendServer && push('Note', { noteId: note.id })}
|
||||
>
|
||||
<Card.Content style={[styles.content, { borderColor: theme.colors.onSecondary }]}>
|
||||
<Card>
|
||||
<Card.Content style={styles.title}>
|
||||
<View>
|
||||
<Avatar.Icon
|
||||
size={54}
|
||||
icon='chart-timeline-variant'
|
||||
style={{
|
||||
backgroundColor: theme.colors.tertiaryContainer,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<Text>{t('noteCard.recommendation')}</Text>
|
||||
<Text>{relayName}</Text>
|
||||
</View>
|
||||
</Card.Content>
|
||||
{!relayAdded && REGEX_SOCKET_LINK.test(note.content) && (
|
||||
<Card.Content style={[styles.actions, { borderColor: theme.colors.onSecondary }]}>
|
||||
<Button onPress={addRelayItem}>{t('noteCard.addRelay')}</Button>
|
||||
</Card.Content>
|
||||
)}
|
||||
</Card>
|
||||
</Card.Content>
|
||||
{!relayAdded && REGEX_SOCKET_LINK.test(note.content) && (
|
||||
<Card.Content style={[styles.actions, { borderColor: theme.colors.onSecondary }]}>
|
||||
<Button onPress={addRelayItem}>{t('noteCard.addRelay')}</Button>
|
||||
</Card.Content>
|
||||
)}
|
||||
</Card>
|
||||
</TouchableRipple>
|
||||
)
|
||||
}
|
||||
|
||||
@ -164,15 +202,7 @@ export const NoteCard: React.FC<NoteCardProps> = ({ note, onPressOptions = () =>
|
||||
<IconButton icon='dots-vertical' size={25} onPress={onPressOptions} />
|
||||
</View>
|
||||
</Card.Content>
|
||||
<TouchableRipple
|
||||
onPress={() =>
|
||||
note.kind !== EventKind.recommendServer && push('Note', { noteId: note.id })
|
||||
}
|
||||
>
|
||||
<Card.Content style={[styles.content, { borderColor: theme.colors.onSecondary }]}>
|
||||
{getNoteContent()}
|
||||
</Card.Content>
|
||||
</TouchableRipple>
|
||||
{getNoteContent()}
|
||||
<Card.Content style={[styles.actions, { borderColor: theme.colors.onSecondary }]}>
|
||||
<Button
|
||||
onPress={() => {
|
||||
@ -182,7 +212,12 @@ export const NoteCard: React.FC<NoteCardProps> = ({ note, onPressOptions = () =>
|
||||
publishReaction(false)
|
||||
}
|
||||
}}
|
||||
icon={() => <MaterialCommunityIcons name={userDownvoted ? 'thumb-down' : 'thumb-down-outline'} size={25} />}
|
||||
icon={() => (
|
||||
<MaterialCommunityIcons
|
||||
name={userDownvoted ? 'thumb-down' : 'thumb-down-outline'}
|
||||
size={25}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
{negaiveReactions === undefined || negaiveReactions === 0 ? '-' : negaiveReactions}
|
||||
</Button>
|
||||
@ -194,7 +229,12 @@ export const NoteCard: React.FC<NoteCardProps> = ({ note, onPressOptions = () =>
|
||||
publishReaction(true)
|
||||
}
|
||||
}}
|
||||
icon={() => <MaterialCommunityIcons name={userUpvoted ? 'thumb-up' : 'thumb-up-outline'} size={25} />}
|
||||
icon={() => (
|
||||
<MaterialCommunityIcons
|
||||
name={userUpvoted ? 'thumb-up' : 'thumb-up-outline'}
|
||||
size={25}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
{positiveReactions === undefined || positiveReactions === 0 ? '-' : positiveReactions}
|
||||
</Button>
|
||||
@ -212,12 +252,21 @@ const styles = StyleSheet.create({
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignContent: 'center',
|
||||
paddingBottom: 16
|
||||
paddingBottom: 16,
|
||||
},
|
||||
titleUser: {
|
||||
flexDirection: 'row',
|
||||
alignContent: 'center',
|
||||
},
|
||||
answerData: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
answerContent: {
|
||||
flexDirection: 'row',
|
||||
borderTopWidth: 1,
|
||||
padding: 16,
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
actions: {
|
||||
paddingTop: 16,
|
||||
flexDirection: 'row',
|
||||
@ -233,6 +282,9 @@ const styles = StyleSheet.create({
|
||||
borderTopWidth: 1,
|
||||
padding: 16,
|
||||
},
|
||||
link: {
|
||||
textDecorationLine: 'underline',
|
||||
},
|
||||
})
|
||||
|
||||
export default NoteCard
|
||||
|
@ -1,55 +0,0 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { Card, Layout, Text } from '@ui-kitten/components'
|
||||
import { User } from '../../Functions/DatabaseFunctions/Users'
|
||||
import { StyleSheet } from 'react-native'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import Avatar from '../Avatar'
|
||||
|
||||
interface UserCardProps {
|
||||
user: User
|
||||
}
|
||||
|
||||
export const UserCard: React.FC<UserCardProps> = ({ user }) => {
|
||||
const { goToPage } = useContext(AppContext)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
layout: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
profile: {
|
||||
flex: 1,
|
||||
width: 38,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
content: {
|
||||
flex: 5,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
actions: {
|
||||
flex: 1,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
user && (
|
||||
<Card onPress={() => goToPage(`profile#${user.id}`)}>
|
||||
<Layout style={styles.layout} level='2'>
|
||||
<Layout style={styles.profile}>
|
||||
<Avatar name={user.name} src={user.picture} pubKey={user.id} />
|
||||
</Layout>
|
||||
<Layout style={styles.content} level='2'>
|
||||
<Text>{user.name}</Text>
|
||||
<Text appearance='hint'>{user.id}</Text>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</Card>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export default UserCard
|
@ -4,4 +4,4 @@
|
||||
|
||||
export { Avatar } from './Avatar'
|
||||
export { Button } from './Button'
|
||||
export { UserCard } from './UserCard'
|
||||
export { UserCard } from '../Pages/HomeFeed'
|
||||
|
@ -6,6 +6,7 @@ export interface Note extends Event {
|
||||
name: string
|
||||
picture: string
|
||||
lnurl: string
|
||||
reply_event_id: string
|
||||
}
|
||||
|
||||
const databaseToEntity: (object: any) => Note = (object) => {
|
||||
|
@ -16,6 +16,9 @@
|
||||
"configuration": "Configuration",
|
||||
"about": "About",
|
||||
"logout": "Logout"
|
||||
},
|
||||
"noteCard": {
|
||||
"answering": "Answer to {{username}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,6 @@
|
||||
import {
|
||||
Card,
|
||||
Input,
|
||||
Layout,
|
||||
Modal,
|
||||
Tab,
|
||||
TabBar,
|
||||
TopNavigation,
|
||||
useTheme,
|
||||
} from '@ui-kitten/components'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { ScrollView, StyleSheet, Text, TouchableOpacity } from 'react-native'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import Icon from 'react-native-vector-icons/FontAwesome5'
|
||||
import { showMessage } from 'react-native-flash-message'
|
||||
import { EventKind } from '../../lib/nostr/Events'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getUsers, updateUserContact, User } from '../../Functions/DatabaseFunctions/Users'
|
||||
@ -21,8 +9,9 @@ import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { populatePets } from '../../Functions/RelayFunctions/Users'
|
||||
import { getNip19Key } from '../../lib/nostr/Nip19'
|
||||
import { UserContext } from '../../Contexts/UserContext'
|
||||
import { useTheme } from 'react-native-paper'
|
||||
|
||||
export const ContactsPage: React.FC = () => {
|
||||
export const ContactsFeed: React.FC = () => {
|
||||
const { database } = useContext(AppContext)
|
||||
const { publicKey, privateKey } = React.useContext(UserContext)
|
||||
const { relayPool, lastEventId } = useContext(RelayPoolContext)
|
||||
@ -95,64 +84,14 @@ export const ContactsPage: React.FC = () => {
|
||||
setIsAddingContact(false) // restore sending status
|
||||
})
|
||||
.catch((err) => {
|
||||
showMessage({
|
||||
message: t('alerts.contactAddError'),
|
||||
description: err.message,
|
||||
type: 'danger',
|
||||
})
|
||||
setIsAddingContact(false) // restore sending status
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onPressBack: () => void = () => {
|
||||
relayPool?.unsubscribeAll()
|
||||
goBack()
|
||||
}
|
||||
|
||||
const renderBackAction = (): JSX.Element => {
|
||||
return (
|
||||
<Button
|
||||
accessoryRight={<Icon name='arrow-left' size={16} color={theme['text-basic-color']} />}
|
||||
onPress={onPressBack}
|
||||
appearance='ghost'
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
actionContainer: {
|
||||
marginTop: 30,
|
||||
marginBottom: 30,
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
},
|
||||
button: {
|
||||
marginTop: 30,
|
||||
},
|
||||
icon: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
},
|
||||
modal: {
|
||||
paddingLeft: 32,
|
||||
paddingRight: 32,
|
||||
width: '100%',
|
||||
},
|
||||
backdrop: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
topBar: {
|
||||
height: 64,
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<TopNavigation alignment='center' accessoryLeft={renderBackAction} />
|
||||
{/* <TopNavigation alignment='center' accessoryLeft={renderBackAction} />
|
||||
<TabBar
|
||||
style={styles.topBar}
|
||||
selectedIndex={selectedTab}
|
||||
@ -211,9 +150,9 @@ export const ContactsPage: React.FC = () => {
|
||||
>
|
||||
<Icon name='user-plus' size={30} color={theme['text-basic-color']} solid />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
)} */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContactsPage
|
||||
export default ContactsFeed
|
205
frontend/Pages/HomeFeed/index.tsx
Normal file
205
frontend/Pages/HomeFeed/index.tsx
Normal file
@ -0,0 +1,205 @@
|
||||
import React, { useCallback, useContext, useState, useEffect } from 'react'
|
||||
import { getUsers, User } from '../../Functions/DatabaseFunctions/Users'
|
||||
import {
|
||||
Dimensions,
|
||||
NativeScrollEvent,
|
||||
NativeSyntheticEvent,
|
||||
RefreshControl,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { getMainNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
|
||||
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
||||
import { UserContext } from '../../Contexts/UserContext'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { EventKind } from '../../lib/nostr/Events'
|
||||
import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
|
||||
import { ActivityIndicator, AnimatedFAB } from 'react-native-paper'
|
||||
import NoteCard from '../../Components/NoteCard'
|
||||
import RBSheet from 'react-native-raw-bottom-sheet'
|
||||
import ProfileCard from '../../Components/ProfileCard'
|
||||
import { useTheme } from '@react-navigation/native'
|
||||
import { navigate } from '../../lib/Navigation'
|
||||
|
||||
export const HomeFeed: React.FC = () => {
|
||||
const theme = useTheme()
|
||||
const { database } = useContext(AppContext)
|
||||
const { publicKey } = useContext(UserContext)
|
||||
const { lastEventId, relayPool } = useContext(RelayPoolContext)
|
||||
const initialPageSize = 10
|
||||
const [notes, setNotes] = useState<Note[]>([])
|
||||
const [authors, setAuthors] = useState<User[]>([])
|
||||
const [pageSize, setPageSize] = useState<number>(initialPageSize)
|
||||
const [refreshing, setRefreshing] = useState(true)
|
||||
const [firstLoad, setFirstLoad] = useState(true)
|
||||
const [profileCardPubkey, setProfileCardPubKey] = useState<string>()
|
||||
const bottomSheetProfileRef = React.useRef<RBSheet>(null)
|
||||
|
||||
useEffect(() => {
|
||||
relayPool?.unsubscribeAll()
|
||||
if (relayPool && publicKey) {
|
||||
setFirstLoad(false)
|
||||
calculateInitialNotes().then(() => loadNotes())
|
||||
}
|
||||
}, [publicKey, relayPool])
|
||||
|
||||
useEffect(() => {
|
||||
if (!firstLoad) {
|
||||
loadNotes()
|
||||
}
|
||||
}, [lastEventId])
|
||||
|
||||
useEffect(() => {
|
||||
if (pageSize > initialPageSize) {
|
||||
relayPool?.unsubscribeAll()
|
||||
subscribeNotes(authors, true)
|
||||
loadNotes()
|
||||
}
|
||||
}, [pageSize])
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
setRefreshing(true)
|
||||
relayPool?.unsubscribeAll()
|
||||
if (relayPool && publicKey) {
|
||||
calculateInitialNotes().then(() => loadNotes())
|
||||
}
|
||||
}, [])
|
||||
|
||||
const subscribeNotes: (users: User[], past?: boolean) => void = async (users, past) => {
|
||||
if (!database || !publicKey) return
|
||||
|
||||
const lastNotes: Note[] = await getMainNotes(database, publicKey, initialPageSize)
|
||||
const lastNote: Note = lastNotes[lastNotes.length - 1]
|
||||
|
||||
const message: RelayFilters = {
|
||||
kinds: [EventKind.textNote, EventKind.recommendServer],
|
||||
authors: [...users.map((user) => user.id), publicKey],
|
||||
}
|
||||
|
||||
if (lastNote && lastNotes.length >= pageSize && !past) {
|
||||
message.since = lastNote.created_at
|
||||
} else {
|
||||
message.limit = pageSize + initialPageSize
|
||||
}
|
||||
|
||||
relayPool?.subscribe('homepage-main', [message])
|
||||
}
|
||||
|
||||
const calculateInitialNotes: () => Promise<void> = async () => {
|
||||
if (database && publicKey) {
|
||||
relayPool?.subscribe('homepage-contacts', [
|
||||
{
|
||||
kinds: [EventKind.petNames],
|
||||
authors: [publicKey],
|
||||
},
|
||||
])
|
||||
const users = await getUsers(database, { contacts: true, includeIds: [publicKey] })
|
||||
subscribeNotes(users)
|
||||
setAuthors(users)
|
||||
}
|
||||
}
|
||||
|
||||
const onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void = (event) => {
|
||||
if (handleInfinityScroll(event)) {
|
||||
setPageSize(pageSize + initialPageSize)
|
||||
}
|
||||
}
|
||||
|
||||
const loadNotes: () => void = () => {
|
||||
if (database && publicKey) {
|
||||
getMainNotes(database, publicKey, pageSize).then((notes) => {
|
||||
setNotes(notes)
|
||||
setRefreshing(false)
|
||||
relayPool?.subscribe('homepage-contacts-meta', [
|
||||
{
|
||||
kinds: [EventKind.meta],
|
||||
authors: notes.map((note) => note.pubkey),
|
||||
},
|
||||
{
|
||||
kinds: [EventKind.reaction],
|
||||
'#e': notes.map((note) => note.id ?? ''),
|
||||
},
|
||||
])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const renderItem: (note: Note) => JSX.Element = (note) => {
|
||||
return (
|
||||
<View style={styles.noteCard} key={note.id}>
|
||||
<NoteCard
|
||||
note={note}
|
||||
onPressOptions={() => {
|
||||
setProfileCardPubKey(note.pubkey)
|
||||
bottomSheetProfileRef.current?.open()
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const bottomSheetStyles = React.useMemo(() => {
|
||||
return {
|
||||
container: {
|
||||
backgroundColor: theme.colors.background,
|
||||
padding: 16,
|
||||
borderTopRightRadius: 28,
|
||||
borderTopLeftRadius: 28,
|
||||
},
|
||||
draggableIcon: {
|
||||
backgroundColor: '#000',
|
||||
},
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
{notes && notes.length > 0 && (
|
||||
<ScrollView
|
||||
onScroll={onScroll}
|
||||
horizontal={false}
|
||||
showsVerticalScrollIndicator={false}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
|
||||
style={styles.list}
|
||||
>
|
||||
{notes.map((note) => renderItem(note))}
|
||||
{notes.length >= 10 && <ActivityIndicator animating={true} />}
|
||||
</ScrollView>
|
||||
)}
|
||||
<AnimatedFAB
|
||||
style={[styles.fab, { top: Dimensions.get('window').height - 220 }]}
|
||||
icon='pencil-outline'
|
||||
label='Label'
|
||||
onPress={() => navigate('Send')}
|
||||
animateFrom='right'
|
||||
iconMode='static'
|
||||
extended={false}
|
||||
/>
|
||||
<RBSheet
|
||||
ref={bottomSheetProfileRef}
|
||||
closeOnDragDown={true}
|
||||
height={280}
|
||||
customStyles={bottomSheetStyles}
|
||||
>
|
||||
<ProfileCard userPubKey={profileCardPubkey ?? ''} bottomSheetRef={bottomSheetProfileRef} />
|
||||
</RBSheet>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
list: {
|
||||
padding: 16,
|
||||
},
|
||||
noteCard: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
fab: {
|
||||
right: 16,
|
||||
position: 'absolute',
|
||||
},
|
||||
})
|
||||
|
||||
export default HomeFeed
|
@ -1,233 +1,31 @@
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react'
|
||||
import { t } from 'i18next'
|
||||
import {
|
||||
Dimensions,
|
||||
NativeScrollEvent,
|
||||
NativeSyntheticEvent,
|
||||
RefreshControl,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
} from 'react-native'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { getMainNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
|
||||
import NoteCard from '../../Components/NoteCard'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { EventKind } from '../../lib/nostr/Events'
|
||||
import { getReplyEventId } from '../../Functions/RelayFunctions/Events'
|
||||
import { getUsers, User } from '../../Functions/DatabaseFunctions/Users'
|
||||
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
||||
import { RelayFilters } from '../../lib/nostr/RelayPool/intex'
|
||||
import { AnimatedFAB, BottomNavigation, Text, useTheme } from 'react-native-paper'
|
||||
import { navigate } from '../../lib/Navigation'
|
||||
import React from 'react'
|
||||
import { BottomNavigation } from 'react-native-paper'
|
||||
import ContactsFeed from '../ContactsFeed'
|
||||
import HomeFeed from '../HomeFeed'
|
||||
import NotificationsFeed from '../NotificationsFeed'
|
||||
|
||||
export const HomePage: React.FC = () => {
|
||||
const { database, goToPage } = useContext(AppContext)
|
||||
const initialPageSize = 10
|
||||
const { lastEventId, relayPool, publicKey, privateKey } = useContext(RelayPoolContext)
|
||||
const theme = useTheme()
|
||||
const [pageSize, setPageSize] = useState<number>(initialPageSize)
|
||||
const [notes, setNotes] = useState<Note[]>([])
|
||||
const [authors, setAuthors] = useState<User[]>([])
|
||||
const [refreshing, setRefreshing] = useState(true)
|
||||
const [firstLoad, setFirstLoad] = useState(true)
|
||||
const [index, setIndex] = React.useState(0);
|
||||
const [index, setIndex] = React.useState(0)
|
||||
const [routes] = React.useState([
|
||||
{ key: 'home', focusedIcon: 'home', unfocusedIcon: 'home-outline'},
|
||||
{ key: 'feed', focusedIcon: 'home', unfocusedIcon: 'home-outline' },
|
||||
{ key: 'contacts', focusedIcon: 'account-group', unfocusedIcon: 'account-group-outline' },
|
||||
{ key: 'notifications', focusedIcon: 'bell', unfocusedIcon: 'bell-outline' },
|
||||
])
|
||||
|
||||
const calculateInitialNotes: () => Promise<void> = async () => {
|
||||
if (database && publicKey) {
|
||||
relayPool?.subscribe('homepage-contacts', [
|
||||
{
|
||||
kinds: [EventKind.petNames],
|
||||
authors: [publicKey],
|
||||
},
|
||||
])
|
||||
const users = await getUsers(database, { contacts: true, includeIds: [publicKey] })
|
||||
subscribeNotes(users)
|
||||
setAuthors(users)
|
||||
}
|
||||
}
|
||||
|
||||
const subscribeNotes: (users: User[], past?: boolean) => void = async (users, past) => {
|
||||
if (!database || !publicKey) return
|
||||
|
||||
const lastNotes: Note[] = await getMainNotes(database, publicKey, initialPageSize)
|
||||
const lastNote: Note = lastNotes[lastNotes.length - 1]
|
||||
|
||||
const message: RelayFilters = {
|
||||
kinds: [EventKind.textNote, EventKind.recommendServer],
|
||||
authors: [...users.map((user) => user.id), publicKey],
|
||||
}
|
||||
|
||||
if (lastNote && lastNotes.length >= pageSize && !past) {
|
||||
message.since = lastNote.created_at
|
||||
} else {
|
||||
message.limit = pageSize + initialPageSize
|
||||
}
|
||||
|
||||
relayPool?.subscribe('homepage-main', [message])
|
||||
}
|
||||
|
||||
const loadNotes: () => void = () => {
|
||||
if (database && publicKey) {
|
||||
getMainNotes(database, publicKey, pageSize).then((notes) => {
|
||||
setNotes(notes)
|
||||
setRefreshing(false)
|
||||
relayPool?.subscribe('homepage-contacts-meta', [
|
||||
{
|
||||
kinds: [EventKind.meta],
|
||||
authors: notes.map((note) => note.pubkey),
|
||||
},
|
||||
{
|
||||
kinds: [EventKind.reaction],
|
||||
'#e': notes.map((note) => note.id ?? ''),
|
||||
},
|
||||
])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
relayPool?.unsubscribeAll()
|
||||
if (relayPool && publicKey) {
|
||||
setFirstLoad(false)
|
||||
calculateInitialNotes().then(() => loadNotes())
|
||||
}
|
||||
}, [publicKey, relayPool])
|
||||
|
||||
useEffect(() => {
|
||||
if (!firstLoad) {
|
||||
loadNotes()
|
||||
}
|
||||
}, [lastEventId])
|
||||
|
||||
useEffect(() => {
|
||||
if (pageSize > initialPageSize) {
|
||||
relayPool?.unsubscribeAll()
|
||||
subscribeNotes(authors, true)
|
||||
loadNotes()
|
||||
}
|
||||
}, [pageSize])
|
||||
|
||||
const onPress: (note: Note) => void = (note) => {
|
||||
if (note.kind !== EventKind.recommendServer) {
|
||||
const replyEventId = getReplyEventId(note)
|
||||
if (replyEventId) {
|
||||
goToPage(`note#${replyEventId}`)
|
||||
} else if (note.id) {
|
||||
goToPage(`note#${note.id}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const itemCard: (note: Note) => JSX.Element = (note) => {
|
||||
return (
|
||||
// <Card onPress={() => onPress(note)} key={note.id ?? ''}>
|
||||
// <NoteCard note={note} onlyContactsReplies={true} showReplies={true} />
|
||||
// </Card>
|
||||
<></>
|
||||
)
|
||||
}
|
||||
|
||||
const onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void = (event) => {
|
||||
if (handleInfinityScroll(event)) {
|
||||
setPageSize(pageSize + initialPageSize)
|
||||
}
|
||||
}
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
setRefreshing(true)
|
||||
relayPool?.unsubscribeAll()
|
||||
if (relayPool && publicKey) {
|
||||
calculateInitialNotes().then(() => loadNotes())
|
||||
}
|
||||
}, [])
|
||||
|
||||
const HomeRoute: () => JSX.Element = () => <Text>MyFeedRoute</Text>
|
||||
|
||||
const renderScene = BottomNavigation.SceneMap({
|
||||
home: HomeRoute,
|
||||
feed: HomeFeed,
|
||||
contacts: ContactsFeed,
|
||||
notifications: NotificationsFeed,
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <Layout style={styles.container} level='3'>
|
||||
{notes.length > 0 ? (
|
||||
<ScrollView
|
||||
onScroll={onScroll}
|
||||
horizontal={false}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
|
||||
>
|
||||
{notes.map((note) => itemCard(note))}
|
||||
{notes.length >= 10 && (
|
||||
<Layout style={styles.spinner}>
|
||||
<Spinner size='small' />
|
||||
</Layout>
|
||||
)}
|
||||
</ScrollView>
|
||||
) : (
|
||||
<Layout style={styles.empty} level='3'>
|
||||
<Layout style={styles.noContacts} level='3'>
|
||||
<Text>{t('homePage.noContacts')}</Text>
|
||||
</Layout>
|
||||
<Button
|
||||
onPress={() => goToPage('contacts')}
|
||||
status='warning'
|
||||
accessoryLeft={
|
||||
<Icon name='address-book' size={16} color={theme['text-basic-color']} solid />
|
||||
}
|
||||
>
|
||||
{t('homePage.addContacts')}
|
||||
</Button>
|
||||
</Layout>
|
||||
)}
|
||||
</Layout>
|
||||
{privateKey && (
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(0,0,0,0.2)',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: 65,
|
||||
position: 'absolute',
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
height: 65,
|
||||
backgroundColor: theme['color-warning-500'],
|
||||
borderRadius: 100,
|
||||
}}
|
||||
onPress={() => goToPage('send')}
|
||||
>
|
||||
<Icon name='paper-plane' size={30} color={theme['text-basic-color']} solid />
|
||||
</TouchableOpacity>
|
||||
)} */}
|
||||
<BottomNavigation
|
||||
navigationState={{ index, routes }}
|
||||
onIndexChange={setIndex}
|
||||
renderScene={renderScene}
|
||||
labeled={false}
|
||||
/>
|
||||
<AnimatedFAB
|
||||
style={[styles.fab, { top: Dimensions.get('window').height - 220 }]}
|
||||
icon='pencil-outline'
|
||||
label='Label'
|
||||
onPress={() => navigate('Send')}
|
||||
animateFrom='right'
|
||||
iconMode='static'
|
||||
extended={false}
|
||||
/>
|
||||
</>
|
||||
<BottomNavigation
|
||||
navigationState={{ index, routes }}
|
||||
onIndexChange={setIndex}
|
||||
renderScene={renderScene}
|
||||
labeled={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
fab: {
|
||||
right: 16,
|
||||
position: 'absolute',
|
||||
},
|
||||
})
|
||||
|
||||
export default HomePage
|
||||
|
@ -1,146 +0,0 @@
|
||||
import { Card, Layout, Spinner, Text } from '@ui-kitten/components'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { t } from 'i18next'
|
||||
import { NativeScrollEvent, NativeSyntheticEvent, ScrollView, StyleSheet } from 'react-native'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { getMentionNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
|
||||
import NoteCard from '../../Components/NoteCard'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { EventKind } from '../../lib/nostr/Events'
|
||||
import { getReplyEventId } from '../../Functions/RelayFunctions/Events'
|
||||
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
||||
|
||||
export const MentionsPage: React.FC = () => {
|
||||
const { database, goToPage } = useContext(AppContext)
|
||||
const initialPageSize = 10
|
||||
const { lastEventId, relayPool, publicKey } = useContext(RelayPoolContext)
|
||||
const [pageSize, setPageSize] = useState<number>(initialPageSize)
|
||||
const [notes, setNotes] = useState<Note[]>([])
|
||||
|
||||
const calculateInitialNotes: () => Promise<void> = async () => {
|
||||
if (database && publicKey) {
|
||||
subscribeNotes()
|
||||
}
|
||||
}
|
||||
|
||||
const subscribeNotes: () => void = async () => {
|
||||
if (!database || !publicKey) return
|
||||
|
||||
relayPool?.subscribe('mentions-user', [
|
||||
{
|
||||
kinds: [EventKind.textNote],
|
||||
'#p': [publicKey],
|
||||
limit: pageSize,
|
||||
},
|
||||
{
|
||||
kinds: [EventKind.textNote],
|
||||
'#e': [publicKey],
|
||||
limit: pageSize,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
const loadNotes: () => void = () => {
|
||||
if (database && publicKey) {
|
||||
getMentionNotes(database, publicKey, pageSize).then((notes) => {
|
||||
setNotes(notes)
|
||||
const missingDataNotes = notes.map((note) => note.pubkey)
|
||||
relayPool?.subscribe('mentions-answers', [
|
||||
{
|
||||
kinds: [EventKind.reaction],
|
||||
'#e': notes.map((note) => note.id ?? ''),
|
||||
},
|
||||
{
|
||||
kinds: [EventKind.meta],
|
||||
authors: missingDataNotes,
|
||||
},
|
||||
])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
relayPool?.unsubscribeAll()
|
||||
if (relayPool && publicKey) {
|
||||
calculateInitialNotes().then(() => loadNotes())
|
||||
}
|
||||
}, [publicKey, relayPool])
|
||||
|
||||
useEffect(() => {
|
||||
loadNotes()
|
||||
}, [lastEventId])
|
||||
|
||||
useEffect(() => {
|
||||
if (pageSize > initialPageSize) {
|
||||
relayPool?.unsubscribeAll()
|
||||
subscribeNotes()
|
||||
loadNotes()
|
||||
}
|
||||
}, [pageSize])
|
||||
|
||||
const onPress: (note: Note) => void = (note) => {
|
||||
if (note.kind !== EventKind.recommendServer) {
|
||||
const replyEventId = getReplyEventId(note)
|
||||
if (replyEventId) {
|
||||
goToPage(`note#${replyEventId}`)
|
||||
} else if (note.id) {
|
||||
goToPage(`note#${note.id}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const itemCard: (note: Note) => JSX.Element = (note) => {
|
||||
return (
|
||||
<Card onPress={() => onPress(note)} key={note.id ?? ''}>
|
||||
<NoteCard note={note} onlyContactsReplies={true} showReplies={true} />
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void = (event) => {
|
||||
if (handleInfinityScroll(event)) {
|
||||
setPageSize(pageSize + initialPageSize)
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
icon: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
},
|
||||
empty: {
|
||||
height: 64,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
spinner: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: 64,
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<Layout style={styles.container} level='3'>
|
||||
{notes && notes.length > 0 ? (
|
||||
<ScrollView onScroll={onScroll} horizontal={false}>
|
||||
{notes.map((note) => itemCard(note))}
|
||||
{notes.length >= 10 && (
|
||||
<Layout style={styles.spinner}>
|
||||
<Spinner size='small' />
|
||||
</Layout>
|
||||
)}
|
||||
</ScrollView>
|
||||
) : (
|
||||
<Layout style={styles.empty} level='3'>
|
||||
<Text>{t('mentionsPage.noMentions')}</Text>
|
||||
</Layout>
|
||||
)}
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default MentionsPage
|
@ -144,6 +144,7 @@ export const NotePage: React.FC<NotePageProps> = ({ route }) => {
|
||||
setProfileCardPubKey(note.pubkey)
|
||||
bottomSheetProfileRef.current?.open()
|
||||
}}
|
||||
showAnswerData={false}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
|
172
frontend/Pages/NotificationsFeed/index.tsx
Normal file
172
frontend/Pages/NotificationsFeed/index.tsx
Normal file
@ -0,0 +1,172 @@
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react'
|
||||
import {
|
||||
NativeScrollEvent,
|
||||
NativeSyntheticEvent,
|
||||
RefreshControl,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import { AppContext } from '../../Contexts/AppContext'
|
||||
import { getMentionNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
|
||||
import NoteCard from '../../Components/NoteCard'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { EventKind } from '../../lib/nostr/Events'
|
||||
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
|
||||
import { UserContext } from '../../Contexts/UserContext'
|
||||
import RBSheet from 'react-native-raw-bottom-sheet'
|
||||
import { ActivityIndicator, useTheme } from 'react-native-paper'
|
||||
import ProfileCard from '../../Components/ProfileCard'
|
||||
|
||||
export const NotificationsFeed: React.FC = () => {
|
||||
const theme = useTheme()
|
||||
const { database } = useContext(AppContext)
|
||||
const { publicKey } = useContext(UserContext)
|
||||
const initialPageSize = 10
|
||||
const { lastEventId, relayPool } = useContext(RelayPoolContext)
|
||||
const [pageSize, setPageSize] = useState<number>(initialPageSize)
|
||||
const [notes, setNotes] = useState<Note[]>([])
|
||||
const bottomSheetProfileRef = React.useRef<RBSheet>(null)
|
||||
const [refreshing, setRefreshing] = useState(true)
|
||||
const [profileCardPubkey, setProfileCardPubKey] = useState<string>()
|
||||
|
||||
useEffect(() => {
|
||||
relayPool?.unsubscribeAll()
|
||||
if (relayPool && publicKey) {
|
||||
calculateInitialNotes().then(() => loadNotes())
|
||||
}
|
||||
}, [publicKey, relayPool])
|
||||
|
||||
useEffect(() => {
|
||||
loadNotes()
|
||||
}, [lastEventId])
|
||||
|
||||
useEffect(() => {
|
||||
if (pageSize > initialPageSize) {
|
||||
relayPool?.unsubscribeAll()
|
||||
subscribeNotes()
|
||||
loadNotes()
|
||||
}
|
||||
}, [pageSize])
|
||||
|
||||
const calculateInitialNotes: () => Promise<void> = async () => {
|
||||
if (database && publicKey) {
|
||||
subscribeNotes()
|
||||
}
|
||||
}
|
||||
|
||||
const subscribeNotes: () => void = async () => {
|
||||
if (!database || !publicKey) return
|
||||
|
||||
relayPool?.subscribe('mentions-user', [
|
||||
{
|
||||
kinds: [EventKind.textNote],
|
||||
'#p': [publicKey],
|
||||
limit: pageSize,
|
||||
},
|
||||
{
|
||||
kinds: [EventKind.textNote],
|
||||
'#e': [publicKey],
|
||||
limit: pageSize,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
const loadNotes: () => void = () => {
|
||||
if (database && publicKey) {
|
||||
getMentionNotes(database, publicKey, pageSize).then((notes) => {
|
||||
setNotes(notes)
|
||||
setRefreshing(false)
|
||||
const missingDataNotes = notes.map((note) => note.pubkey)
|
||||
relayPool?.subscribe('mentions-answers', [
|
||||
{
|
||||
kinds: [EventKind.reaction],
|
||||
'#e': notes.map((note) => note.id ?? ''),
|
||||
},
|
||||
{
|
||||
kinds: [EventKind.meta],
|
||||
authors: missingDataNotes,
|
||||
},
|
||||
])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onRefresh = useCallback(() => {
|
||||
setRefreshing(true)
|
||||
relayPool?.unsubscribeAll()
|
||||
if (relayPool && publicKey) {
|
||||
calculateInitialNotes().then(() => loadNotes())
|
||||
}
|
||||
}, [])
|
||||
|
||||
const renderItem: (note: Note) => JSX.Element = (note) => {
|
||||
return (
|
||||
<View style={styles.noteCard} key={note.id}>
|
||||
<NoteCard
|
||||
note={note}
|
||||
onPressOptions={() => {
|
||||
setProfileCardPubKey(note.pubkey)
|
||||
bottomSheetProfileRef.current?.open()
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void = (event) => {
|
||||
if (handleInfinityScroll(event)) {
|
||||
setPageSize(pageSize + initialPageSize)
|
||||
}
|
||||
}
|
||||
|
||||
const bottomSheetStyles = React.useMemo(() => {
|
||||
return {
|
||||
container: {
|
||||
backgroundColor: theme.colors.background,
|
||||
padding: 16,
|
||||
borderTopRightRadius: 28,
|
||||
borderTopLeftRadius: 28,
|
||||
},
|
||||
draggableIcon: {
|
||||
backgroundColor: '#000',
|
||||
},
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
{notes && notes.length > 0 && (
|
||||
<ScrollView
|
||||
onScroll={onScroll}
|
||||
horizontal={false}
|
||||
showsVerticalScrollIndicator={false}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
|
||||
style={styles.list}
|
||||
>
|
||||
{notes.map((note) => renderItem(note))}
|
||||
{notes.length >= 10 && <ActivityIndicator animating={true} />}
|
||||
</ScrollView>
|
||||
)}
|
||||
<RBSheet
|
||||
ref={bottomSheetProfileRef}
|
||||
closeOnDragDown={true}
|
||||
height={280}
|
||||
customStyles={bottomSheetStyles}
|
||||
>
|
||||
<ProfileCard userPubKey={profileCardPubkey ?? ''} bottomSheetRef={bottomSheetProfileRef} />
|
||||
</RBSheet>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
list: {
|
||||
padding: 16,
|
||||
},
|
||||
noteCard: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
})
|
||||
|
||||
export default NotificationsFeed
|
@ -41,11 +41,14 @@ export const SendPage: React.FC<SendPageProps> = ({ route }) => {
|
||||
const match = text.match(/@(.*)$/)
|
||||
const note: Note | undefined = route.params?.note
|
||||
if (database && match && match?.length > 0) {
|
||||
let request = getUsers(database, { name: match[1], order: 'contact' })
|
||||
let request = getUsers(database, { name: match[1], order: 'contact DESC,name ASC' })
|
||||
|
||||
if (match[1] === '' && note) {
|
||||
const taggedPubKeys = getTaggedPubKeys(note)
|
||||
request = getUsers(database, { includeIds: [...taggedPubKeys, note.pubkey], order: 'contact' })
|
||||
request = getUsers(database, {
|
||||
includeIds: [...taggedPubKeys, note.pubkey],
|
||||
order: 'contact DESC,name ASC',
|
||||
})
|
||||
}
|
||||
|
||||
request.then((results) => {
|
||||
@ -153,17 +156,20 @@ export const SendPage: React.FC<SendPageProps> = ({ route }) => {
|
||||
onChangeText={onChangeText}
|
||||
/>
|
||||
</View>
|
||||
{/* FIXME: can't find this color */}
|
||||
<View style={styles.actions}>
|
||||
{/* flexDirection: 'column-reverse' */}
|
||||
{userSuggestions.length > 0 ? (
|
||||
<FlatList
|
||||
style={styles.contactsList}
|
||||
ItemSeparatorComponent={Divider}
|
||||
data={userSuggestions}
|
||||
renderItem={renderContactItem}
|
||||
/>
|
||||
// FIXME: can't find this color
|
||||
<View style={{ backgroundColor: '#001C37' }}>
|
||||
<FlatList
|
||||
style={styles.contactsList}
|
||||
ItemSeparatorComponent={Divider}
|
||||
data={userSuggestions}
|
||||
renderItem={renderContactItem}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
// FIXME: can't find this color
|
||||
<View style={{ backgroundColor: '#001C37' }}>
|
||||
<View style={styles.contentWarning}>
|
||||
<Text>{t('sendPage.contentWarning')}</Text>
|
||||
@ -204,7 +210,6 @@ const styles = StyleSheet.create({
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
backgroundColor: '#001C37', // FIXME: somehow it can't be imported from theme
|
||||
},
|
||||
contactName: {
|
||||
paddingLeft: 16,
|
||||
|
Loading…
Reference in New Issue
Block a user