mirror of https://github.com/tooot-app/app
commit
8da6be7b60
|
@ -126,7 +126,7 @@ const renderNode = ({
|
|||
}
|
||||
}}
|
||||
>
|
||||
{(content && content !== href && content) || (showFullLink ? href : domain[1])}
|
||||
{content && content !== href ? content : showFullLink ? href : domain?.[1]}
|
||||
{!shouldBeTag ? (
|
||||
<Icon
|
||||
color={colors.blue}
|
||||
|
|
|
@ -53,10 +53,10 @@ const TimelineDefault: React.FC<Props> = ({
|
|||
const status = item.reblog ? item.reblog : item
|
||||
const ownAccount = status.account?.id === instanceAccount?.id
|
||||
const [spoilerExpanded, setSpoilerExpanded] = useState(
|
||||
instanceAccount.preferences['reading:expand:spoilers'] || false
|
||||
instanceAccount?.preferences['reading:expand:spoilers'] || false
|
||||
)
|
||||
const spoilerHidden = status.spoiler_text?.length
|
||||
? !instanceAccount.preferences['reading:expand:spoilers'] && !spoilerExpanded
|
||||
? !instanceAccount?.preferences['reading:expand:spoilers'] && !spoilerExpanded
|
||||
: false
|
||||
const copiableContent = useRef<{ content: string; complete: boolean }>({
|
||||
content: '',
|
||||
|
|
|
@ -28,9 +28,7 @@ const AttachmentAltText: React.FC<Props> = ({ sensitiveShown, text }) => {
|
|||
type='text'
|
||||
content='ALT'
|
||||
fontBold
|
||||
onPress={() => {
|
||||
navigation.navigate('Screen-Actions', { type: 'alt_text', text })
|
||||
}}
|
||||
onPress={() => navigation.navigate('Screen-Actions', { type: 'alt_text', text })}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import Button from '@components/Button'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { ResizeMode, Video, VideoFullscreenUpdate } from 'expo-av'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { AppState, AppStateStatus, Pressable, View } from 'react-native'
|
||||
import React, { useRef, useState } from 'react'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import { Blurhash } from 'react-native-blurhash'
|
||||
import attachmentAspectRatio from './aspectRatio'
|
||||
import AttachmentAltText from './AltText'
|
||||
|
@ -27,46 +27,19 @@ const AttachmentVideo: React.FC<Props> = ({
|
|||
const [videoLoading, setVideoLoading] = useState(false)
|
||||
const [videoLoaded, setVideoLoaded] = useState(false)
|
||||
const [videoResizeMode, setVideoResizeMode] = useState<ResizeMode>(ResizeMode.COVER)
|
||||
const playOnPress = useCallback(async () => {
|
||||
const playOnPress = async () => {
|
||||
setVideoLoading(true)
|
||||
if (!videoLoaded) {
|
||||
await videoPlayer.current?.loadAsync({ uri: video.url })
|
||||
}
|
||||
setVideoLoading(false)
|
||||
|
||||
Platform.OS === 'android' && setVideoResizeMode(ResizeMode.CONTAIN)
|
||||
await videoPlayer.current?.presentFullscreenPlayer()
|
||||
|
||||
videoPlayer.current?.playAsync()
|
||||
setVideoLoading(false)
|
||||
videoPlayer.current?.setOnPlaybackStatusUpdate(props => {
|
||||
if (props.isLoaded) {
|
||||
setVideoLoaded(true)
|
||||
}
|
||||
})
|
||||
}, [videoLoaded])
|
||||
|
||||
const appState = useRef(AppState.currentState)
|
||||
useEffect(() => {
|
||||
const appState = AppState.addEventListener('change', _handleAppStateChange)
|
||||
|
||||
return () => appState.remove()
|
||||
}, [])
|
||||
const _handleAppStateChange = async (nextAppState: AppStateStatus) => {
|
||||
if (appState.current.match(/active/) && nextAppState.match(/inactive/)) {
|
||||
// await videoPlayer.current?.stopAsync()
|
||||
} else if (gifv && appState.current.match(/background/) && nextAppState.match(/active/)) {
|
||||
await videoPlayer.current?.setIsMutedAsync(true)
|
||||
await videoPlayer.current?.playAsync()
|
||||
}
|
||||
|
||||
appState.current = nextAppState
|
||||
}
|
||||
|
||||
const playerStatus = useRef<any>(null)
|
||||
useEffect(() => {
|
||||
videoPlayer.current?.setOnPlaybackStatusUpdate(playbackStatus => {
|
||||
playerStatus.current = playbackStatus
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
|
@ -79,11 +52,7 @@ const AttachmentVideo: React.FC<Props> = ({
|
|||
<Video
|
||||
accessibilityLabel={video.description}
|
||||
ref={videoPlayer}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
opacity: sensitiveShown ? 0 : 1
|
||||
}}
|
||||
style={{ width: '100%', height: '100%', opacity: sensitiveShown ? 0 : 1 }}
|
||||
usePoster
|
||||
resizeMode={videoResizeMode}
|
||||
{...(gifv
|
||||
|
@ -98,11 +67,22 @@ const AttachmentVideo: React.FC<Props> = ({
|
|||
posterStyle: { resizeMode: ResizeMode.COVER }
|
||||
})}
|
||||
useNativeControls={false}
|
||||
onFullscreenUpdate={async event => {
|
||||
onFullscreenUpdate={event => {
|
||||
if (event.fullscreenUpdate === VideoFullscreenUpdate.PLAYER_DID_DISMISS) {
|
||||
Platform.OS === 'android' && setVideoResizeMode(ResizeMode.COVER)
|
||||
if (!gifv) {
|
||||
await videoPlayer.current?.stopAsync()
|
||||
videoPlayer.current?.pauseAsync()
|
||||
} else {
|
||||
videoPlayer.current?.playAsync()
|
||||
}
|
||||
}
|
||||
}}
|
||||
onPlaybackStatusUpdate={event => {
|
||||
if (event.isLoaded) {
|
||||
!videoLoaded && setVideoLoaded(true)
|
||||
|
||||
if (event.didJustFinish) {
|
||||
videoPlayer.current?.setPositionAsync(0)
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
@ -116,19 +96,14 @@ const AttachmentVideo: React.FC<Props> = ({
|
|||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
disabled={gifv ? (sensitiveShown ? true : false) : true}
|
||||
onPress={gifv ? playOnPress : null}
|
||||
>
|
||||
{sensitiveShown ? (
|
||||
video.blurhash ? (
|
||||
<Blurhash
|
||||
blurhash={video.blurhash}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}}
|
||||
/>
|
||||
<Blurhash blurhash={video.blurhash} style={{ width: '100%', height: '100%' }} />
|
||||
) : null
|
||||
) : !gifv || (gifv && playerStatus.current === false) ? (
|
||||
) : !gifv ? (
|
||||
<Button
|
||||
round
|
||||
overlay
|
||||
|
|
|
@ -20,23 +20,19 @@ const HeaderSharedAccount = React.memo(
|
|||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
{withoutName ? null : (
|
||||
<CustomText
|
||||
accessibilityHint={t(
|
||||
'shared.header.shared.account.name.accessibilityHint'
|
||||
)}
|
||||
accessibilityHint={t('shared.header.shared.account.name.accessibilityHint')}
|
||||
style={{ marginRight: StyleConstants.Spacing.XS }}
|
||||
numberOfLines={1}
|
||||
>
|
||||
<ParseEmojis
|
||||
content={account?.display_name || account?.username}
|
||||
emojis={account.emojis}
|
||||
emojis={account?.emojis}
|
||||
fontBold
|
||||
/>
|
||||
</CustomText>
|
||||
)}
|
||||
<CustomText
|
||||
accessibilityHint={t(
|
||||
'shared.header.shared.account.account.accessibilityHint'
|
||||
)}
|
||||
accessibilityHint={t('shared.header.shared.account.account.accessibilityHint')}
|
||||
style={{ flexShrink: 1, color: colors.secondary }}
|
||||
numberOfLines={1}
|
||||
>
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
}
|
||||
},
|
||||
"at": {
|
||||
"direct": "",
|
||||
"public": ""
|
||||
"direct": "Direktnachricht",
|
||||
"public": "Öffentliche Nachricht"
|
||||
},
|
||||
"copy": {
|
||||
"action": "Tröt kopieren",
|
||||
|
|
|
@ -329,8 +329,8 @@
|
|||
"name": "<0 /><1>的媒體</1>"
|
||||
},
|
||||
"hashtag": {
|
||||
"follow": "追蹤",
|
||||
"unfollow": "取消追蹤"
|
||||
"follow": "跟隨",
|
||||
"unfollow": "取消跟隨"
|
||||
},
|
||||
"history": {
|
||||
"name": "編輯歷史"
|
||||
|
|
|
@ -26,10 +26,10 @@ export const uploadAttachment = async ({
|
|||
}) => {
|
||||
const hash = await Crypto.digestStringAsync(
|
||||
Crypto.CryptoDigestAlgorithm.SHA256,
|
||||
media.uri + Math.random()
|
||||
media.uri + Math.random().toString()
|
||||
)
|
||||
|
||||
switch (media.type.split('/')[0]) {
|
||||
switch (media.type?.split('/')[0] || '') {
|
||||
case 'image':
|
||||
composeDispatch({
|
||||
type: 'attachment/upload/start',
|
||||
|
@ -77,15 +77,11 @@ export const uploadAttachment = async ({
|
|||
payload: hash
|
||||
})
|
||||
Alert.alert(
|
||||
i18next.t(
|
||||
'screenCompose:content.root.actions.attachment.failed.alert.title'
|
||||
),
|
||||
i18next.t('screenCompose:content.root.actions.attachment.failed.alert.title'),
|
||||
message,
|
||||
[
|
||||
{
|
||||
text: i18next.t(
|
||||
'screenCompose:content.root.actions.attachment.failed.alert.button'
|
||||
),
|
||||
text: i18next.t('screenCompose:content.root.actions.attachment.failed.alert.button'),
|
||||
onPress: () => {}
|
||||
}
|
||||
]
|
||||
|
@ -117,9 +113,7 @@ export const uploadAttachment = async ({
|
|||
})
|
||||
.catch((err: any) => {
|
||||
uploadFailed(
|
||||
err?.message && typeof err?.message === 'string'
|
||||
? err?.message.slice(0, 50)
|
||||
: undefined
|
||||
err?.message && typeof err?.message === 'string' ? err?.message.slice(0, 50) : undefined
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -37,25 +37,16 @@ const composePost = async (
|
|||
formData.append('poll[multiple]', composeState.poll.multiple.toString())
|
||||
}
|
||||
|
||||
if (
|
||||
composeState.attachments.uploads.filter(
|
||||
upload => upload.remote && upload.remote.id
|
||||
).length
|
||||
) {
|
||||
if (composeState.attachments.uploads.filter(upload => upload.remote && upload.remote.id).length) {
|
||||
formData.append('sensitive', composeState.attachments.sensitive.toString())
|
||||
composeState.attachments.uploads.forEach(e =>
|
||||
formData.append('media_ids[]', e.remote!.id!)
|
||||
)
|
||||
composeState.attachments.uploads.forEach(e => formData.append('media_ids[]', e.remote!.id!))
|
||||
}
|
||||
|
||||
formData.append('visibility', composeState.visibility)
|
||||
|
||||
return apiInstance<Mastodon.Status>({
|
||||
method: params?.type === 'edit' ? 'put' : 'post',
|
||||
url:
|
||||
params?.type === 'edit'
|
||||
? `statuses/${params.incomingStatus.id}`
|
||||
: 'statuses',
|
||||
url: params?.type === 'edit' ? `statuses/${params.incomingStatus.id}` : 'statuses',
|
||||
headers: {
|
||||
'Idempotency-Key': await Crypto.digestStringAsync(
|
||||
Crypto.CryptoDigestAlgorithm.SHA256,
|
||||
|
@ -70,9 +61,7 @@ const composePost = async (
|
|||
composeState.attachments.sensitive +
|
||||
composeState.attachments.uploads.map(upload => upload.remote?.id) +
|
||||
composeState.visibility +
|
||||
(params?.type === 'edit' || params?.type === 'deleteEdit'
|
||||
? Math.random()
|
||||
: '')
|
||||
(params?.type === 'edit' || params?.type === 'deleteEdit' ? Math.random().toString() : '')
|
||||
)
|
||||
},
|
||||
body: formData
|
||||
|
|
|
@ -86,12 +86,7 @@ const TabLocal = React.memo(
|
|||
accessibilityLabel={t('common.search.accessibilityLabel')}
|
||||
accessibilityHint={t('common.search.accessibilityHint')}
|
||||
content='Search'
|
||||
onPress={() =>
|
||||
navigation.navigate('Tab-Local', {
|
||||
screen: 'Tab-Shared-Search',
|
||||
params: { text: undefined }
|
||||
})
|
||||
}
|
||||
onPress={() => navigation.navigate('Tab-Local', { screen: 'Tab-Shared-Search' })}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
|
|
|
@ -3,10 +3,7 @@ import { useNavigation } from '@react-navigation/native'
|
|||
import { useAppDispatch } from '@root/store'
|
||||
import { useAnnouncementQuery } from '@utils/queryHooks/announcement'
|
||||
import { useListsQuery } from '@utils/queryHooks/lists'
|
||||
import {
|
||||
getInstanceMePage,
|
||||
updateInstanceMePage
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import { getInstanceMePage, updateInstanceMePage } from '@utils/slices/instancesSlice'
|
||||
import { getInstancePush } from '@utils/slices/instancesSlice'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
@ -41,19 +38,17 @@ const Collections: React.FC = () => {
|
|||
}
|
||||
})
|
||||
useEffect(() => {
|
||||
if (announcementsQuery.isSuccess) {
|
||||
if (announcementsQuery.data) {
|
||||
dispatch(
|
||||
updateInstanceMePage({
|
||||
announcements: {
|
||||
shown: announcementsQuery.data?.length ? true : false,
|
||||
unread: announcementsQuery.data?.filter(
|
||||
announcement => !announcement.read
|
||||
).length
|
||||
shown: announcementsQuery.data.length ? true : false,
|
||||
unread: announcementsQuery.data.filter(announcement => !announcement.read).length
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}, [announcementsQuery.isSuccess, announcementsQuery.data?.length])
|
||||
}, [announcementsQuery.data])
|
||||
|
||||
const instancePush = useSelector(
|
||||
getInstancePush,
|
||||
|
@ -100,9 +95,7 @@ const Collections: React.FC = () => {
|
|||
})
|
||||
: t('me.root.announcements.content.read')
|
||||
}
|
||||
onPress={() =>
|
||||
navigation.navigate('Screen-Announcements', { showAll: true })
|
||||
}
|
||||
onPress={() => navigation.navigate('Screen-Announcements', { showAll: true })}
|
||||
/>
|
||||
) : null}
|
||||
<MenuRow
|
||||
|
|
|
@ -50,12 +50,7 @@ const TabPublic = React.memo(
|
|||
accessibilityLabel={t('common.search.accessibilityLabel')}
|
||||
accessibilityHint={t('common.search.accessibilityHint')}
|
||||
content='Search'
|
||||
onPress={() =>
|
||||
navigation.navigate('Tab-Public', {
|
||||
screen: 'Tab-Shared-Search',
|
||||
params: { text: undefined }
|
||||
})
|
||||
}
|
||||
onPress={() => navigation.navigate('Tab-Public', { screen: 'Tab-Shared-Search' })}
|
||||
/>
|
||||
)
|
||||
}),
|
||||
|
|
|
@ -81,7 +81,7 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
|
|||
)
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
}, [mAccount])
|
||||
|
||||
const { data } = useAccountQuery({ id: account.id })
|
||||
|
||||
|
|
|
@ -48,8 +48,8 @@ const TabSharedAccountInLists: React.FC<
|
|||
id: 'out',
|
||||
title: t('shared.accountInLists.notInLists'),
|
||||
data:
|
||||
listsQuery?.data?.filter(
|
||||
({ id }) => !accountInListsQuery?.data?.filter(d => d.id === id)?.length
|
||||
listsQuery.data?.filter(
|
||||
({ id }) => accountInListsQuery.data?.filter(d => d.id !== id).length
|
||||
) || []
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
import ComponentHashtag from '@components/Hashtag'
|
||||
import ComponentSeparator from '@components/Separator'
|
||||
import CustomText from '@components/Text'
|
||||
import { useTrendsQuery } from '@utils/queryHooks/trends'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { StyleSheet, TextInput, View } from 'react-native'
|
||||
import { Circle } from 'react-native-animated-spinkit'
|
||||
|
||||
export interface Props {
|
||||
isLoading: boolean
|
||||
inputRef: React.RefObject<TextInput>
|
||||
setSearchTerm: React.Dispatch<React.SetStateAction<string>>
|
||||
}
|
||||
|
||||
const SearchEmpty: React.FC<Props> = ({ isLoading, inputRef, setSearchTerm }) => {
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('screenTabs')
|
||||
|
||||
const trendsTags = useTrendsQuery({ type: 'tags' })
|
||||
|
||||
return (
|
||||
<View style={{ paddingVertical: StyleConstants.Spacing.Global.PagePadding }}>
|
||||
{isLoading ? (
|
||||
<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={{
|
||||
marginBottom: StyleConstants.Spacing.L,
|
||||
color: colors.primaryDefault
|
||||
}}
|
||||
>
|
||||
<Trans
|
||||
i18nKey='screenTabs:shared.search.empty.general'
|
||||
components={{
|
||||
bold: <CustomText fontWeight='Bold' />
|
||||
}}
|
||||
/>
|
||||
</CustomText>
|
||||
<CustomText
|
||||
style={[styles.emptyAdvanced, { color: colors.primaryDefault }]}
|
||||
fontWeight='Bold'
|
||||
>
|
||||
{t('shared.search.empty.advanced.header')}
|
||||
</CustomText>
|
||||
<CustomText style={[styles.emptyAdvanced, { color: colors.primaryDefault }]}>
|
||||
<CustomText style={{ color: colors.secondary }}>@username@domain</CustomText>
|
||||
{' '}
|
||||
{t('shared.search.empty.advanced.example.account')}
|
||||
</CustomText>
|
||||
<CustomText style={[styles.emptyAdvanced, { color: colors.primaryDefault }]}>
|
||||
<CustomText style={{ color: colors.secondary }}>#example</CustomText>
|
||||
{' '}
|
||||
{t('shared.search.empty.advanced.example.hashtag')}
|
||||
</CustomText>
|
||||
<CustomText style={[styles.emptyAdvanced, { color: colors.primaryDefault }]}>
|
||||
<CustomText style={{ color: colors.secondary }}>URL</CustomText>
|
||||
{' '}
|
||||
{t('shared.search.empty.advanced.example.statusLink')}
|
||||
</CustomText>
|
||||
<CustomText style={[styles.emptyAdvanced, { color: colors.primaryDefault }]}>
|
||||
<CustomText style={{ color: colors.secondary }}>URL</CustomText>
|
||||
{' '}
|
||||
{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={() => {
|
||||
inputRef.current?.setNativeProps({ text: `#${hashtag.name}` })
|
||||
setSearchTerm(`#${hashtag.name}`)
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
emptyAdvanced: {
|
||||
marginBottom: StyleConstants.Spacing.S
|
||||
}
|
||||
})
|
||||
|
||||
export default SearchEmpty
|
|
@ -6,31 +6,22 @@ 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, { useEffect } from 'react'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
SectionList,
|
||||
StyleSheet,
|
||||
TextInput,
|
||||
View
|
||||
} from 'react-native'
|
||||
import { Circle } from 'react-native-animated-spinkit'
|
||||
import { KeyboardAvoidingView, Platform, SectionList, TextInput, View } from 'react-native'
|
||||
import SearchEmpty from './Empty'
|
||||
|
||||
const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>> = ({
|
||||
navigation,
|
||||
route: {
|
||||
params: { text }
|
||||
}
|
||||
navigation
|
||||
}) => {
|
||||
const { t } = useTranslation('screenTabs')
|
||||
const { colors, mode } = useTheme()
|
||||
|
||||
const inputRef = useRef<TextInput>(null)
|
||||
const [searchTerm, setSearchTerm] = useState<string>('')
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
...(Platform.OS === 'ios'
|
||||
|
@ -56,6 +47,7 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
|
|||
defaultValue={t('shared.search.header.prefix')}
|
||||
/>
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
accessibilityRole='search'
|
||||
keyboardAppearance={mode}
|
||||
style={{
|
||||
|
@ -65,15 +57,18 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
|
|||
paddingLeft: StyleConstants.Spacing.XS
|
||||
}}
|
||||
autoFocus
|
||||
value={text}
|
||||
onChangeText={debounce((text: string) => navigation.setParams({ text }), 1000, {
|
||||
trailing: true
|
||||
})}
|
||||
onChangeText={debounce(
|
||||
text => {
|
||||
setSearchTerm(text)
|
||||
refetch()
|
||||
},
|
||||
1000,
|
||||
{ trailing: true }
|
||||
)}
|
||||
autoCapitalize='none'
|
||||
autoCorrect={false}
|
||||
clearButtonMode='always'
|
||||
keyboardType='web-search'
|
||||
onSubmitEditing={({ nativeEvent: { text } }) => navigation.setParams({ text })}
|
||||
placeholder={t('shared.search.header.placeholder')}
|
||||
placeholderTextColor={colors.secondary}
|
||||
returnKeyType='search'
|
||||
|
@ -82,25 +77,23 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
|
|||
)
|
||||
}
|
||||
})
|
||||
}, [text, mode])
|
||||
|
||||
const trendsTags = useTrendsQuery({ type: 'tags' })
|
||||
}, [mode])
|
||||
|
||||
const mapKeyToTranslations = {
|
||||
accounts: t('shared.search.sections.accounts'),
|
||||
hashtags: t('shared.search.sections.hashtags'),
|
||||
statuses: t('shared.search.sections.statuses')
|
||||
}
|
||||
const { status, data } = useSearchQuery<
|
||||
const { isLoading, data, refetch } = useSearchQuery<
|
||||
{
|
||||
title: string
|
||||
translation: string
|
||||
data: any[]
|
||||
}[]
|
||||
>({
|
||||
term: text,
|
||||
term: searchTerm,
|
||||
options: {
|
||||
enabled: text !== undefined,
|
||||
enabled: !!searchTerm.length,
|
||||
select: data =>
|
||||
Object.keys(data as Mastodon.Results)
|
||||
.map(key => ({
|
||||
|
@ -110,6 +103,7 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
|
|||
// @ts-ignore
|
||||
data: data[key]
|
||||
}))
|
||||
.filter(d => d.data.length)
|
||||
.sort((a, b) => {
|
||||
if (!a.data.length) {
|
||||
return 1
|
||||
|
@ -122,88 +116,6 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
|
|||
}
|
||||
})
|
||||
|
||||
const listEmpty = () => {
|
||||
return (
|
||||
<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={{
|
||||
marginBottom: StyleConstants.Spacing.L,
|
||||
color: colors.primaryDefault
|
||||
}}
|
||||
>
|
||||
<Trans
|
||||
i18nKey='screenTabs:shared.search.empty.general'
|
||||
components={{
|
||||
bold: <CustomText fontWeight='Bold' />
|
||||
}}
|
||||
/>
|
||||
</CustomText>
|
||||
<CustomText
|
||||
style={[styles.emptyAdvanced, { color: colors.primaryDefault }]}
|
||||
fontWeight='Bold'
|
||||
>
|
||||
{t('shared.search.empty.advanced.header')}
|
||||
</CustomText>
|
||||
<CustomText style={[styles.emptyAdvanced, { color: colors.primaryDefault }]}>
|
||||
<CustomText style={{ color: colors.secondary }}>@username@domain</CustomText>
|
||||
{' '}
|
||||
{t('shared.search.empty.advanced.example.account')}
|
||||
</CustomText>
|
||||
<CustomText style={[styles.emptyAdvanced, { color: colors.primaryDefault }]}>
|
||||
<CustomText style={{ color: colors.secondary }}>#example</CustomText>
|
||||
{' '}
|
||||
{t('shared.search.empty.advanced.example.hashtag')}
|
||||
</CustomText>
|
||||
<CustomText style={[styles.emptyAdvanced, { color: colors.primaryDefault }]}>
|
||||
<CustomText style={{ color: colors.secondary }}>URL</CustomText>
|
||||
{' '}
|
||||
{t('shared.search.empty.advanced.example.statusLink')}
|
||||
</CustomText>
|
||||
<CustomText style={[styles.emptyAdvanced, { color: colors.primaryDefault }]}>
|
||||
<CustomText style={{ color: colors.secondary }}>URL</CustomText>
|
||||
{' '}
|
||||
{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>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
|
@ -211,6 +123,7 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
|
|||
>
|
||||
<SectionList
|
||||
style={{ minHeight: '100%' }}
|
||||
sections={data || []}
|
||||
renderItem={({ item, section }: { item: any; section: any }) => {
|
||||
switch (section.title) {
|
||||
case 'accounts':
|
||||
|
@ -224,8 +137,9 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
|
|||
}
|
||||
}}
|
||||
stickySectionHeadersEnabled
|
||||
sections={data || []}
|
||||
ListEmptyComponent={listEmpty()}
|
||||
ListEmptyComponent={
|
||||
<SearchEmpty isLoading={isLoading} inputRef={inputRef} setSearchTerm={setSearchTerm} />
|
||||
}
|
||||
keyboardShouldPersistTaps='always'
|
||||
renderSectionHeader={({ section: { translation } }) => (
|
||||
<View
|
||||
|
@ -257,7 +171,7 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
|
|||
<CustomText fontStyle='S' style={{ textAlign: 'center', color: colors.secondary }}>
|
||||
<Trans
|
||||
i18nKey='screenTabs:shared.search.notFound'
|
||||
values={{ searchTerm: text, type: translation }}
|
||||
values={{ searchTerm, type: translation }}
|
||||
components={{
|
||||
bold: <CustomText fontWeight='Bold' />
|
||||
}}
|
||||
|
@ -274,10 +188,4 @@ const TabSharedSearch: React.FC<TabSharedStackScreenProps<'Tab-Shared-Search'>>
|
|||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
emptyAdvanced: {
|
||||
marginBottom: StyleConstants.Spacing.S
|
||||
}
|
||||
})
|
||||
|
||||
export default TabSharedSearch
|
|
@ -51,6 +51,8 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
|
|||
scrolled.current = true
|
||||
const pointer = flattenData.findIndex(({ id }) => id === toot.id)
|
||||
if (pointer < 1) return
|
||||
const length = flRef.current?.props.data?.length
|
||||
if (!length) return
|
||||
try {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
|
|
|
@ -4,7 +4,7 @@ import log from './log'
|
|||
const audio = () => {
|
||||
log('log', 'audio', 'setting audio playback default options')
|
||||
Audio.setAudioModeAsync({
|
||||
interruptionModeIOS: InterruptionModeIOS.MixWithOthers,
|
||||
interruptionModeIOS: InterruptionModeIOS.DuckOthers,
|
||||
interruptionModeAndroid: InterruptionModeAndroid.DuckOthers,
|
||||
playsInSilentModeIOS: true,
|
||||
staysActiveInBackground: false
|
||||
|
|
|
@ -104,7 +104,7 @@ export type TabSharedStackParamList = {
|
|||
'Tab-Shared-History': {
|
||||
id: Mastodon.Status['id']
|
||||
}
|
||||
'Tab-Shared-Search': { text: string | undefined }
|
||||
'Tab-Shared-Search': undefined
|
||||
'Tab-Shared-Toot': {
|
||||
toot: Mastodon.Status
|
||||
rootQueryKey?: QueryKeyTimeline
|
||||
|
|
|
@ -27,7 +27,9 @@ const useAccountQuery = ({
|
|||
|
||||
export type QueryKeyAccountInLists = ['AccountInLists', { id: Mastodon.Account['id'] }]
|
||||
|
||||
const accountInListsQueryFunction = ({ queryKey }: QueryFunctionContext<QueryKeyAccount>) => {
|
||||
const accountInListsQueryFunction = ({
|
||||
queryKey
|
||||
}: QueryFunctionContext<QueryKeyAccountInLists>) => {
|
||||
const { id } = queryKey[1]
|
||||
|
||||
return apiInstance<Mastodon.List[]>({
|
||||
|
@ -42,7 +44,7 @@ const useAccountInListsQuery = ({
|
|||
}: QueryKeyAccount[1] & {
|
||||
options?: UseQueryOptions<Mastodon.List[], AxiosError>
|
||||
}) => {
|
||||
const queryKey: QueryKeyAccount = ['Account', { ...queryKeyParams }]
|
||||
const queryKey: QueryKeyAccountInLists = ['AccountInLists', { ...queryKeyParams }]
|
||||
return useQuery(queryKey, accountInListsQueryFunction, options)
|
||||
}
|
||||
|
||||
|
|
|
@ -287,7 +287,7 @@ const instancesSlice = createSlice({
|
|||
const activeIndex = findInstanceActive(state.instances)
|
||||
state.instances[activeIndex].frequentEmojis = state.instances[
|
||||
activeIndex
|
||||
].frequentEmojis?.filter(emoji => {
|
||||
]?.frequentEmojis?.filter(emoji => {
|
||||
return action.payload?.find(
|
||||
e => e.shortcode === emoji.emoji.shortcode && e.url === emoji.emoji.url
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue