Search is done

This commit is contained in:
Zhiyuan Zheng 2020-12-19 01:57:57 +01:00
parent 7491478176
commit 54799aabb8
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
13 changed files with 250 additions and 109 deletions

View File

@ -120,10 +120,11 @@ const Timeline: React.FC<Props> = ({
() => !disableRefresh && fetchPreviousPage(), () => !disableRefresh && fetchPreviousPage(),
[] []
) )
const flOnEndReach = useCallback(() => fetchNextPage(), []) const flOnEndReach = useCallback(() => !disableRefresh && fetchNextPage(), [])
const flFooter = useCallback(() => { const flFooter = useCallback(
return <TimelineEnd hasNextPage={hasNextPage} /> () => (!disableRefresh ? <TimelineEnd hasNextPage={hasNextPage} /> : null),
}, [hasNextPage]) [hasNextPage]
)
const onScrollToIndexFailed = useCallback(error => { const onScrollToIndexFailed = useCallback(error => {
const offset = error.averageItemLength * error.index const offset = error.averageItemLength * error.index
flRef.current?.scrollToOffset({ offset }) flRef.current?.scrollToOffset({ offset })

View File

@ -38,7 +38,7 @@ const TimelineConversation: React.FC<Props> = ({
paddingTop: highlighted ? StyleConstants.Spacing.S : 0, paddingTop: highlighted ? StyleConstants.Spacing.S : 0,
paddingLeft: highlighted paddingLeft: highlighted
? 0 ? 0
: StyleConstants.Avatar.S + StyleConstants.Spacing.S : StyleConstants.Avatar.M + StyleConstants.Spacing.S
}} }}
> >
<TimelineContent <TimelineContent
@ -71,7 +71,7 @@ const TimelineConversation: React.FC<Props> = ({
style={{ style={{
paddingLeft: highlighted paddingLeft: highlighted
? 0 ? 0
: StyleConstants.Avatar.S + StyleConstants.Spacing.S : StyleConstants.Avatar.M + StyleConstants.Spacing.S
}} }}
> >
<TimelineActions queryKey={queryKey} status={item.last_status!} /> <TimelineActions queryKey={queryKey} status={item.last_status!} />

View File

@ -38,7 +38,7 @@ const TimelineDefault: React.FC<Props> = ({
StyleConstants.Spacing.Global.PagePadding * 2 // Global page padding on both sides StyleConstants.Spacing.Global.PagePadding * 2 // Global page padding on both sides
: Dimensions.get('window').width - : Dimensions.get('window').width -
StyleConstants.Spacing.Global.PagePadding * 2 - // Global page padding on both sides StyleConstants.Spacing.Global.PagePadding * 2 - // Global page padding on both sides
StyleConstants.Avatar.S - // Avatar width StyleConstants.Avatar.M - // Avatar width
StyleConstants.Spacing.S // Avatar margin to the right StyleConstants.Spacing.S // Avatar margin to the right
const tootOnPress = useCallback( const tootOnPress = useCallback(
@ -56,7 +56,7 @@ const TimelineDefault: React.FC<Props> = ({
paddingTop: highlighted ? StyleConstants.Spacing.S : 0, paddingTop: highlighted ? StyleConstants.Spacing.S : 0,
paddingLeft: highlighted paddingLeft: highlighted
? 0 ? 0
: StyleConstants.Avatar.S + StyleConstants.Spacing.S : StyleConstants.Avatar.M + StyleConstants.Spacing.S
}} }}
> >
{actualStatus.content.length > 0 && ( {actualStatus.content.length > 0 && (
@ -100,7 +100,7 @@ const TimelineDefault: React.FC<Props> = ({
style={{ style={{
paddingLeft: highlighted paddingLeft: highlighted
? 0 ? 0
: StyleConstants.Avatar.S + StyleConstants.Spacing.S : StyleConstants.Avatar.M + StyleConstants.Spacing.S
}} }}
> >
<TimelineActions queryKey={queryKey} status={actualStatus} /> <TimelineActions queryKey={queryKey} status={actualStatus} />

View File

@ -33,7 +33,7 @@ const TimelineNotifications: React.FC<Props> = ({
StyleConstants.Spacing.Global.PagePadding * 2 // Global page padding on both sides StyleConstants.Spacing.Global.PagePadding * 2 // Global page padding on both sides
: Dimensions.get('window').width - : Dimensions.get('window').width -
StyleConstants.Spacing.Global.PagePadding * 2 - // Global page padding on both sides StyleConstants.Spacing.Global.PagePadding * 2 - // Global page padding on both sides
StyleConstants.Avatar.S - // Avatar width StyleConstants.Avatar.M - // Avatar width
StyleConstants.Spacing.S // Avatar margin to the right StyleConstants.Spacing.S // Avatar margin to the right
const tootOnPress = useCallback( const tootOnPress = useCallback(
@ -51,7 +51,7 @@ const TimelineNotifications: React.FC<Props> = ({
paddingTop: highlighted ? StyleConstants.Spacing.S : 0, paddingTop: highlighted ? StyleConstants.Spacing.S : 0,
paddingLeft: highlighted paddingLeft: highlighted
? 0 ? 0
: StyleConstants.Avatar.S + StyleConstants.Spacing.S : StyleConstants.Avatar.M + StyleConstants.Spacing.S
}} }}
> >
{notification.status.content.length > 0 && ( {notification.status.content.length > 0 && (
@ -97,7 +97,7 @@ const TimelineNotifications: React.FC<Props> = ({
style={{ style={{
paddingLeft: highlighted paddingLeft: highlighted
? 0 ? 0
: StyleConstants.Avatar.S + StyleConstants.Spacing.S : StyleConstants.Avatar.M + StyleConstants.Spacing.S
}} }}
> >
<TimelineActions queryKey={queryKey} status={notification.status} /> <TimelineActions queryKey={queryKey} status={notification.status} />

View File

@ -20,7 +20,7 @@ const TimelineSeparator: React.FC<Props> = ({ highlighted = false }) => {
marginLeft: highlighted marginLeft: highlighted
? StyleConstants.Spacing.Global.PagePadding ? StyleConstants.Spacing.Global.PagePadding
: StyleConstants.Spacing.Global.PagePadding + : StyleConstants.Spacing.Global.PagePadding +
StyleConstants.Avatar.S + StyleConstants.Avatar.M +
StyleConstants.Spacing.S StyleConstants.Spacing.S
} }
]} ]}

View File

@ -107,7 +107,7 @@ const styles = StyleSheet.create({
marginBottom: StyleConstants.Spacing.S marginBottom: StyleConstants.Spacing.S
}, },
icon: { icon: {
marginLeft: StyleConstants.Avatar.S - StyleConstants.Font.Size.S, marginLeft: StyleConstants.Avatar.M - StyleConstants.Font.Size.S,
marginRight: StyleConstants.Spacing.S marginRight: StyleConstants.Spacing.S
}, },
content: { content: {

View File

@ -27,8 +27,8 @@ const TimelineAvatar: React.FC<Props> = ({ queryKey, account }) => {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
avatar: { avatar: {
flexBasis: StyleConstants.Avatar.S, flexBasis: StyleConstants.Avatar.M,
height: StyleConstants.Avatar.S, height: StyleConstants.Avatar.M,
marginRight: StyleConstants.Spacing.S marginRight: StyleConstants.Spacing.S
}, },
image: { image: {

View File

@ -42,20 +42,14 @@ const Login: React.FC = () => {
const instanceQuery = useQuery( const instanceQuery = useQuery(
['Instance', { instanceDomain }], ['Instance', { instanceDomain }],
instanceFetch, instanceFetch,
{ { enabled: false, retry: false }
enabled: false,
retry: false
}
) )
const applicationQuery = useQuery( const applicationQuery = useQuery(
['Application', { instanceDomain }], ['Application', { instanceDomain }],
applicationFetch, applicationFetch,
{ { enabled: false, retry: false }
enabled: false,
retry: false
}
) )
useEffect(() => { useEffect(() => {
if ( if (
applicationQuery.data?.client_id.length && applicationQuery.data?.client_id.length &&
@ -115,9 +109,6 @@ const Login: React.FC = () => {
text => { text => {
setInstanceDomain(text) setInstanceDomain(text)
setApplicationData(undefined) setApplicationData(undefined)
if (text) {
instanceQuery.refetch()
}
}, },
1000, 1000,
{ {
@ -126,6 +117,11 @@ const Login: React.FC = () => {
), ),
[] []
) )
useEffect(() => {
if (instanceDomain) {
instanceQuery.refetch()
}
}, [instanceDomain])
const instanceInfo = useCallback( const instanceInfo = useCallback(
({ ({
@ -191,12 +187,19 @@ const Login: React.FC = () => {
clearButtonMode='never' clearButtonMode='never'
keyboardType='url' keyboardType='url'
textContentType='URL' textContentType='URL'
onSubmitEditing={() => onSubmitEditing={({ nativeEvent: { text } }) => {
instanceQuery.isSuccess && if (
instanceQuery.data && text === instanceDomain &&
instanceQuery.data.uri && instanceQuery.isSuccess &&
applicationQuery.refetch() instanceQuery.data &&
} instanceQuery.data.uri
) {
applicationQuery.refetch()
} else {
setInstanceDomain(text)
setApplicationData(undefined)
}
}}
placeholder={t('content.login.server.placeholder')} placeholder={t('content.login.server.placeholder')}
placeholderTextColor={theme.secondary} placeholderTextColor={theme.secondary}
returnKeyType='go' returnKeyType='go'

View File

@ -19,7 +19,7 @@ const ComposeReply: React.FC = () => {
const contentWidth = const contentWidth =
Dimensions.get('window').width - Dimensions.get('window').width -
StyleConstants.Spacing.Global.PagePadding * 2 - // Global page padding on both sides StyleConstants.Spacing.Global.PagePadding * 2 - // Global page padding on both sides
StyleConstants.Avatar.S - // Avatar width StyleConstants.Avatar.M - // Avatar width
StyleConstants.Spacing.S // Avatar margin to the right StyleConstants.Spacing.S // Avatar margin to the right
return ( return (

View File

@ -227,7 +227,7 @@ const styles = StyleSheet.create({
width: StyleConstants.Font.LineHeight.M * 2, width: StyleConstants.Font.LineHeight.M * 2,
height: StyleConstants.Font.LineHeight.M * 2, height: StyleConstants.Font.LineHeight.M * 2,
marginRight: StyleConstants.Spacing.S, marginRight: StyleConstants.Spacing.S,
borderRadius: StyleConstants.Avatar.S borderRadius: StyleConstants.Avatar.M
}, },
accountName: { accountName: {
fontSize: StyleConstants.Font.Size.S, fontSize: StyleConstants.Font.Size.S,

View File

@ -1,27 +1,42 @@
import { Feather } from '@expo/vector-icons' import { Feather } from '@expo/vector-icons'
import { useNavigation } from '@react-navigation/native' import { useNavigation } from '@react-navigation/native'
import { HeaderRight } from '@root/components/Header' import { HeaderRight } from '@root/components/Header'
import { MenuHeader, MenuRow } from '@root/components/Menu' import Emojis from '@root/components/Timelines/Timeline/Shared/Emojis'
import { searchFetch } from '@root/utils/fetches/searchFetch' import { searchFetch } from '@root/utils/fetches/searchFetch'
import { StyleConstants } from '@root/utils/styles/constants' import { StyleConstants } from '@root/utils/styles/constants'
import { useTheme } from '@root/utils/styles/ThemeManager' import { useTheme } from '@root/utils/styles/ThemeManager'
import { debounce } from 'lodash' import { debounce } from 'lodash'
import React, { useCallback, useMemo, useState } from 'react' import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { SectionList, StyleSheet, Text, TextInput, View } from 'react-native' import {
Image,
Pressable,
SectionList,
StyleSheet,
Text,
TextInput,
View
} from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import { useQuery } from 'react-query' import { useQuery } from 'react-query'
const ScreenSharedSearch: React.FC = () => { const ScreenSharedSearch: React.FC = () => {
const navigation = useNavigation() const navigation = useNavigation()
const { theme } = useTheme() const { theme } = useTheme()
const [searchTerm, setSearchTerm] = useState<string | undefined>() const [searchTerm, setSearchTerm] = useState<string | undefined>()
const { isFetching, data, refetch } = useQuery( const { status, data, refetch } = useQuery(
['Search', { term: searchTerm }], ['Search', { term: searchTerm }],
searchFetch, searchFetch,
{ enabled: false } { enabled: false }
) )
const transformData = () => {
return data const [setctionData, setSectionData] = useState<
? Object.keys(data as Mastodon.Results) { title: string; data: any }[]
>([])
useEffect(
() =>
data &&
setSectionData(
Object.keys(data as Mastodon.Results)
.map(key => ({ .map(key => ({
title: key, title: key,
// @ts-ignore // @ts-ignore
@ -36,24 +51,23 @@ const ScreenSharedSearch: React.FC = () => {
return 0 return 0
} }
}) })
: [] ),
} [data]
)
const onChangeText = useCallback( const onChangeText = useCallback(
debounce( debounce(text => setSearchTerm(text), 1000, {
text => { trailing: true
setSearchTerm(text) }),
if (text) {
refetch()
}
},
1000,
{
trailing: true
}
),
[] []
) )
useEffect(() => {
if (searchTerm) {
refetch()
} else {
setSectionData([])
}
}, [searchTerm])
const listEmpty = useMemo( const listEmpty = useMemo(
() => ( () => (
@ -93,9 +107,107 @@ const ScreenSharedSearch: React.FC = () => {
), ),
[] []
) )
const sectionHeader = useCallback(
({ section: { title } }) => (
<View
style={[
styles.sectionHeader,
{ borderBottomColor: theme.border, backgroundColor: theme.background }
]}
>
<Text style={[styles.sectionHeaderText, { color: theme.primary }]}>
{title}
</Text>
</View>
),
[]
)
const sectionFooter = useCallback(
({ section: { data, title } }) =>
!data.length ? (
<View
style={[styles.sectionFooter, { backgroundColor: theme.background }]}
>
<Text style={[styles.sectionFooterText, { color: theme.secondary }]}>
{' '}
<Text style={{ fontWeight: StyleConstants.Font.Weight.Bold }}>
{searchTerm}
</Text>{' '}
{title}
</Text>
</View>
) : null,
[searchTerm]
)
const listItem = useCallback(({ item, section }) => {
switch (section.title) {
case 'accounts':
return (
<Pressable
style={[
styles.itemDefault,
styles.itemAccount,
{ borderBottomColor: theme.border }
]}
onPress={() => {
navigation.goBack()
navigation.push('Screen-Shared-Account', {
id: item.id
})
}}
>
<Image
source={{ uri: item.avatar_static }}
style={styles.itemAccountAvatar}
/>
<View>
{item.emojis?.length ? (
<Emojis
content={item.display_name || item.username}
emojis={item.emojis}
size={StyleConstants.Font.Size.S}
fontBold={true}
/>
) : (
<Text
style={[styles.nameWithoutEmoji, { color: theme.primary }]}
>
{item.display_name || item.username}
</Text>
)}
<Text
style={[styles.itemAccountAcct, { color: theme.secondary }]}
>
@{item.acct}
</Text>
</View>
</Pressable>
)
case 'hashtags':
return (
<Pressable
style={[styles.itemDefault, { borderBottomColor: theme.border }]}
onPress={() => {
navigation.goBack()
navigation.push('Screen-Shared-Hashtag', {
hashtag: item.name
})
}}
>
<Text style={[styles.itemHashtag, { color: theme.primary }]}>
#{item.name}
</Text>
</Pressable>
)
case 'statuses':
return <Text>{item.id || 'empty'}</Text>
default:
return null
}
}, [])
return ( return (
<> <SafeAreaView style={{ flex: 1 }} edges={['bottom']}>
<View style={styles.searchBar}> <View style={styles.searchBar}>
<View <View
style={[styles.searchField, { borderBottomColor: theme.secondary }]} style={[styles.searchField, { borderBottomColor: theme.secondary }]}
@ -119,12 +231,7 @@ const ScreenSharedSearch: React.FC = () => {
autoCorrect={false} autoCorrect={false}
clearButtonMode='never' clearButtonMode='never'
keyboardType='web-search' keyboardType='web-search'
// onSubmitEditing={() => onSubmitEditing={({ nativeEvent: { text } }) => setSearchTerm(text)}
// instanceQuery.isSuccess &&
// instanceQuery.data &&
// instanceQuery.data.uri &&
// applicationQuery.refetch()
// }
placeholder={'搜索些什么'} placeholder={'搜索些什么'}
placeholderTextColor={theme.secondary} placeholderTextColor={theme.secondary}
returnKeyType='go' returnKeyType='go'
@ -135,36 +242,26 @@ const ScreenSharedSearch: React.FC = () => {
</View> </View>
</View> </View>
<SectionList <SectionList
ListEmptyComponent={listEmpty}
style={styles.base} style={styles.base}
sections={transformData()} renderItem={listItem}
refreshing={isFetching}
keyExtractor={(item, index) => item + index}
renderSectionHeader={({ section: { title } }) => (
<MenuHeader heading={title} />
)}
renderItem={({ item, section }) => {
switch (section.title) {
case 'accounts':
return <Text>{item.display_name || item.username}</Text>
case 'statuses':
return <Text>{item.id || 'empty'}</Text>
case 'hashtags':
return <MenuRow title={item.name} />
default:
return null
}
}}
stickySectionHeadersEnabled stickySectionHeadersEnabled
sections={setctionData}
ListEmptyComponent={listEmpty}
refreshing={status === 'loading'}
keyboardShouldPersistTaps='always'
renderSectionHeader={sectionHeader}
renderSectionFooter={sectionFooter}
keyExtractor={(item, index) => item + index}
/> />
</> </SafeAreaView>
) )
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
base: { base: {
flex: 1, flex: 1,
padding: StyleConstants.Spacing.Global.PagePadding padding: StyleConstants.Spacing.Global.PagePadding,
paddingTop: 0
}, },
searchBar: { searchBar: {
padding: StyleConstants.Spacing.Global.PagePadding, padding: StyleConstants.Spacing.Global.PagePadding,
@ -193,7 +290,7 @@ const styles = StyleSheet.create({
}, },
emptyBase: { emptyBase: {
marginTop: StyleConstants.Spacing.S, marginTop: StyleConstants.Spacing.M,
marginLeft: marginLeft:
StyleConstants.Spacing.S + StyleConstants.Spacing.S +
StyleConstants.Spacing.M + StyleConstants.Spacing.M +
@ -208,6 +305,44 @@ const styles = StyleSheet.create({
}, },
emptyAdvanced: { emptyAdvanced: {
marginBottom: StyleConstants.Spacing.S marginBottom: StyleConstants.Spacing.S
},
sectionHeader: {
padding: StyleConstants.Spacing.M,
borderBottomWidth: StyleSheet.hairlineWidth
},
sectionHeaderText: {
fontSize: StyleConstants.Font.Size.M,
fontWeight: StyleConstants.Font.Weight.Bold,
textAlign: 'center'
},
sectionFooter: {
padding: StyleConstants.Spacing.S
},
sectionFooterText: {
fontSize: StyleConstants.Font.Size.S,
textAlign: 'center'
},
itemDefault: {
padding: StyleConstants.Spacing.S * 1.5,
borderBottomWidth: StyleSheet.hairlineWidth
},
itemAccount: {
flexDirection: 'row',
alignItems: 'center'
},
itemAccountAvatar: {
width: StyleConstants.Avatar.S,
height: StyleConstants.Avatar.S,
borderRadius: 6,
marginRight: StyleConstants.Spacing.S
},
nameWithoutEmoji: {
fontSize: StyleConstants.Font.Size.S,
fontWeight: StyleConstants.Font.Weight.Bold
},
itemAccountAcct: { marginTop: StyleConstants.Spacing.XS },
itemHashtag: {
fontSize: StyleConstants.Font.Size.M
} }
}) })

View File

@ -77,28 +77,29 @@ export const timelineFetch = async ({
case 'Account_Default': case 'Account_Default':
if (pageParam) { if (pageParam) {
if (pageParam.direction === 'prev') { switch (pageParam.direction) {
res = await client({ case 'prev':
method: 'get', res = await client({
instance: 'local', method: 'get',
url: `accounts/${account}/statuses`, instance: 'local',
params: { url: `accounts/${account}/statuses`,
pinned: 'true', params: {
...params pinned: 'true',
} ...params
}) }
return Promise.resolve({ toots: res.body }) })
} else { return Promise.resolve({ toots: res.body })
res = await client({ case 'next':
method: 'get', res = await client({
instance: 'local', method: 'get',
url: `accounts/${account}/statuses`, instance: 'local',
params: { url: `accounts/${account}/statuses`,
exclude_replies: 'true', params: {
...params exclude_replies: 'true',
} ...params
}) }
return Promise.resolve({ toots: res.body }) })
return Promise.resolve({ toots: res.body })
} }
} else { } else {
res = await client({ res = await client({

View File

@ -25,7 +25,8 @@ export const StyleConstants = {
}, },
Avatar: { Avatar: {
S: 52, S: 36,
M: 52,
L: 104 L: 104
} }
} }