mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Fix bugs
This commit is contained in:
@ -10,7 +10,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as Linking from 'expo-linking'
|
||||
import { debounce } from 'lodash'
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert, Image, StyleSheet, Text, TextInput, View } from 'react-native'
|
||||
import { useQueryClient } from 'react-query'
|
||||
@ -42,6 +42,7 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
|
||||
const instanceQuery = useInstanceQuery({
|
||||
instanceDomain,
|
||||
checkPublic: type === 'remote',
|
||||
options: { enabled: false, retry: false }
|
||||
})
|
||||
const appsQuery = useAppsQuery({
|
||||
@ -170,7 +171,12 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
styles.textInput,
|
||||
{
|
||||
color: theme.primary,
|
||||
borderBottomColor: theme.border
|
||||
borderBottomColor:
|
||||
type === 'remote' &&
|
||||
instanceQuery.data &&
|
||||
!instanceQuery.data.publicAllow
|
||||
? theme.red
|
||||
: theme.border
|
||||
}
|
||||
]}
|
||||
onChangeText={onChangeText}
|
||||
@ -188,10 +194,20 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
type='text'
|
||||
content={buttonContent}
|
||||
onPress={processUpdate}
|
||||
disabled={!instanceQuery.data?.uri}
|
||||
disabled={
|
||||
!instanceQuery.data?.uri ||
|
||||
(type === 'remote' && !instanceQuery.data.publicAllow)
|
||||
}
|
||||
loading={instanceQuery.isFetching || appsQuery.isFetching}
|
||||
/>
|
||||
</View>
|
||||
{type === 'remote' &&
|
||||
instanceQuery.data &&
|
||||
!instanceQuery.data.publicAllow ? (
|
||||
<Text style={[styles.privateInstance, { color: theme.red }]}>
|
||||
{t('server.privateInstance')}
|
||||
</Text>
|
||||
) : null}
|
||||
<View>
|
||||
<InstanceInfo
|
||||
visible={instanceQuery.data?.title !== undefined}
|
||||
@ -278,6 +294,12 @@ const styles = StyleSheet.create({
|
||||
...StyleConstants.FontStyle.M,
|
||||
marginRight: StyleConstants.Spacing.M
|
||||
},
|
||||
privateInstance: {
|
||||
...StyleConstants.FontStyle.S,
|
||||
fontWeight: StyleConstants.Font.Weight.Bold,
|
||||
marginLeft: StyleConstants.Spacing.Global.PagePadding,
|
||||
marginTop: StyleConstants.Spacing.XS
|
||||
},
|
||||
instanceStats: {
|
||||
flex: 1,
|
||||
flexDirection: 'row'
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { StyleSheet, Text } from 'react-native'
|
||||
import React, { useMemo } from 'react'
|
||||
import { StyleSheet, Text, View } from 'react-native'
|
||||
import { Image } from 'react-native-expo-image-cache'
|
||||
|
||||
const regexEmoji = new RegExp(/(:[A-Za-z0-9_]+:)/)
|
||||
@ -19,18 +19,26 @@ const ParseEmojis: React.FC<Props> = ({
|
||||
size = 'M',
|
||||
fontBold = false
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const styles = StyleSheet.create({
|
||||
text: {
|
||||
color: theme.primary,
|
||||
...StyleConstants.FontStyle[size],
|
||||
...(fontBold && { fontWeight: StyleConstants.Font.Weight.Bold })
|
||||
},
|
||||
image: {
|
||||
width: StyleConstants.Font.Size[size],
|
||||
height: StyleConstants.Font.Size[size]
|
||||
}
|
||||
})
|
||||
const { mode, theme } = useTheme()
|
||||
const styles = useMemo(() => {
|
||||
return StyleSheet.create({
|
||||
text: {
|
||||
color: theme.primary,
|
||||
...StyleConstants.FontStyle[size],
|
||||
...(fontBold && { fontWeight: StyleConstants.Font.Weight.Bold })
|
||||
},
|
||||
imageContainer: {
|
||||
paddingVertical:
|
||||
(StyleConstants.Font.LineHeight[size] -
|
||||
StyleConstants.Font.Size[size]) /
|
||||
3
|
||||
},
|
||||
image: {
|
||||
width: StyleConstants.Font.Size[size],
|
||||
height: StyleConstants.Font.Size[size]
|
||||
}
|
||||
})
|
||||
}, [mode])
|
||||
|
||||
return (
|
||||
<Text style={styles.text}>
|
||||
@ -50,7 +58,13 @@ const ParseEmojis: React.FC<Props> = ({
|
||||
<Text key={i}>
|
||||
{/* When emoji starts a paragraph, lineHeight will break */}
|
||||
{i === 0 ? <Text> </Text> : null}
|
||||
<Image uri={emojis[emojiIndex].url} style={[styles.image]} />
|
||||
<View style={styles.imageContainer}>
|
||||
<Image
|
||||
transitionDuration={0}
|
||||
uri={emojis[emojiIndex].url}
|
||||
style={[styles.image]}
|
||||
/>
|
||||
</View>
|
||||
</Text>
|
||||
)
|
||||
} else {
|
||||
|
@ -154,12 +154,16 @@ const ParseHTML: React.FC<Props> = ({
|
||||
tags,
|
||||
showFullLink = false,
|
||||
numberOfLines = 10,
|
||||
expandHint = '全文',
|
||||
expandHint,
|
||||
disableDetails = false
|
||||
}) => {
|
||||
const navigation = useNavigation()
|
||||
const route = useRoute()
|
||||
const { theme } = useTheme()
|
||||
const { t, i18n } = useTranslation('componentParse')
|
||||
if (!expandHint) {
|
||||
expandHint = t('HTML.defaultHint')
|
||||
}
|
||||
|
||||
const renderNodeCallback = useCallback(
|
||||
(node, index) =>
|
||||
@ -261,7 +265,7 @@ const ParseHTML: React.FC<Props> = ({
|
||||
</View>
|
||||
)
|
||||
},
|
||||
[theme]
|
||||
[theme, i18n.language]
|
||||
)
|
||||
|
||||
return (
|
||||
|
@ -6,7 +6,8 @@ import sharedScreens from '@screens/Shared/sharedScreens'
|
||||
import { getLocalActiveIndex, getRemoteUrl } from '@utils/slices/instancesSlice'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { Dimensions, Platform, StyleSheet, View } from 'react-native'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Dimensions, Platform, StyleSheet } from 'react-native'
|
||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
import { TabView } from 'react-native-tab-view'
|
||||
import ViewPagerAdapter from 'react-native-tab-view-viewpager-adapter'
|
||||
@ -18,22 +19,32 @@ const Stack = createNativeStackNavigator<
|
||||
|
||||
export interface Props {
|
||||
name: 'Local' | 'Public'
|
||||
content: { title: string; page: App.Pages; remote?: boolean }[]
|
||||
}
|
||||
|
||||
const Timelines: React.FC<Props> = ({ name, content }) => {
|
||||
const Timelines: React.FC<Props> = ({ name }) => {
|
||||
const { t, i18n } = useTranslation()
|
||||
const remoteUrl = useSelector(getRemoteUrl)
|
||||
const mapNameToContent: {
|
||||
[key: string]: { title: string; page: App.Pages }[]
|
||||
} = {
|
||||
Local: [
|
||||
{ title: t('local:heading.segments.left'), page: 'Following' },
|
||||
{ title: t('local:heading.segments.right'), page: 'Local' }
|
||||
],
|
||||
Public: [
|
||||
{ title: t('public:heading.segments.left'), page: 'LocalPublic' },
|
||||
{ title: remoteUrl, page: 'RemotePublic' }
|
||||
]
|
||||
}
|
||||
|
||||
const navigation = useNavigation()
|
||||
const { mode } = useTheme()
|
||||
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||
const publicDomain = useSelector(getRemoteUrl)
|
||||
const [segment, setSegment] = useState(0)
|
||||
|
||||
const onPressSearch = useCallback(() => {
|
||||
navigation.navigate(`Screen-${name}`, { screen: 'Screen-Shared-Search' })
|
||||
}, [])
|
||||
|
||||
const routes = content
|
||||
const routes = mapNameToContent[name]
|
||||
.filter(p => (localActiveIndex !== null ? true : p.page === 'RemotePublic'))
|
||||
.map(p => ({ key: p.page }))
|
||||
|
||||
@ -54,39 +65,37 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
|
||||
[localActiveIndex]
|
||||
)
|
||||
|
||||
const { mode } = useTheme()
|
||||
const [segment, setSegment] = useState(0)
|
||||
const screenOptions = useMemo(() => {
|
||||
if (localActiveIndex === null) {
|
||||
if (name === 'Public') {
|
||||
return {
|
||||
headerTitle: publicDomain,
|
||||
headerTitle: remoteUrl,
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => <HeaderCenter content={publicDomain} />
|
||||
headerCenter: () => <HeaderCenter content={remoteUrl} />
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
headerCenter: () => (
|
||||
<View style={styles.segmentsContainer}>
|
||||
<SegmentedControl
|
||||
appearance={mode}
|
||||
values={[
|
||||
content[0].title,
|
||||
content[1].remote ? remoteUrl : content[1].title
|
||||
]}
|
||||
selectedIndex={segment}
|
||||
onChange={({ nativeEvent }) =>
|
||||
setSegment(nativeEvent.selectedSegmentIndex)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<SegmentedControl
|
||||
appearance={mode}
|
||||
values={mapNameToContent[name].map(p => p.title)}
|
||||
selectedIndex={segment}
|
||||
onChange={({ nativeEvent }) =>
|
||||
setSegment(nativeEvent.selectedSegmentIndex)
|
||||
}
|
||||
style={styles.segmentsContainer}
|
||||
/>
|
||||
),
|
||||
headerRight: () => (
|
||||
<HeaderRight content='Search' onPress={onPressSearch} />
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [localActiveIndex, mode, segment])
|
||||
}, [localActiveIndex, mode, segment, i18n.language])
|
||||
|
||||
const renderPager = useCallback(props => <ViewPagerAdapter {...props} />, [])
|
||||
|
||||
|
@ -5,7 +5,7 @@ import TimelineEmpty from '@components/Timelines/Timeline/Empty'
|
||||
import TimelineEnd from '@root/components/Timelines/Timeline/End'
|
||||
import TimelineHeader from '@components/Timelines/Timeline/Header'
|
||||
import TimelineNotifications from '@components/Timelines/Timeline/Notifications'
|
||||
import { useScrollToTop } from '@react-navigation/native'
|
||||
import { useNavigation, useScrollToTop } from '@react-navigation/native'
|
||||
import { localUpdateNotification } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
@ -19,7 +19,6 @@ import { FlatList } from 'react-native-gesture-handler'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import { findIndex } from 'lodash'
|
||||
import { InfiniteData, useQueryClient } from 'react-query'
|
||||
import { getPublicRemoteNotice } from '@utils/slices/contextsSlice'
|
||||
|
||||
export interface Props {
|
||||
@ -58,25 +57,12 @@ const Timeline: React.FC<Props> = ({
|
||||
isSuccess,
|
||||
isFetching,
|
||||
isLoading,
|
||||
hasPreviousPage,
|
||||
fetchPreviousPage,
|
||||
isFetchingPreviousPage,
|
||||
hasNextPage,
|
||||
fetchNextPage,
|
||||
isFetchingNextPage
|
||||
} = useTimelineQuery({
|
||||
...queryKeyParams,
|
||||
options: {
|
||||
getPreviousPageParam: firstPage => {
|
||||
return Array.isArray(firstPage) && firstPage.length
|
||||
? {
|
||||
direction: 'prev',
|
||||
id: firstPage[0].last_status
|
||||
? firstPage[0].last_status.id
|
||||
: firstPage[0].id
|
||||
}
|
||||
: undefined
|
||||
},
|
||||
getNextPageParam: lastPage => {
|
||||
return Array.isArray(lastPage) && lastPage.length
|
||||
? {
|
||||
@ -94,16 +80,23 @@ const Timeline: React.FC<Props> = ({
|
||||
|
||||
// Clear unread notification badge
|
||||
const dispatch = useDispatch()
|
||||
const navigation = useNavigation()
|
||||
useEffect(() => {
|
||||
if (page === 'Notifications' && flattenData.length) {
|
||||
dispatch(
|
||||
localUpdateNotification({
|
||||
unread: false,
|
||||
latestTime: (flattenData[0] as Mastodon.Notification).created_at
|
||||
})
|
||||
)
|
||||
}
|
||||
}, [flattenData])
|
||||
const unsubscribe = navigation.addListener('focus', props => {
|
||||
if (props.target && props.target.includes('Screen-Notifications-Root')) {
|
||||
if (flattenData.length) {
|
||||
dispatch(
|
||||
localUpdateNotification({
|
||||
unread: false,
|
||||
latestTime: (flattenData[0] as Mastodon.Notification).created_at
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return unsubscribe
|
||||
}, [navigation, flattenData])
|
||||
|
||||
const flRef = useRef<FlatList<any>>(null)
|
||||
useEffect(() => {
|
||||
@ -166,43 +159,29 @@ const Timeline: React.FC<Props> = ({
|
||||
[hasNextPage]
|
||||
)
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const isSwipeDown = useRef(false)
|
||||
const refreshControl = useMemo(
|
||||
() => (
|
||||
<RefreshControl
|
||||
{...(Platform.OS === 'android' && { enabled: true })}
|
||||
refreshing={
|
||||
isFetchingPreviousPage ||
|
||||
(isFetching && !isFetchingNextPage && !isLoading)
|
||||
isSwipeDown.current && isFetching && !isFetchingNextPage && !isLoading
|
||||
}
|
||||
onRefresh={() => {
|
||||
// if (hasPreviousPage) {
|
||||
// fetchPreviousPage()
|
||||
// } else {
|
||||
// queryClient.setQueryData<InfiniteData<any> | undefined>(
|
||||
// queryKey,
|
||||
// data => {
|
||||
// if (data) {
|
||||
// return {
|
||||
// pages: data.pages.slice(1),
|
||||
// pageParams: data.pageParams.slice(1)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
isSwipeDown.current = true
|
||||
refetch()
|
||||
// }
|
||||
}}
|
||||
/>
|
||||
),
|
||||
[
|
||||
hasPreviousPage,
|
||||
isFetchingPreviousPage,
|
||||
isFetching,
|
||||
isFetchingNextPage,
|
||||
isLoading
|
||||
]
|
||||
[isSwipeDown.current, isFetching, isFetchingNextPage, isLoading]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFetching) {
|
||||
isSwipeDown.current = false
|
||||
}
|
||||
}, [isFetching])
|
||||
|
||||
const onScrollToIndexFailed = useCallback(error => {
|
||||
const offset = error.averageItemLength * error.index
|
||||
flRef.current?.scrollToOffset({ offset })
|
||||
|
@ -79,43 +79,44 @@ const TimelineConversation: React.FC<Props> = ({
|
||||
</View>
|
||||
|
||||
{conversation.last_status ? (
|
||||
<View
|
||||
style={{
|
||||
paddingTop: highlighted ? StyleConstants.Spacing.S : 0,
|
||||
paddingLeft: highlighted
|
||||
? 0
|
||||
: StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
<TimelineContent
|
||||
status={conversation.last_status}
|
||||
highlighted={highlighted}
|
||||
/>
|
||||
{conversation.last_status.poll && (
|
||||
<TimelinePoll
|
||||
queryKey={queryKey}
|
||||
statusId={conversation.last_status.id}
|
||||
poll={conversation.last_status.poll}
|
||||
reblog={false}
|
||||
sameAccount={conversation.last_status.id === localAccount?.id}
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
paddingTop: highlighted ? StyleConstants.Spacing.S : 0,
|
||||
paddingLeft: highlighted
|
||||
? 0
|
||||
: StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
<TimelineContent
|
||||
status={conversation.last_status}
|
||||
highlighted={highlighted}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
{conversation.last_status.poll && (
|
||||
<TimelinePoll
|
||||
queryKey={queryKey}
|
||||
statusId={conversation.last_status.id}
|
||||
poll={conversation.last_status.poll}
|
||||
reblog={false}
|
||||
sameAccount={conversation.last_status.id === localAccount?.id}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
paddingLeft: highlighted
|
||||
? 0
|
||||
: StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
<TimelineActions
|
||||
queryKey={queryKey}
|
||||
status={conversation.last_status}
|
||||
reblog={false}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<View
|
||||
style={{
|
||||
paddingLeft: highlighted
|
||||
? 0
|
||||
: StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
<TimelineActions
|
||||
queryKey={queryKey}
|
||||
status={conversation.last_status!}
|
||||
reblog={false}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
@ -111,12 +111,10 @@ const AttachmentAudio: React.FC<Props> = ({
|
||||
minimumTrackTintColor={theme.secondary}
|
||||
maximumTrackTintColor={theme.disabled}
|
||||
// onSlidingStart={() => {
|
||||
// console.log('yes!!!')
|
||||
// audioPlayer?.pauseAsync()
|
||||
// setAudioPlaying(false)
|
||||
// }}
|
||||
// onSlidingComplete={value => {
|
||||
// console.log('no!!!')
|
||||
// setAudioPosition(value)
|
||||
// }}
|
||||
enabled={false} // Bug in above sliding actions
|
||||
|
@ -59,7 +59,9 @@ const AttachmentUnsupported: React.FC<Props> = ({
|
||||
content={t('shared.attachment.unsupported.button')}
|
||||
size='S'
|
||||
overlay
|
||||
onPress={async () => await openLink(attachment.remote_url!)}
|
||||
onPress={async () =>
|
||||
attachment.remote_url && (await openLink(attachment.remote_url))
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
|
Reference in New Issue
Block a user