mirror of https://github.com/tooot-app/app
Add trending tags in search landing page
This commit is contained in:
parent
8a7e78485d
commit
dab09369cb
|
@ -7,30 +7,23 @@ const handleError = (error: any) => {
|
|||
// The request was made and the server responded with a status code
|
||||
// that falls out of the range of 2xx
|
||||
console.error(
|
||||
ctx.bold(' API instance '),
|
||||
ctx.bold(' API '),
|
||||
ctx.bold('response'),
|
||||
error.response.status,
|
||||
error?.response.data?.error || error?.response.message || 'Unknown error'
|
||||
)
|
||||
return Promise.reject({
|
||||
status: error?.response.status,
|
||||
message:
|
||||
error?.response.data?.error ||
|
||||
error?.response.message ||
|
||||
'Unknown error'
|
||||
message: error?.response.data?.error || error?.response.message || 'Unknown error'
|
||||
})
|
||||
} else if (error?.request) {
|
||||
// The request was made but no response was received
|
||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||
// http.ClientRequest in node.js
|
||||
console.error(ctx.bold(' API instance '), ctx.bold('request'), error)
|
||||
console.error(ctx.bold(' API '), ctx.bold('request'), error)
|
||||
return Promise.reject()
|
||||
} else {
|
||||
console.error(
|
||||
ctx.bold(' API instance '),
|
||||
ctx.bold('internal'),
|
||||
error?.message
|
||||
)
|
||||
console.error(ctx.bold(' API '), ctx.bold('internal'), error?.message)
|
||||
return Promise.reject()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,10 +13,7 @@ export type Params = {
|
|||
}
|
||||
headers?: { [key: string]: string }
|
||||
body?: FormData
|
||||
extras?: Omit<
|
||||
AxiosRequestConfig,
|
||||
'method' | 'url' | 'params' | 'headers' | 'data'
|
||||
>
|
||||
extras?: Omit<AxiosRequestConfig, 'method' | 'url' | 'params' | 'headers' | 'data'>
|
||||
}
|
||||
|
||||
export type InstanceResponse<T = unknown> = {
|
||||
|
@ -35,9 +32,7 @@ const apiInstance = async <T = unknown>({
|
|||
}: Params): Promise<InstanceResponse<T>> => {
|
||||
const { store } = require('@root/store')
|
||||
const state = store.getState() as RootState
|
||||
const instanceActive = state.instances.instances.findIndex(
|
||||
instance => instance.active
|
||||
)
|
||||
const instanceActive = state.instances.instances.findIndex(instance => instance.active)
|
||||
|
||||
let domain
|
||||
let token
|
||||
|
@ -45,9 +40,7 @@ const apiInstance = async <T = unknown>({
|
|||
domain = state.instances.instances[instanceActive].url
|
||||
token = state.instances.instances[instanceActive].token
|
||||
} else {
|
||||
console.warn(
|
||||
ctx.bgRed.white.bold(' API ') + ' ' + 'No instance domain is provided'
|
||||
)
|
||||
console.warn(ctx.bgRed.white.bold(' API ') + ' ' + 'No instance domain is provided')
|
||||
return Promise.reject()
|
||||
}
|
||||
|
||||
|
@ -70,10 +63,7 @@ const apiInstance = async <T = unknown>({
|
|||
url,
|
||||
params,
|
||||
headers: {
|
||||
'Content-Type':
|
||||
body && body instanceof FormData
|
||||
? 'multipart/form-data'
|
||||
: 'application/json',
|
||||
'Content-Type': body && body instanceof FormData ? 'multipart/form-data' : 'application/json',
|
||||
Accept: '*/*',
|
||||
...userAgent,
|
||||
...headers,
|
||||
|
@ -87,10 +77,10 @@ const apiInstance = async <T = unknown>({
|
|||
.then(response => {
|
||||
let prev
|
||||
let next
|
||||
if (response.headers.link) {
|
||||
const headersLinks = li.parse(response.headers.link)
|
||||
prev = headersLinks.prev?.match(/_id=([0-9]*)/)[1]
|
||||
next = headersLinks.next?.match(/_id=([0-9]*)/)[1]
|
||||
if (response.headers?.link) {
|
||||
const headersLinks = li.parse(response.headers?.link)
|
||||
prev = headersLinks.prev?.match(/_id=([0-9]*)/)?.[1]
|
||||
next = headersLinks.next?.match(/_id=([0-9]*)/)?.[1]
|
||||
}
|
||||
return Promise.resolve({
|
||||
body: response.data,
|
||||
|
|
|
@ -3,9 +3,8 @@ import { StackNavigationProp } from '@react-navigation/stack'
|
|||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { sumBy } from 'lodash'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { Dimensions, Pressable, View } from 'react-native'
|
||||
import { Dimensions, Pressable } from 'react-native'
|
||||
import Sparkline from './Sparkline'
|
||||
import CustomText from './Text'
|
||||
|
||||
|
@ -22,7 +21,7 @@ const ComponentHashtag: React.FC<Props> = ({ hashtag, onPress: customOnPress })
|
|||
navigation.push('Tab-Shared-Hashtag', { hashtag: hashtag.name })
|
||||
}, [])
|
||||
|
||||
const padding = StyleConstants.Spacing.S * 1.5
|
||||
const padding = StyleConstants.Spacing.Global.PagePadding
|
||||
const width = Dimensions.get('window').width / 4
|
||||
const [height, setHeight] = useState<number>(0)
|
||||
|
||||
|
|
|
@ -24,6 +24,11 @@
|
|||
"version": 3.5,
|
||||
"reference": "https://github.com/mastodon/mastodon/releases/tag/v3.5.0"
|
||||
},
|
||||
{
|
||||
"feature": "trends_new_path",
|
||||
"version": 3.5,
|
||||
"reference": "https://github.com/mastodon/mastodon/releases/tag/v3.5.0"
|
||||
},
|
||||
{
|
||||
"feature": "follow_tags",
|
||||
"version": 4.0,
|
||||
|
|
|
@ -350,6 +350,9 @@
|
|||
"statusLink": "$t(shared.search.header.prefix) $t(shared.search.sections.statuses)",
|
||||
"accountLink": "$t(shared.search.header.prefix) $t(shared.search.sections.accounts)"
|
||||
}
|
||||
},
|
||||
"trending": {
|
||||
"tags": "Trending tags"
|
||||
}
|
||||
},
|
||||
"sections": {
|
||||
|
|
|
@ -6,10 +6,11 @@ import CustomText from '@components/Text'
|
|||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { useSearchQuery } from '@utils/queryHooks/search'
|
||||
import { useTrendsQuery } from '@utils/queryHooks/trends'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { debounce } from 'lodash'
|
||||
import React, { useCallback, useEffect, useMemo } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
|
@ -38,9 +39,6 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
|
|||
}
|
||||
: { headerLeft: () => null }),
|
||||
headerTitle: () => {
|
||||
const onChangeText = debounce((text: string) => navigation.setParams({ text }), 1000, {
|
||||
trailing: true
|
||||
})
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
|
@ -67,7 +65,10 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
|
|||
paddingLeft: StyleConstants.Spacing.XS
|
||||
}}
|
||||
autoFocus
|
||||
onChangeText={onChangeText}
|
||||
value={text}
|
||||
onChangeText={debounce((text: string) => navigation.setParams({ text }), 1000, {
|
||||
trailing: true
|
||||
})}
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
clearButtonMode='never'
|
||||
|
@ -81,7 +82,9 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
|
|||
)
|
||||
}
|
||||
})
|
||||
}, [mode])
|
||||
}, [text, mode])
|
||||
|
||||
const trendsTags = useTrendsQuery({ type: 'tags' })
|
||||
|
||||
const mapKeyToTranslations = {
|
||||
accounts: t('shared.search.sections.accounts'),
|
||||
|
@ -119,21 +122,16 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
|
|||
}
|
||||
})
|
||||
|
||||
const listEmpty = useMemo(() => {
|
||||
const listEmpty = () => {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
marginVertical: StyleConstants.Spacing.Global.PagePadding,
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<View>
|
||||
<View style={{ paddingVertical: StyleConstants.Spacing.Global.PagePadding }}>
|
||||
{status === 'loading' ? (
|
||||
<View style={{ flex: 1, alignItems: 'center' }}>
|
||||
<Circle size={StyleConstants.Font.Size.M * 1.25} color={colors.secondary} />
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<View style={{ paddingHorizontal: StyleConstants.Spacing.Global.PagePadding }}>
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
style={{
|
||||
|
@ -148,7 +146,10 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
|
|||
}}
|
||||
/>
|
||||
</CustomText>
|
||||
<CustomText style={[styles.emptyAdvanced, { color: colors.primaryDefault }]}>
|
||||
<CustomText
|
||||
style={[styles.emptyAdvanced, { color: colors.primaryDefault }]}
|
||||
fontWeight='Bold'
|
||||
>
|
||||
{t('shared.search.empty.advanced.header')}
|
||||
</CustomText>
|
||||
<CustomText style={[styles.emptyAdvanced, { color: colors.primaryDefault }]}>
|
||||
|
@ -171,14 +172,46 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
|
|||
{' '}
|
||||
{t('shared.search.empty.advanced.example.accountLink')}
|
||||
</CustomText>
|
||||
</View>
|
||||
|
||||
<CustomText
|
||||
style={{
|
||||
color: colors.primaryDefault,
|
||||
marginTop: StyleConstants.Spacing.M,
|
||||
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding
|
||||
}}
|
||||
fontWeight='Bold'
|
||||
>
|
||||
{t('shared.search.empty.trending.tags')}
|
||||
</CustomText>
|
||||
<View>
|
||||
{trendsTags.data?.map((tag, index) => {
|
||||
const hashtag = tag as Mastodon.Tag
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{index !== 0 ? <ComponentSeparator /> : null}
|
||||
<ComponentHashtag
|
||||
hashtag={hashtag}
|
||||
onPress={() => navigation.setParams({ text: `#${hashtag.name}` })}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}, [status])
|
||||
}
|
||||
|
||||
const listItem = useCallback(({ item, section }: { item: any; section: any }) => {
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<SectionList
|
||||
style={{ minHeight: '100%' }}
|
||||
renderItem={({ item, section }: { item: any; section: any }) => {
|
||||
switch (section.title) {
|
||||
case 'accounts':
|
||||
return <ComponentAccount account={item} />
|
||||
|
@ -189,19 +222,10 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
|
|||
default:
|
||||
return null
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
style={{ flex: 1 }}
|
||||
>
|
||||
<SectionList
|
||||
style={{ minHeight: '100%' }}
|
||||
renderItem={listItem}
|
||||
}}
|
||||
stickySectionHeadersEnabled
|
||||
sections={data || []}
|
||||
ListEmptyComponent={listEmpty}
|
||||
ListEmptyComponent={listEmpty()}
|
||||
keyboardShouldPersistTaps='always'
|
||||
renderSectionHeader={({ section: { translation } }) => (
|
||||
<View
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
import apiInstance from '@api/instance'
|
||||
import { store } from '@root/store'
|
||||
import { checkInstanceFeature } from '@utils/slices/instancesSlice'
|
||||
import { AxiosError } from 'axios'
|
||||
import { QueryFunctionContext, useQuery, UseQueryOptions } from 'react-query'
|
||||
|
||||
export type QueryKeyTrends = ['Trends', { type: 'tags' | 'statuses' | 'links' }]
|
||||
|
||||
const queryFunction = ({ queryKey }: QueryFunctionContext<QueryKeyTrends>) => {
|
||||
const trendsNewPath = checkInstanceFeature('trends_new_path')(store.getState())
|
||||
|
||||
if (!trendsNewPath && queryKey[1].type !== 'tags') {
|
||||
return []
|
||||
}
|
||||
|
||||
const { type } = queryKey[1]
|
||||
|
||||
switch (type) {
|
||||
case 'tags':
|
||||
return apiInstance<Mastodon.Tag[]>({
|
||||
method: 'get',
|
||||
url: trendsNewPath ? 'trends/tags' : 'trends'
|
||||
}).then(res => res.body)
|
||||
case 'statuses':
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
url: 'trends/tags'
|
||||
}).then(res => res.body)
|
||||
}
|
||||
}
|
||||
|
||||
const useTrendsQuery = ({
|
||||
options,
|
||||
...queryKeyParams
|
||||
}: QueryKeyTrends[1] & {
|
||||
options?: UseQueryOptions<Mastodon.Tag[] | Mastodon.Status[], AxiosError>
|
||||
}) => {
|
||||
const queryKey: QueryKeyTrends = ['Trends', { ...queryKeyParams }]
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
}
|
||||
|
||||
export { useTrendsQuery }
|
Loading…
Reference in New Issue