Infinite Loader

This commit is contained in:
KoalaSat 2022-11-01 02:30:08 +01:00
parent 12379ee151
commit 2abf638ec3
No known key found for this signature in database
GPG Key ID: 2F7F61C6146AB157
7 changed files with 163 additions and 52 deletions

View File

@ -53,7 +53,7 @@ yarn start
### Basics
- [ ] Infinite Load
- [x] Infinite Load
- [x] Go to replied event
- [ ] Relays management (add, remove and recomend)
- [ ] Random Key Generator

View File

@ -109,6 +109,7 @@ export const ContactsPage: React.FC = () => {
<>
<Layout style={styles.container} level='3'>
<ScrollView
horizontal={false}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
>
{users.map((user) => (

View File

@ -1,6 +1,14 @@
import { Card, Layout, useTheme } from '@ui-kitten/components'
import { Card, Layout, Spinner, useTheme } from '@ui-kitten/components'
import React, { useCallback, useContext, useEffect, useState } from 'react'
import { RefreshControl, ScrollView, StyleSheet, TouchableOpacity } from 'react-native'
import {
NativeScrollEvent,
NativeSyntheticEvent,
RefreshControl,
ScrollView,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native'
import { AppContext } from '../../Contexts/AppContext'
import { getNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
import NoteCard from '../NoteCard'
@ -9,44 +17,27 @@ import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
import { EventKind } from '../../lib/nostr/Events'
import { RelayFilters } from '../../lib/nostr/Relay'
import { getReplyEventId } from '../../Functions/RelayFunctions/Events'
import { getUsers } from '../../Functions/DatabaseFunctions/Users'
import { getUsers, User } from '../../Functions/DatabaseFunctions/Users'
import Loading from '../Loading'
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
export const HomePage: React.FC = () => {
const { database, goToPage, page } = useContext(AppContext)
const initialPageSize = 15
const { lastEventId, relayPool, publicKey, privateKey } = useContext(RelayPoolContext)
const theme = useTheme()
const [pageSize, setPageSize] = useState<number>(initialPageSize)
const [notes, setNotes] = useState<Note[]>([])
const [totalContacts, setTotalContacts] = useState<number>(-1)
const [authors, setAuthors] = useState<User[]>([])
const [refreshing, setRefreshing] = useState(false)
const loadNotes: () => void = () => {
if (database && publicKey) {
getNotes(database, { contacts: true, includeIds: [publicKey], limit: 15 }).then((notes) => {
setNotes(notes)
})
}
}
const subscribeNotes: () => Promise<void> = async () => {
const calculateInitialNotes: () => Promise<void> = async () => {
return await new Promise<void>((resolve, reject) => {
if (database && publicKey && relayPool) {
getNotes(database, { limit: 1 }).then((notes) => {
getUsers(database, { contacts: true, includeIds: [publicKey] }).then((users) => {
setTotalContacts(users.length)
let message: RelayFilters = {
kinds: [EventKind.textNote, EventKind.recommendServer],
authors: users.map((user) => user.id),
limit: 15,
}
if (notes.length !== 0) {
message = {
...message,
since: notes[0].created_at,
}
}
relayPool?.subscribe('main-channel', message)
setAuthors(users)
subscribeNotes(users, notes[0]?.created_at)
resolve()
})
})
@ -56,6 +47,36 @@ export const HomePage: React.FC = () => {
})
}
const subscribeNotes: (users: User[], since?: number, until?: number) => void = (
users,
since,
until,
) => {
const message: RelayFilters = {
kinds: [EventKind.textNote, EventKind.recommendServer],
authors: users.map((user) => user.id),
limit: initialPageSize,
}
if (since) {
message.since = since
}
if (until) {
message.until = until
}
console.log('message', message)
relayPool?.subscribe('main-channel', message)
}
const loadNotes: () => void = () => {
if (database && publicKey) {
getNotes(database, { contacts: true, includeIds: [publicKey], limit: pageSize }).then(
(notes) => {
setNotes(notes)
},
)
}
}
useEffect(() => {
relayPool?.unsubscribeAll()
}, [])
@ -64,15 +85,23 @@ export const HomePage: React.FC = () => {
loadNotes()
}, [lastEventId])
useEffect(() => {
if (pageSize > initialPageSize) {
relayPool?.unsubscribeAll()
subscribeNotes(authors, undefined, notes[notes.length - 1]?.created_at)
loadNotes()
}
}, [pageSize])
useEffect(() => {
loadNotes()
subscribeNotes()
calculateInitialNotes()
}, [database, publicKey, relayPool])
const onRefresh = useCallback(() => {
setRefreshing(true)
relayPool?.unsubscribeAll()
subscribeNotes().finally(() => setRefreshing(false))
calculateInitialNotes().finally(() => setRefreshing(false))
}, [])
const onPress: (note: Note) => void = (note) => {
@ -94,6 +123,12 @@ export const HomePage: React.FC = () => {
)
}
const onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void = (event) => {
if (handleInfinityScroll(event)) {
setPageSize(pageSize + initialPageSize)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
@ -102,19 +137,33 @@ export const HomePage: React.FC = () => {
width: 32,
height: 32,
},
loadingBottom: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
flexWrap: 'wrap',
padding: 12,
},
})
return (
<>
<Layout style={styles.container} level='4'>
{notes.length === 0 && totalContacts !== 0 ? (
{notes.length === 0 && authors.length !== 0 ? (
<Loading />
) : (
<ScrollView
onScroll={onScroll}
horizontal={false}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
>
{notes.map((note) => itemCard(note))}
{notes.length >= initialPageSize && (
<View style={styles.loadingBottom}>
<Spinner size='tiny' />
</View>
)}
</ScrollView>
)}
</Layout>

View File

@ -183,6 +183,7 @@ export const NotePage: React.FC = () => {
<Layout level='4'>
{note ? (
<ScrollView
horizontal={false}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
>
{[note, ...(replies ?? [])].map((note) => itemCard(note))}

View File

@ -8,7 +8,16 @@ import {
useTheme,
} from '@ui-kitten/components'
import React, { useCallback, useContext, useEffect, useState } from 'react'
import { Clipboard, RefreshControl, ScrollView, StyleSheet, TouchableOpacity } from 'react-native'
import {
Clipboard,
NativeScrollEvent,
NativeSyntheticEvent,
RefreshControl,
ScrollView,
StyleSheet,
TouchableOpacity,
View,
} from 'react-native'
import { AppContext } from '../../Contexts/AppContext'
import UserAvatar from 'react-native-user-avatar'
import { getNotes, Note } from '../../Functions/DatabaseFunctions/Notes'
@ -28,14 +37,17 @@ import { populatePets, tagToUser } from '../../Functions/RelayFunctions/Users'
import { getReplyEventId } from '../../Functions/RelayFunctions/Events'
import Loading from '../Loading'
import { storeEvent } from '../../Functions/DatabaseFunctions/Events'
import { handleInfinityScroll } from '../../Functions/NativeFunctions'
export const ProfilePage: React.FC = () => {
const { database, page, goToPage, goBack } = useContext(AppContext)
const { publicKey, privateKey, lastEventId, relayPool, setLastEventId } =
useContext(RelayPoolContext)
const theme = useTheme()
const initialPageSize = 10
const [notes, setNotes] = useState<Note[]>()
const [user, setUser] = useState<User>()
const [pageSize, setPageSize] = useState<number>(initialPageSize)
const [contactsIds, setContactsIds] = useState<string[]>()
const [isContact, setIsContact] = useState<boolean>()
const [refreshing, setRefreshing] = useState(false)
@ -63,9 +75,7 @@ export const ProfilePage: React.FC = () => {
setContactsIds(users.map((user) => user.id))
})
}
getNotes(database, { filters: { pubkey: userId }, limit: 10 }).then((results) => {
if (results.length > 0) setNotes(results)
})
loadNotes()
}
}, [lastEventId])
@ -113,7 +123,7 @@ export const ProfilePage: React.FC = () => {
)
}
} else {
return <Spinner size='tiny' />
return <Spinner size='small' />
}
}
}
@ -143,21 +153,42 @@ export const ProfilePage: React.FC = () => {
loadProfile().finally(() => setRefreshing(false))
}, [])
const subscribeNotes: () => void = () => {
useEffect(() => {
if (pageSize > initialPageSize && notes) {
relayPool?.unsubscribeAll()
subscribeNotes(undefined, notes[notes.length - 1]?.created_at)
loadNotes()
}
}, [pageSize])
const loadNotes: () => void = () => {
if (database) {
getNotes(database, { filters: { pubkey: userId }, limit: 10 }).then((results) => {
getNotes(database, { filters: { pubkey: userId }, limit: pageSize }).then((results) => {
if (results.length > 0) setNotes(results)
})
}
}
const subscribeNotes: (since?: number, until?: number) => void = (since, until) => {
const message: RelayFilters = {
kinds: [EventKind.textNote, EventKind.recommendServer],
authors: [userId],
limit: initialPageSize,
}
if (since) {
message.since = since
}
if (until) {
message.until = until
}
relayPool?.subscribe('main-channel', message)
}
const calculateInitialNotes: () => void = () => {
if (database) {
getNotes(database, { filters: { pubkey: userId }, limit: pageSize }).then((results) => {
if (results) {
const notesEvent: RelayFilters = {
kinds: [EventKind.textNote, EventKind.recommendServer],
authors: [userId],
limit: 10,
}
if (results.length >= 10) {
notesEvent.since = results[0]?.created_at
}
relayPool?.subscribe('main-channel', notesEvent)
subscribeNotes(results[0]?.created_at)
}
})
}
@ -181,7 +212,7 @@ export const ProfilePage: React.FC = () => {
if (event?.id) setLastEventId(event.id)
})
}
subscribeNotes()
calculateInitialNotes()
}
} else {
reject(new Error('Not Ready'))
@ -191,6 +222,12 @@ export const ProfilePage: React.FC = () => {
})
}
const onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void = (event) => {
if (handleInfinityScroll(event)) {
setPageSize(pageSize + initialPageSize)
}
}
const styles = StyleSheet.create({
list: {
flex: 1,
@ -234,6 +271,14 @@ export const ProfilePage: React.FC = () => {
marginTop: 16,
flexDirection: 'row',
},
loadingBottom: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
flexWrap: 'wrap',
padding: 12,
},
})
const itemCard: (note: Note) => JSX.Element = (note) => {
@ -270,7 +315,7 @@ export const ProfilePage: React.FC = () => {
if (contactsIds === undefined) {
return (
<Layout style={styles.stats} level='3'>
<Spinner size='tiny' />
<Spinner size='small' />
</Layout>
)
}
@ -341,9 +386,16 @@ export const ProfilePage: React.FC = () => {
<Layout style={styles.list} level='3'>
{notes ? (
<ScrollView
onScroll={onScroll}
horizontal={false}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
>
{notes.map((note) => itemCard(note))}
{notes.length >= initialPageSize && (
<View style={styles.loadingBottom}>
<Spinner size='tiny' />
</View>
)}
</ScrollView>
) : (
<Loading />

View File

@ -125,7 +125,7 @@ export const SendPage: React.FC = () => {
<Button
onPress={onPressSend}
disabled={sending}
accessoryLeft={sending ? <Spinner size='tiny' /> : <></>}
accessoryLeft={sending ? <Spinner size='small' /> : <></>}
>
{t('sendPage.send')}
</Button>

View File

@ -0,0 +1,8 @@
export const handleInfinityScroll: (event: any) => boolean = (event) => {
const mHeight = event.nativeEvent.layoutMeasurement.height
const cSize = event.nativeEvent.contentSize.height
const Y = event.nativeEvent.contentOffset.y
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
if (Math.ceil(mHeight + Y) >= cSize) return true
return false
}