mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Update translations
This commit is contained in:
@ -85,8 +85,7 @@ const client = async <T = unknown>({
|
|||||||
ctx.bold(' API '),
|
ctx.bold(' API '),
|
||||||
ctx.bold('response'),
|
ctx.bold('response'),
|
||||||
error.response.status,
|
error.response.status,
|
||||||
error.response.data.error,
|
error.response.data.error
|
||||||
error.request
|
|
||||||
)
|
)
|
||||||
return Promise.reject(error.response)
|
return Promise.reject(error.response)
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useRef } from 'react'
|
import React from 'react'
|
||||||
import { Dimensions, Modal, StyleSheet, View } from 'react-native'
|
import { Dimensions, Modal, StyleSheet, View } from 'react-native'
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
} from '@utils/slices/instancesSlice'
|
} from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
import * as Linking from 'expo-linking'
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -36,7 +37,7 @@ const ComponentInstance: React.FC<Props> = ({
|
|||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const { t } = useTranslation('meRoot')
|
const { t } = useTranslation('componentInstance')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const [instanceDomain, setInstanceDomain] = useState<string | undefined>()
|
const [instanceDomain, setInstanceDomain] = useState<string | undefined>()
|
||||||
const [appData, setApplicationData] = useState<InstanceLocal['appData']>()
|
const [appData, setApplicationData] = useState<InstanceLocal['appData']>()
|
||||||
@ -92,13 +93,14 @@ const ComponentInstance: React.FC<Props> = ({
|
|||||||
.length
|
.length
|
||||||
) {
|
) {
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
'域名已存在',
|
t('update.local.alert.title'),
|
||||||
'可以登录同个域名的另外一个账户,现有账户🈚️用',
|
t('update.local.alert.message'),
|
||||||
[
|
[
|
||||||
{ text: '取消', style: 'cancel' },
|
{ text: t('update.local.alert.buttons.cancel'), style: 'cancel' },
|
||||||
{
|
{
|
||||||
text: '继续',
|
text: t('update.local.alert.buttons.continue'),
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
|
setApplicationData(undefined)
|
||||||
applicationQuery.refetch()
|
applicationQuery.refetch()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,8 +118,8 @@ const ComponentInstance: React.FC<Props> = ({
|
|||||||
]
|
]
|
||||||
dispatch(remoteUpdate(instanceDomain))
|
dispatch(remoteUpdate(instanceDomain))
|
||||||
queryClient.resetQueries(queryKey)
|
queryClient.resetQueries(queryKey)
|
||||||
toast({ type: 'success', message: '重置成功' })
|
toast({ type: 'success', message: t('update.remote.succeed') })
|
||||||
navigation.navigate('Screen-Public', { screen: 'Screen-Public-Root' })
|
navigation.goBack()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,9 +145,9 @@ const ComponentInstance: React.FC<Props> = ({
|
|||||||
const buttonContent = useMemo(() => {
|
const buttonContent = useMemo(() => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'local':
|
case 'local':
|
||||||
return t('content.login.button')
|
return t('server.button.local')
|
||||||
case 'remote':
|
case 'remote':
|
||||||
return '登记'
|
return t('server.button.remote')
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -176,7 +178,7 @@ const ComponentInstance: React.FC<Props> = ({
|
|||||||
keyboardType='url'
|
keyboardType='url'
|
||||||
textContentType='URL'
|
textContentType='URL'
|
||||||
onSubmitEditing={onSubmitEditing}
|
onSubmitEditing={onSubmitEditing}
|
||||||
placeholder={t('content.login.server.placeholder')}
|
placeholder={t('server.textInput.placeholder')}
|
||||||
placeholderTextColor={theme.secondary}
|
placeholderTextColor={theme.secondary}
|
||||||
returnKeyType='go'
|
returnKeyType='go'
|
||||||
/>
|
/>
|
||||||
@ -191,21 +193,21 @@ const ComponentInstance: React.FC<Props> = ({
|
|||||||
<View>
|
<View>
|
||||||
<InstanceInfo
|
<InstanceInfo
|
||||||
visible={instanceQuery.data?.title !== undefined}
|
visible={instanceQuery.data?.title !== undefined}
|
||||||
header='实例名称'
|
header={t('server.information.name')}
|
||||||
content={instanceQuery.data?.title || undefined}
|
content={instanceQuery.data?.title || undefined}
|
||||||
potentialWidth={10}
|
potentialWidth={10}
|
||||||
/>
|
/>
|
||||||
<InstanceInfo
|
<InstanceInfo
|
||||||
visible={instanceQuery.data?.short_description !== undefined}
|
visible={instanceQuery.data?.short_description !== undefined}
|
||||||
header='实例介绍'
|
header={t('server.information.description.heading')}
|
||||||
content={instanceQuery.data?.short_description || undefined}
|
content={instanceQuery.data?.short_description || undefined}
|
||||||
potentialLines={5}
|
potentialLines={5}
|
||||||
/>
|
/>
|
||||||
<View style={styles.instanceStats}>
|
<View style={styles.instanceStats}>
|
||||||
<InstanceInfo
|
<InstanceInfo
|
||||||
style={{ alignItems: 'flex-start' }}
|
style={{ alignItems: 'flex-start' }}
|
||||||
visible={instanceQuery.data?.stats?.user_count !== null}
|
visible={instanceQuery.data?.stats?.user_count === null}
|
||||||
header='用户总数'
|
header={t('server.information.accounts')}
|
||||||
content={
|
content={
|
||||||
instanceQuery.data?.stats?.user_count?.toString() || undefined
|
instanceQuery.data?.stats?.user_count?.toString() || undefined
|
||||||
}
|
}
|
||||||
@ -213,8 +215,8 @@ const ComponentInstance: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
<InstanceInfo
|
<InstanceInfo
|
||||||
style={{ alignItems: 'center' }}
|
style={{ alignItems: 'center' }}
|
||||||
visible={instanceQuery.data?.stats?.status_count !== null}
|
visible={instanceQuery.data?.stats?.status_count === null}
|
||||||
header='嘟嘟总数'
|
header={t('server.information.statuses')}
|
||||||
content={
|
content={
|
||||||
instanceQuery.data?.stats?.status_count?.toString() || undefined
|
instanceQuery.data?.stats?.status_count?.toString() || undefined
|
||||||
}
|
}
|
||||||
@ -222,22 +224,31 @@ const ComponentInstance: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
<InstanceInfo
|
<InstanceInfo
|
||||||
style={{ alignItems: 'flex-end' }}
|
style={{ alignItems: 'flex-end' }}
|
||||||
visible={instanceQuery.data?.stats?.domain_count !== null}
|
visible={instanceQuery.data?.stats?.domain_count === null}
|
||||||
header='嘟嘟总数'
|
header={t('server.information.domains')}
|
||||||
content={
|
content={
|
||||||
instanceQuery.data?.stats?.domain_count?.toString() || undefined
|
instanceQuery.data?.stats?.domain_count?.toString() || undefined
|
||||||
}
|
}
|
||||||
potentialWidth={4}
|
potentialWidth={4}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Text style={[styles.disclaimer, { color: theme.secondary }]}>
|
<View style={styles.disclaimer}>
|
||||||
<Icon
|
<Icon
|
||||||
name='Lock'
|
name='Lock'
|
||||||
size={StyleConstants.Font.Size.M}
|
size={StyleConstants.Font.Size.S}
|
||||||
color={theme.secondary}
|
color={theme.secondary}
|
||||||
/>{' '}
|
style={styles.disclaimerIcon}
|
||||||
本站不留存任何信息
|
/>
|
||||||
|
<Text
|
||||||
|
style={[styles.disclaimerText, { color: theme.secondary }]}
|
||||||
|
onPress={() => Linking.openURL('https://tooot.app/privacy')}
|
||||||
|
>
|
||||||
|
{t('server.disclaimer')}
|
||||||
|
<Text style={{ color: theme.blue }}>
|
||||||
|
https://tooot.app/privacy
|
||||||
</Text>
|
</Text>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@ -275,9 +286,18 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: 'row'
|
flexDirection: 'row'
|
||||||
},
|
},
|
||||||
disclaimer: {
|
disclaimer: {
|
||||||
...StyleConstants.FontStyle.S,
|
flexDirection: 'row',
|
||||||
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||||
marginVertical: StyleConstants.Spacing.M
|
marginVertical: StyleConstants.Spacing.M
|
||||||
|
},
|
||||||
|
disclaimerIcon: {
|
||||||
|
marginTop:
|
||||||
|
(StyleConstants.Font.LineHeight.S - StyleConstants.Font.Size.S) / 2,
|
||||||
|
marginRight: StyleConstants.Spacing.XS
|
||||||
|
},
|
||||||
|
disclaimerText: {
|
||||||
|
flex: 1,
|
||||||
|
...StyleConstants.FontStyle.S
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { LinearGradient } from 'expo-linear-gradient'
|
import { LinearGradient } from 'expo-linear-gradient'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Dimensions, StyleSheet, Text, View, ViewStyle } from 'react-native'
|
import { Dimensions, StyleSheet, Text, View, ViewStyle } from 'react-native'
|
||||||
import { createShimmerPlaceholder } from 'react-native-shimmer-placeholder'
|
import { createShimmerPlaceholder } from 'react-native-shimmer-placeholder'
|
||||||
|
|
||||||
@ -24,6 +25,7 @@ const InstanceInfo = React.memo(
|
|||||||
potentialWidth,
|
potentialWidth,
|
||||||
potentialLines = 1
|
potentialLines = 1
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const { t } = useTranslation('componentInstance')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const ShimmerPlaceholder = createShimmerPlaceholder(LinearGradient)
|
const ShimmerPlaceholder = createShimmerPlaceholder(LinearGradient)
|
||||||
|
|
||||||
@ -40,14 +42,18 @@ const InstanceInfo = React.memo(
|
|||||||
StyleConstants.Spacing.Global.PagePadding * 4
|
StyleConstants.Spacing.Global.PagePadding * 4
|
||||||
}
|
}
|
||||||
height={StyleConstants.Font.LineHeight.M * potentialLines}
|
height={StyleConstants.Font.LineHeight.M * potentialLines}
|
||||||
shimmerColors={[theme.shimmerDefault, theme.shimmerHighlight, theme.shimmerDefault]}
|
shimmerColors={[
|
||||||
|
theme.shimmerDefault,
|
||||||
|
theme.shimmerHighlight,
|
||||||
|
theme.shimmerDefault
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
{content ? (
|
{content ? (
|
||||||
<ParseHTML
|
<ParseHTML
|
||||||
content={content}
|
content={content}
|
||||||
size={'M'}
|
size={'M'}
|
||||||
numberOfLines={5}
|
numberOfLines={5}
|
||||||
expandHint='介绍'
|
expandHint={t('server.information.description.expandHint')}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</ShimmerPlaceholder>
|
</ShimmerPlaceholder>
|
||||||
|
@ -19,7 +19,7 @@ export interface Props {
|
|||||||
switchDisabled?: boolean
|
switchDisabled?: boolean
|
||||||
switchOnValueChange?: () => void
|
switchOnValueChange?: () => void
|
||||||
|
|
||||||
iconBack?: 'ChevronRight' | 'Check'
|
iconBack?: 'ChevronRight' | 'ExternalLink'
|
||||||
iconBackColor?: ColorDefinitions
|
iconBackColor?: ColorDefinitions
|
||||||
|
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { StyleSheet, Text } from 'react-native'
|
import { StyleSheet, Text } from 'react-native'
|
||||||
import { Image } from 'react-native-expo-image-cache'
|
import { Image } from 'react-native-expo-image-cache'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
|
||||||
|
|
||||||
const regexEmoji = new RegExp(/(:[A-Za-z0-9_]+:)/)
|
const regexEmoji = new RegExp(/(:[A-Za-z0-9_]+:)/)
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import layoutAnimation from '@utils/styles/layoutAnimation'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { LinearGradient } from 'expo-linear-gradient'
|
import { LinearGradient } from 'expo-linear-gradient'
|
||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Pressable, Text, View } from 'react-native'
|
import { Pressable, Text, View } from 'react-native'
|
||||||
import HTMLView from 'react-native-htmlview'
|
import HTMLView from 'react-native-htmlview'
|
||||||
|
|
||||||
@ -191,6 +192,7 @@ const ParseHTML: React.FC<Props> = ({
|
|||||||
}, [])
|
}, [])
|
||||||
const rootComponent = useCallback(
|
const rootComponent = useCallback(
|
||||||
({ children }) => {
|
({ children }) => {
|
||||||
|
const { t } = useTranslation('componentParse')
|
||||||
const lineHeight = StyleConstants.Font.LineHeight[size]
|
const lineHeight = StyleConstants.Font.LineHeight[size]
|
||||||
|
|
||||||
const [expandAllow, setExpandAllow] = useState(false)
|
const [expandAllow, setExpandAllow] = useState(false)
|
||||||
@ -249,7 +251,9 @@ const ParseHTML: React.FC<Props> = ({
|
|||||||
color: theme.primary
|
color: theme.primary
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{`${expanded ? '折叠' : '展开'}${expandHint}`}
|
{expanded
|
||||||
|
? t('HTML.expanded.true', { hint: expandHint })
|
||||||
|
: t('HTML.expanded.false', { hint: expandHint })}
|
||||||
</Text>
|
</Text>
|
||||||
</LinearGradient>
|
</LinearGradient>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
@ -16,7 +16,7 @@ export interface Props {
|
|||||||
|
|
||||||
const RelationshipOutgoing = React.memo(
|
const RelationshipOutgoing = React.memo(
|
||||||
({ id }: Props) => {
|
({ id }: Props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation('componentRelationship')
|
||||||
|
|
||||||
const query = useRelationshipQuery({ id })
|
const query = useRelationshipQuery({ id })
|
||||||
|
|
||||||
@ -30,12 +30,12 @@ const RelationshipOutgoing = React.memo(
|
|||||||
[res]
|
[res]
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onError: (err: any, { type }) => {
|
onError: (err: any, { payload: { action } }) => {
|
||||||
haptics('Error')
|
haptics('Error')
|
||||||
toast({
|
toast({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: t('common:toastMessage.error.message', {
|
message: t('common:toastMessage.error.message', {
|
||||||
function: t(`relationship:${type}.function`)
|
function: t(`button.${action}.function`)
|
||||||
}),
|
}),
|
||||||
...(err.status &&
|
...(err.status &&
|
||||||
typeof err.status === 'number' &&
|
typeof err.status === 'number' &&
|
||||||
@ -52,15 +52,15 @@ const RelationshipOutgoing = React.memo(
|
|||||||
let onPress: () => void
|
let onPress: () => void
|
||||||
|
|
||||||
if (query.isError) {
|
if (query.isError) {
|
||||||
content = t('relationship:button.error')
|
content = t('button.error')
|
||||||
onPress = () => {}
|
onPress = () => {}
|
||||||
} else {
|
} else {
|
||||||
if (query.data?.blocked_by) {
|
if (query.data?.blocked_by) {
|
||||||
content = t('relationship:button.blocked_by')
|
content = t('button.blocked_by')
|
||||||
onPress = () => null
|
onPress = () => null
|
||||||
} else {
|
} else {
|
||||||
if (query.data?.blocking) {
|
if (query.data?.blocking) {
|
||||||
content = t('relationship:button.blocking')
|
content = t('button.blocking')
|
||||||
onPress = () =>
|
onPress = () =>
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
id,
|
id,
|
||||||
@ -72,7 +72,7 @@ const RelationshipOutgoing = React.memo(
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
if (query.data?.following) {
|
if (query.data?.following) {
|
||||||
content = t('relationship:button.following')
|
content = t('button.following')
|
||||||
onPress = () =>
|
onPress = () =>
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
id,
|
id,
|
||||||
@ -84,7 +84,7 @@ const RelationshipOutgoing = React.memo(
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
if (query.data?.requested) {
|
if (query.data?.requested) {
|
||||||
content = t('relationship:button.requested')
|
content = t('button.requested')
|
||||||
onPress = () =>
|
onPress = () =>
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
id,
|
id,
|
||||||
@ -95,7 +95,7 @@ const RelationshipOutgoing = React.memo(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
content = t('relationship:button.default')
|
content = t('button.default')
|
||||||
onPress = () =>
|
onPress = () =>
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
id,
|
id,
|
||||||
|
@ -10,7 +10,7 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const RelativeTime: React.FC<Props> = ({ date }) => {
|
const RelativeTime: React.FC<Props> = ({ date }) => {
|
||||||
const { t } = useTranslation('relativeTime')
|
const { t } = useTranslation('componentRelativeTime')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TimeAgo
|
<TimeAgo
|
||||||
|
@ -15,7 +15,7 @@ export interface Props {
|
|||||||
|
|
||||||
const TimelineEmpty: React.FC<Props> = ({ status, refetch }) => {
|
const TimelineEmpty: React.FC<Props> = ({ status, refetch }) => {
|
||||||
const { mode, theme } = useTheme()
|
const { mode, theme } = useTheme()
|
||||||
const { t, i18n } = useTranslation('timeline')
|
const { t, i18n } = useTranslation('componentTimeline')
|
||||||
|
|
||||||
const children = useMemo(() => {
|
const children = useMemo(() => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
|
@ -20,7 +20,7 @@ const TimelineEnd: React.FC<Props> = ({ hasNextPage }) => {
|
|||||||
) : (
|
) : (
|
||||||
<Text style={[styles.text, { color: theme.secondary }]}>
|
<Text style={[styles.text, { color: theme.secondary }]}>
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey='timeline:shared.end.message'
|
i18nKey='componentTimeline:end.message'
|
||||||
components={[
|
components={[
|
||||||
<Icon
|
<Icon
|
||||||
name='Coffee'
|
name='Coffee'
|
||||||
|
@ -4,11 +4,13 @@ import { StyleConstants } from '@root/utils/styles/constants'
|
|||||||
import { useTheme } from '@root/utils/styles/ThemeManager'
|
import { useTheme } from '@root/utils/styles/ThemeManager'
|
||||||
import { updatePublicRemoteNotice } from '@utils/slices/contextsSlice'
|
import { updatePublicRemoteNotice } from '@utils/slices/contextsSlice'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { StyleSheet, Text, View } from 'react-native'
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
import { useDispatch } from 'react-redux'
|
import { useDispatch } from 'react-redux'
|
||||||
|
|
||||||
const TimelineHeader = React.memo(
|
const TimelineHeader = React.memo(
|
||||||
() => {
|
() => {
|
||||||
|
const { t } = useTranslation('componentTimeline')
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
@ -16,7 +18,7 @@ const TimelineHeader = React.memo(
|
|||||||
return (
|
return (
|
||||||
<View style={[styles.base, { borderColor: theme.border }]}>
|
<View style={[styles.base, { borderColor: theme.border }]}>
|
||||||
<Text style={[styles.text, { color: theme.primary }]}>
|
<Text style={[styles.text, { color: theme.primary }]}>
|
||||||
一大堆文字一大堆文字一大堆文字一大堆文字一大堆文字一大堆文字一大堆文字一大堆文字一大堆文字一大堆文字一大堆文字一大堆文字一大堆文字一大堆文字一大堆文字一大堆文字一大堆文字一大堆文字{' '}
|
{t('header.explanation')}
|
||||||
<Text
|
<Text
|
||||||
style={{ color: theme.blue }}
|
style={{ color: theme.blue }}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
@ -27,7 +29,7 @@ const TimelineHeader = React.memo(
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
前往设置{' '}
|
{t('header.button')}
|
||||||
<Icon
|
<Icon
|
||||||
name='ArrowRight'
|
name='ArrowRight'
|
||||||
size={StyleConstants.Font.Size.S}
|
size={StyleConstants.Font.Size.S}
|
||||||
|
@ -18,7 +18,7 @@ const TimelineActioned: React.FC<Props> = ({
|
|||||||
action,
|
action,
|
||||||
notification = false
|
notification = false
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation('timeline')
|
const { t } = useTranslation('componentTimeline')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
const name = account.display_name || account.username
|
const name = account.display_name || account.username
|
||||||
|
@ -11,14 +11,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useMemo } from 'react'
|
import React, { useCallback, useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||||
Platform,
|
|
||||||
Pressable,
|
|
||||||
Share,
|
|
||||||
StyleSheet,
|
|
||||||
Text,
|
|
||||||
View
|
|
||||||
} from 'react-native'
|
|
||||||
import { useQueryClient } from 'react-query'
|
import { useQueryClient } from 'react-query'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -29,7 +22,7 @@ export interface Props {
|
|||||||
|
|
||||||
const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation('componentTimeline')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const iconColor = theme.secondary
|
const iconColor = theme.secondary
|
||||||
const iconColorAction = (state: boolean) =>
|
const iconColorAction = (state: boolean) =>
|
||||||
@ -84,7 +77,7 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
|||||||
type: 'error',
|
type: 'error',
|
||||||
message: t('common:toastMessage.error.message', {
|
message: t('common:toastMessage.error.message', {
|
||||||
function: t(
|
function: t(
|
||||||
`timeline:shared.actions.${correctParam.payload.property}.function`
|
`shared.actions.${correctParam.payload.property}.function`
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
...(err.status &&
|
...(err.status &&
|
||||||
@ -95,7 +88,7 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
|||||||
description: err.data.error
|
description: err.data.error
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
queryClient.setQueryData(queryKey, oldData)
|
queryClient.invalidateQueries(queryKey)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -150,18 +143,6 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
|||||||
}),
|
}),
|
||||||
[status.bookmarked]
|
[status.bookmarked]
|
||||||
)
|
)
|
||||||
const onPressShare = useCallback(() => {
|
|
||||||
switch (Platform.OS) {
|
|
||||||
case 'ios':
|
|
||||||
return Share.share({
|
|
||||||
url: status.uri
|
|
||||||
})
|
|
||||||
case 'android':
|
|
||||||
return Share.share({
|
|
||||||
message: status.uri
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const childrenReply = useMemo(
|
const childrenReply = useMemo(
|
||||||
() => (
|
() => (
|
||||||
@ -220,12 +201,6 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
|||||||
),
|
),
|
||||||
[status.bookmarked]
|
[status.bookmarked]
|
||||||
)
|
)
|
||||||
const childrenShare = useMemo(
|
|
||||||
() => (
|
|
||||||
<Icon name='Share2' color={iconColor} size={StyleConstants.Font.Size.L} />
|
|
||||||
),
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -256,12 +231,6 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
|||||||
onPress={onPressBookmark}
|
onPress={onPressBookmark}
|
||||||
children={childrenBookmark}
|
children={childrenBookmark}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Pressable
|
|
||||||
style={styles.action}
|
|
||||||
onPress={onPressShare}
|
|
||||||
children={childrenShare}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@ -269,16 +238,13 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
actions: {
|
actions: {
|
||||||
width: '100%',
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
marginTop: StyleConstants.Spacing.S
|
marginTop: StyleConstants.Spacing.S
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
width: '20%',
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
|
||||||
paddingVertical: StyleConstants.Spacing.S
|
paddingVertical: StyleConstants.Spacing.S
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -17,7 +17,7 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TimelineAttachment: React.FC<Props> = ({ status }) => {
|
const TimelineAttachment: React.FC<Props> = ({ status }) => {
|
||||||
const { t } = useTranslation('timeline')
|
const { t } = useTranslation('componentTimeline')
|
||||||
|
|
||||||
const [sensitiveShown, setSensitiveShown] = useState(status.sensitive)
|
const [sensitiveShown, setSensitiveShown] = useState(status.sensitive)
|
||||||
const onPressBlurView = useCallback(() => {
|
const onPressBlurView = useCallback(() => {
|
||||||
|
@ -22,7 +22,7 @@ const AttachmentUnsupported: React.FC<Props> = ({
|
|||||||
sensitiveShown,
|
sensitiveShown,
|
||||||
attachment
|
attachment
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation('timeline')
|
const { t } = useTranslation('componentTimeline')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -17,7 +17,7 @@ const TimelineContent: React.FC<Props> = ({
|
|||||||
highlighted = false,
|
highlighted = false,
|
||||||
disableDetails = false
|
disableDetails = false
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation('timeline')
|
const { t } = useTranslation('componentTimeline')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -20,30 +20,29 @@ const HeaderActionsAccount: React.FC<Props> = ({
|
|||||||
account,
|
account,
|
||||||
setBottomSheetVisible
|
setBottomSheetVisible
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation('componentTimeline')
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const mutateion = useTimelineMutation({
|
const mutateion = useTimelineMutation({
|
||||||
queryClient,
|
queryClient,
|
||||||
onSuccess: (_, { type }) => {
|
onSuccess: (_, { payload: { property } }) => {
|
||||||
haptics('Success')
|
haptics('Success')
|
||||||
toast({
|
toast({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: t('common:toastMessage.success.message', {
|
message: t('common:toastMessage.success.message', {
|
||||||
function: t(
|
function: t(`shared.header.actions.account.${property}.function`, {
|
||||||
`timeline:shared.header.default.actions.account.${type}.function`,
|
acct: account.acct
|
||||||
{ acct: account.acct }
|
})
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onError: (err: any, { type }) => {
|
onError: (err: any, { payload: { property } }) => {
|
||||||
haptics('Error')
|
haptics('Error')
|
||||||
toast({
|
toast({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: t('common:toastMessage.error.message', {
|
message: t('common:toastMessage.error.message', {
|
||||||
function: t(
|
function: t(
|
||||||
`timeline:shared.header.default.actions.account.${type}.function`,
|
`shared.header.actions.account.${property}.function`,
|
||||||
{ acct: account.acct }
|
{ acct: account.acct }
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
@ -64,7 +63,7 @@ const HeaderActionsAccount: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<MenuContainer>
|
<MenuContainer>
|
||||||
<MenuHeader
|
<MenuHeader
|
||||||
heading={t('timeline:shared.header.default.actions.account.heading')}
|
heading={t('shared.header.actions.account.heading')}
|
||||||
/>
|
/>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
@ -77,7 +76,7 @@ const HeaderActionsAccount: React.FC<Props> = ({
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
iconFront='EyeOff'
|
iconFront='EyeOff'
|
||||||
title={t('timeline:shared.header.default.actions.account.mute.button', {
|
title={t('shared.header.actions.account.mute.button', {
|
||||||
acct: account.acct
|
acct: account.acct
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@ -93,7 +92,7 @@ const HeaderActionsAccount: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
iconFront='XCircle'
|
iconFront='XCircle'
|
||||||
title={t(
|
title={t(
|
||||||
'timeline:shared.header.default.actions.account.block.button',
|
'shared.header.actions.account.block.button',
|
||||||
{
|
{
|
||||||
acct: account.acct
|
acct: account.acct
|
||||||
}
|
}
|
||||||
@ -111,7 +110,7 @@ const HeaderActionsAccount: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
iconFront='Flag'
|
iconFront='Flag'
|
||||||
title={t(
|
title={t(
|
||||||
'timeline:shared.header.default.actions.account.report.button',
|
'shared.header.actions.account.reports.button',
|
||||||
{
|
{
|
||||||
acct: account.acct
|
acct: account.acct
|
||||||
}
|
}
|
@ -22,7 +22,7 @@ const HeaderActionsDomain: React.FC<Props> = ({
|
|||||||
domain,
|
domain,
|
||||||
setBottomSheetVisible
|
setBottomSheetVisible
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation('componentTimeline')
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const mutation = useTimelineMutation({
|
const mutation = useTimelineMutation({
|
||||||
queryClient,
|
queryClient,
|
||||||
@ -30,9 +30,7 @@ const HeaderActionsDomain: React.FC<Props> = ({
|
|||||||
toast({
|
toast({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: t('common:toastMessage.success.message', {
|
message: t('common:toastMessage.success.message', {
|
||||||
function: t(
|
function: t(`shared.header.actions.domain.block.function`)
|
||||||
`timeline:shared.header.default.actions.domain.block.function`
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
queryClient.invalidateQueries(queryKey)
|
queryClient.invalidateQueries(queryKey)
|
||||||
@ -41,20 +39,19 @@ const HeaderActionsDomain: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuContainer>
|
<MenuContainer>
|
||||||
<MenuHeader
|
<MenuHeader heading={t(`shared.header.actions.domain.heading`)} />
|
||||||
heading={t(`timeline:shared.header.default.actions.domain.heading`)}
|
|
||||||
/>
|
|
||||||
<MenuRow
|
<MenuRow
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
t('timeline:shared.header.default.actions.domain.alert.title'),
|
t('shared.header.actions.domain.alert.title', { domain }),
|
||||||
t('timeline:shared.header.default.actions.domain.alert.message'),
|
t('shared.header.actions.domain.alert.message'),
|
||||||
[
|
[
|
||||||
{ text: t('common:buttons.cancel'), style: 'cancel' },
|
|
||||||
{
|
{
|
||||||
text: t(
|
text: t('shared.header.actions.domain.alert.buttons.cancel'),
|
||||||
'timeline:shared.header.default.actions.domain.alert.confirm'
|
style: 'cancel'
|
||||||
),
|
},
|
||||||
|
{
|
||||||
|
text: t('shared.header.actions.domain.alert.buttons.confirm'),
|
||||||
style: 'destructive',
|
style: 'destructive',
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
setBottomSheetVisible(false)
|
setBottomSheetVisible(false)
|
||||||
@ -69,7 +66,7 @@ const HeaderActionsDomain: React.FC<Props> = ({
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
iconFront='CloudOff'
|
iconFront='CloudOff'
|
||||||
title={t(`timeline:shared.header.default.actions.domain.block.button`, {
|
title={t(`shared.header.actions.domain.block.button`, {
|
||||||
domain
|
domain
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
@ -7,17 +7,20 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
|||||||
import React, { useCallback, useMemo, useState } from 'react'
|
import React, { useCallback, useMemo, useState } from 'react'
|
||||||
import { Pressable, StyleSheet } from 'react-native'
|
import { Pressable, StyleSheet } from 'react-native'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import HeaderActionsAccount from './ActionsAccount'
|
import HeaderActionsAccount from './Account'
|
||||||
import HeaderActionsDomain from './ActionsDomain'
|
import HeaderActionsDomain from './Domain'
|
||||||
import HeaderActionsStatus from './ActionsStatus'
|
import HeaderActionsShare from './Share'
|
||||||
|
import HeaderActionsStatus from './Status'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
queryKey: QueryKeyTimeline
|
queryKey: QueryKeyTimeline
|
||||||
status: Mastodon.Status
|
status: Mastodon.Status
|
||||||
|
url?: string
|
||||||
|
type?: 'status' | 'account'
|
||||||
}
|
}
|
||||||
|
|
||||||
const HeaderActions = React.memo(
|
const HeaderActions = React.memo(
|
||||||
({ queryKey, status }: Props) => {
|
({ queryKey, status, url, type }: Props) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
const localAccount = useSelector(getLocalAccount)
|
const localAccount = useSelector(getLocalAccount)
|
||||||
@ -73,6 +76,14 @@ const HeaderActions = React.memo(
|
|||||||
setBottomSheetVisible={setBottomSheetVisible}
|
setBottomSheetVisible={setBottomSheetVisible}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{url && type ? (
|
||||||
|
<HeaderActionsShare
|
||||||
|
url={url}
|
||||||
|
type={type}
|
||||||
|
setBottomSheetVisible={setBottomSheetVisible}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</BottomSheet>
|
</BottomSheet>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
import MenuContainer from '@components/Menu/Container'
|
||||||
|
import MenuHeader from '@components/Menu/Header'
|
||||||
|
import MenuRow from '@components/Menu/Row'
|
||||||
|
import { toast } from '@components/toast'
|
||||||
|
import {
|
||||||
|
QueryKeyTimeline,
|
||||||
|
useTimelineMutation
|
||||||
|
} from '@utils/queryHooks/timeline'
|
||||||
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Platform, Share } from 'react-native'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
type: 'status' | 'account'
|
||||||
|
url: string
|
||||||
|
setBottomSheetVisible: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
|
}
|
||||||
|
|
||||||
|
const HeaderActionsShare: React.FC<Props> = ({
|
||||||
|
type,
|
||||||
|
url,
|
||||||
|
setBottomSheetVisible
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation('componentTimeline')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuContainer>
|
||||||
|
<MenuHeader heading={t(`shared.header.actions.share.${type}.heading`)} />
|
||||||
|
<MenuRow
|
||||||
|
iconFront='Share2'
|
||||||
|
title={t(`shared.header.actions.share.${type}.button`)}
|
||||||
|
onPress={async () => {
|
||||||
|
switch (Platform.OS) {
|
||||||
|
case 'ios':
|
||||||
|
await Share.share({
|
||||||
|
url
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 'android':
|
||||||
|
await Share.share({
|
||||||
|
message: url
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
setBottomSheetVisible(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</MenuContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HeaderActionsShare
|
@ -23,7 +23,7 @@ const HeaderActionsStatus: React.FC<Props> = ({
|
|||||||
setBottomSheetVisible
|
setBottomSheetVisible
|
||||||
}) => {
|
}) => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation('componentTimeline')
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const mutation = useTimelineMutation({
|
const mutation = useTimelineMutation({
|
||||||
@ -38,7 +38,7 @@ const HeaderActionsStatus: React.FC<Props> = ({
|
|||||||
type: 'error',
|
type: 'error',
|
||||||
message: t('common:toastMessage.error.message', {
|
message: t('common:toastMessage.error.message', {
|
||||||
function: t(
|
function: t(
|
||||||
`timeline:shared.header.default.actions.status.${theFunction}.function`
|
`shared.header.actions.status.${theFunction}.function`
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
...(err.status &&
|
...(err.status &&
|
||||||
@ -56,7 +56,7 @@ const HeaderActionsStatus: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<MenuContainer>
|
<MenuContainer>
|
||||||
<MenuHeader
|
<MenuHeader
|
||||||
heading={t('timeline:shared.header.default.actions.status.heading')}
|
heading={t('shared.header.actions.status.heading')}
|
||||||
/>
|
/>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
@ -69,20 +69,20 @@ const HeaderActionsStatus: React.FC<Props> = ({
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
iconFront='Trash'
|
iconFront='Trash'
|
||||||
title={t('timeline:shared.header.default.actions.status.delete.button')}
|
title={t('shared.header.actions.status.delete.button')}
|
||||||
/>
|
/>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
t('timeline:shared.header.default.actions.status.edit.alert.title'),
|
t('shared.header.actions.status.edit.alert.title'),
|
||||||
t(
|
t(
|
||||||
'timeline:shared.header.default.actions.status.edit.alert.message'
|
'shared.header.actions.status.edit.alert.message'
|
||||||
),
|
),
|
||||||
[
|
[
|
||||||
{ text: t('common:buttons.cancel'), style: 'cancel' },
|
{ text: t('shared.header.actions.status.edit.alert.buttons.cancel'), style: 'cancel' },
|
||||||
{
|
{
|
||||||
text: t(
|
text: t(
|
||||||
'timeline:shared.header.default.actions.status.edit.alert.confirm'
|
'shared.header.actions.status.edit.alert.buttons.confirm'
|
||||||
),
|
),
|
||||||
style: 'destructive',
|
style: 'destructive',
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
@ -105,8 +105,8 @@ const HeaderActionsStatus: React.FC<Props> = ({
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
iconFront='Trash'
|
iconFront='Edit'
|
||||||
title={t('timeline:shared.header.default.actions.status.edit.button')}
|
title={t('shared.header.actions.status.edit.button')}
|
||||||
/>
|
/>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
@ -122,10 +122,10 @@ const HeaderActionsStatus: React.FC<Props> = ({
|
|||||||
title={
|
title={
|
||||||
status.muted
|
status.muted
|
||||||
? t(
|
? t(
|
||||||
'timeline:shared.header.default.actions.status.mute.button.negative'
|
'shared.header.actions.status.mute.button.negative'
|
||||||
)
|
)
|
||||||
: t(
|
: t(
|
||||||
'timeline:shared.header.default.actions.status.mute.button.positive'
|
'shared.header.actions.status.mute.button.positive'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -145,10 +145,10 @@ const HeaderActionsStatus: React.FC<Props> = ({
|
|||||||
title={
|
title={
|
||||||
status.pinned
|
status.pinned
|
||||||
? t(
|
? t(
|
||||||
'timeline:shared.header.default.actions.status.pin.button.negative'
|
'shared.header.actions.status.pin.button.negative'
|
||||||
)
|
)
|
||||||
: t(
|
: t(
|
||||||
'timeline:shared.header.default.actions.status.pin.button.positive'
|
'shared.header.actions.status.pin.button.positive'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
@ -21,7 +21,7 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
|
const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation('componentTimeline')
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const mutation = useTimelineMutation({
|
const mutation = useTimelineMutation({
|
||||||
@ -32,7 +32,7 @@ const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
|
|||||||
toast({
|
toast({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: t('common:toastMessage.error.message', {
|
message: t('common:toastMessage.error.message', {
|
||||||
function: t(`timeline:shared.header.conversation.delete.function`)
|
function: t(`shared.header.conversation.delete.function`)
|
||||||
}),
|
}),
|
||||||
...(err.status &&
|
...(err.status &&
|
||||||
typeof err.status === 'number' &&
|
typeof err.status === 'number' &&
|
||||||
|
@ -27,7 +27,14 @@ const TimelineHeaderDefault: React.FC<Props> = ({ queryKey, status }) => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{queryKey ? <HeaderActions queryKey={queryKey} status={status} /> : null}
|
{queryKey ? (
|
||||||
|
<HeaderActions
|
||||||
|
queryKey={queryKey}
|
||||||
|
status={status}
|
||||||
|
url={status.url || status.uri}
|
||||||
|
type='status'
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ export interface Props {
|
|||||||
|
|
||||||
const HeaderSharedApplication: React.FC<Props> = ({ application }) => {
|
const HeaderSharedApplication: React.FC<Props> = ({ application }) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { t } = useTranslation('timeline')
|
const { t } = useTranslation('componentTimeline')
|
||||||
|
|
||||||
return application && application.name !== 'Web' ? (
|
return application && application.name !== 'Web' ? (
|
||||||
<Text
|
<Text
|
||||||
|
@ -2,7 +2,6 @@ import RelativeTime from '@components/RelativeTime'
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import { StyleSheet, Text } from 'react-native'
|
import { StyleSheet, Text } from 'react-native'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -11,7 +10,6 @@ export interface Props {
|
|||||||
|
|
||||||
const HeaderSharedCreated: React.FC<Props> = ({ created_at }) => {
|
const HeaderSharedCreated: React.FC<Props> = ({ created_at }) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { i18n } = useTranslation()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text style={[styles.created_at, { color: theme.secondary }]}>
|
<Text style={[styles.created_at, { color: theme.secondary }]}>
|
||||||
|
@ -5,6 +5,7 @@ import { ParseEmojis } from '@components/Parse'
|
|||||||
import RelativeTime from '@components/RelativeTime'
|
import RelativeTime from '@components/RelativeTime'
|
||||||
import { toast } from '@components/toast'
|
import { toast } from '@components/toast'
|
||||||
import {
|
import {
|
||||||
|
MutationVarsTimelineUpdateStatusProperty,
|
||||||
QueryKeyTimeline,
|
QueryKeyTimeline,
|
||||||
useTimelineMutation
|
useTimelineMutation
|
||||||
} from '@utils/queryHooks/timeline'
|
} from '@utils/queryHooks/timeline'
|
||||||
@ -32,7 +33,7 @@ const TimelinePoll: React.FC<Props> = ({
|
|||||||
sameAccount
|
sameAccount
|
||||||
}) => {
|
}) => {
|
||||||
const { mode, theme } = useTheme()
|
const { mode, theme } = useTheme()
|
||||||
const { t } = useTranslation('timeline')
|
const { t } = useTranslation('componentTimeline')
|
||||||
|
|
||||||
const [allOptions, setAllOptions] = useState(
|
const [allOptions, setAllOptions] = useState(
|
||||||
new Array(poll.options.length).fill(false)
|
new Array(poll.options.length).fill(false)
|
||||||
@ -42,19 +43,21 @@ const TimelinePoll: React.FC<Props> = ({
|
|||||||
const mutation = useTimelineMutation({
|
const mutation = useTimelineMutation({
|
||||||
queryClient,
|
queryClient,
|
||||||
onSuccess: true,
|
onSuccess: true,
|
||||||
onError: (err: any) => {
|
onError: (err: any, params) => {
|
||||||
|
const theParams = params as MutationVarsTimelineUpdateStatusProperty
|
||||||
haptics('Error')
|
haptics('Error')
|
||||||
toast({
|
toast({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: '投票错误',
|
message: t('common:toastMessage.error.message', {
|
||||||
|
function: t(`shared.poll.meta.button.${theParams.payload.type}`)
|
||||||
|
}),
|
||||||
...(err.status &&
|
...(err.status &&
|
||||||
typeof err.status === 'number' &&
|
typeof err.status === 'number' &&
|
||||||
err.data &&
|
err.data &&
|
||||||
err.data.error &&
|
err.data.error &&
|
||||||
typeof err.data.error === 'string' && {
|
typeof err.data.error === 'string' && {
|
||||||
description: err.data.error
|
description: err.data.error
|
||||||
}),
|
})
|
||||||
autoHide: false
|
|
||||||
})
|
})
|
||||||
queryClient.invalidateQueries(queryKey)
|
queryClient.invalidateQueries(queryKey)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import * as Analytics from 'expo-firebase-analytics'
|
import * as Analytics from 'expo-firebase-analytics'
|
||||||
import * as Sentry from 'sentry-expo'
|
|
||||||
|
|
||||||
const analytics = (event: string, params?: { [key: string]: string }) => {
|
const analytics = (event: string, params?: { [key: string]: string }) => {
|
||||||
Analytics.logEvent(event, params).catch(
|
Analytics.logEvent(event, params).catch(
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export default {
|
export default {
|
||||||
common: require('./common').default,
|
common: require('./common').default,
|
||||||
|
|
||||||
relativeTime: require('./components/relativeTime').default
|
componentRelativeTime: require('./components/relativeTime').default,
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,18 @@ export default {
|
|||||||
meListsList: require('./screens/meListsList').default,
|
meListsList: require('./screens/meListsList').default,
|
||||||
meSettings: require('./screens/meSettings').default,
|
meSettings: require('./screens/meSettings').default,
|
||||||
meSettingsUpdateRemote: require('./screens/meSettingsUpdateRemote').default,
|
meSettingsUpdateRemote: require('./screens/meSettingsUpdateRemote').default,
|
||||||
|
meSwitch: require('./screens/meSwitch').default,
|
||||||
|
|
||||||
sharedAccount: require('./screens/sharedAccount').default,
|
sharedAccount: require('./screens/sharedAccount').default,
|
||||||
sharedToot: require('./screens/sharedToot').default,
|
|
||||||
sharedAnnouncements: require('./screens/sharedAnnouncements').default,
|
sharedAnnouncements: require('./screens/sharedAnnouncements').default,
|
||||||
|
sharedCompose: require('./screens/sharedCompose').default,
|
||||||
|
sharedRelationships: require('./screens/sharedRelationships').default,
|
||||||
|
sharedSearch: require('./screens/sharedSearch').default,
|
||||||
|
sharedToot: require('./screens/sharedToot').default,
|
||||||
|
|
||||||
relationship: require('./components/relationship').default,
|
componentInstance: require('./components/instance').default,
|
||||||
relativeTime: require('./components/relativeTime').default,
|
componentParse: require('./components/parse').default,
|
||||||
timeline: require('./components/timeline').default
|
componentRelationship: require('./components/relationship').default,
|
||||||
|
componentRelativeTime: require('./components/relativeTime').default,
|
||||||
|
componentTimeline: require('./components/timeline').default
|
||||||
}
|
}
|
||||||
|
33
src/i18n/zh-Hans/components/instance.ts
Normal file
33
src/i18n/zh-Hans/components/instance.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
export default {
|
||||||
|
server: {
|
||||||
|
textInput: { placeholder: '输入社区服务器地址' },
|
||||||
|
button: {
|
||||||
|
local: '登录',
|
||||||
|
remote: '围观'
|
||||||
|
},
|
||||||
|
information: {
|
||||||
|
name: '社区名称',
|
||||||
|
description: { heading: '社区简介', expandHint: '简介' },
|
||||||
|
accounts: '用户总数',
|
||||||
|
statuses: '嘟文总数',
|
||||||
|
domains: '连结总数'
|
||||||
|
},
|
||||||
|
disclaimer:
|
||||||
|
'登录过程将使用系统浏览器,您的账户登录信息tooot应用无法读取。详见:'
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
local: {
|
||||||
|
alert: {
|
||||||
|
title: '此社区已登录',
|
||||||
|
message: '您可以登录同个社区的另一个账户,不影响现有账户',
|
||||||
|
buttons: {
|
||||||
|
cancel: '$t(common:buttons.cancel)',
|
||||||
|
continue: '继续'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
remote: {
|
||||||
|
succeed: '围观登记成功'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
src/i18n/zh-Hans/components/parse.ts
Normal file
8
src/i18n/zh-Hans/components/parse.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export default {
|
||||||
|
HTML: {
|
||||||
|
expanded: {
|
||||||
|
true: '折叠{{hint}}',
|
||||||
|
false: '展开{{hint}}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,9 +5,17 @@ export default {
|
|||||||
button: '重试'
|
button: '重试'
|
||||||
},
|
},
|
||||||
success: {
|
success: {
|
||||||
message: '🈳️🈚️1一物'
|
message: '空无一物'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
end: {
|
||||||
|
message: '居然刷到底了,喝杯 <0 /> 吧'
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
explanation:
|
||||||
|
'围观的社区可能不属于已经登录的社区的已知连结,因此只可围观嘟文,不能进行操作。设置里可以切换想要围观的社区。',
|
||||||
|
button: '前往设置'
|
||||||
|
},
|
||||||
shared: {
|
shared: {
|
||||||
actioned: {
|
actioned: {
|
||||||
pinned: '置顶',
|
pinned: '置顶',
|
||||||
@ -22,17 +30,14 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
favourite: {
|
favourited: {
|
||||||
function: '喜欢嘟文'
|
function: '喜欢嘟文'
|
||||||
// button: '隐藏 {{acct}} 的嘟文'
|
|
||||||
},
|
},
|
||||||
reblog: {
|
reblogged: {
|
||||||
function: '转嘟'
|
function: '转嘟'
|
||||||
// button: '屏蔽 {{acct}}'
|
|
||||||
},
|
},
|
||||||
bookmark: {
|
bookmarked: {
|
||||||
function: '收藏嘟文'
|
function: '收藏嘟文'
|
||||||
// button: '举报 {{acct}}'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
attachment: {
|
attachment: {
|
||||||
@ -40,16 +45,13 @@ export default {
|
|||||||
button: '显示敏感内容'
|
button: '显示敏感内容'
|
||||||
},
|
},
|
||||||
unsupported: {
|
unsupported: {
|
||||||
text: '文件读取错误',
|
text: '附件读取错误',
|
||||||
button: '尝试远程链接'
|
button: '尝试远程链接'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
expandHint: '隐藏内容'
|
expandHint: '隐藏内容'
|
||||||
},
|
},
|
||||||
end: {
|
|
||||||
message: '居然刷到底了,喝杯 <0 /> 吧'
|
|
||||||
},
|
|
||||||
header: {
|
header: {
|
||||||
shared: {
|
shared: {
|
||||||
application: '发自于 {{application}}'
|
application: '发自于 {{application}}'
|
||||||
@ -59,7 +61,6 @@ export default {
|
|||||||
function: '删除私信'
|
function: '删除私信'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
default: {
|
|
||||||
actions: {
|
actions: {
|
||||||
account: {
|
account: {
|
||||||
heading: '关于用户',
|
heading: '关于用户',
|
||||||
@ -71,32 +72,48 @@ export default {
|
|||||||
function: '屏蔽 @{{acct}}',
|
function: '屏蔽 @{{acct}}',
|
||||||
button: '屏蔽 @{{acct}}'
|
button: '屏蔽 @{{acct}}'
|
||||||
},
|
},
|
||||||
report: {
|
reports: {
|
||||||
function: '举报 @{{acct}}',
|
function: '举报 @{{acct}}',
|
||||||
button: '举报 @{{acct}}'
|
button: '举报 @{{acct}}'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
domain: {
|
domain: {
|
||||||
heading: '关于域名',
|
heading: '关于社区',
|
||||||
block: {
|
block: {
|
||||||
function: '屏蔽域名',
|
function: '屏蔽社区',
|
||||||
button: '屏蔽域名 {{domain}}'
|
button: '屏蔽社区 {{domain}}'
|
||||||
|
},
|
||||||
|
alert: {
|
||||||
|
title: '确定要屏蔽 {{domain}} 吗?',
|
||||||
|
message:
|
||||||
|
'多数情况下,隐藏或屏蔽特定用户即可。\n\n屏蔽之后,来自此社区的所有内容将不再出现在你的时间轴里。同时,来自该社区的关注者将被移除。请谨慎使用。',
|
||||||
|
buttons: {
|
||||||
|
confirm: '确定屏蔽整个社区',
|
||||||
|
cancel: '$t(common:buttons.cancel)'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
share: {
|
||||||
|
status: { heading: '分享嘟文', button: '分享此条嘟文的链接' },
|
||||||
|
account: { heading: '分享用户', button: '分享此用户的链接' }
|
||||||
|
},
|
||||||
status: {
|
status: {
|
||||||
heading: '关于嘟嘟',
|
heading: '关于嘟文',
|
||||||
delete: {
|
delete: {
|
||||||
function: '删除',
|
function: '删除',
|
||||||
button: '删除次条嘟文'
|
button: '删除此条嘟文'
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
function: '删除',
|
function: '删除',
|
||||||
button: '删除并重新编辑次条嘟文',
|
button: '删除并重新编辑此条嘟文',
|
||||||
alert: {
|
alert: {
|
||||||
title: '确认删除嘟嘟?',
|
title: '确认删除嘟文?',
|
||||||
message:
|
message:
|
||||||
'你确定要删除这条嘟文并重新编辑它吗?所有相关的转嘟和喜欢都会被清除,回复将会失去关联。',
|
'确定要删除这条嘟文并重新编辑它吗?所有相关的转嘟和喜欢都会被清除,回复将会失去关联。',
|
||||||
confirm: '删除并重新编辑'
|
buttons: {
|
||||||
|
confirm: '删除并重新编辑',
|
||||||
|
cancel: '$t(common:buttons.cancel)'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mute: {
|
mute: {
|
||||||
@ -115,7 +132,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
poll: {
|
poll: {
|
||||||
meta: {
|
meta: {
|
||||||
|
@ -4,6 +4,5 @@ export default {
|
|||||||
left: '我的关注',
|
left: '我的关注',
|
||||||
right: '本站嘟嘟'
|
right: '本站嘟嘟'
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
content: {}
|
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
export default {
|
export default {
|
||||||
heading: '我的长毛象',
|
heading: '我的长毛象',
|
||||||
content: {
|
content: {
|
||||||
login: {
|
|
||||||
server: {
|
|
||||||
placeholder: '请输入服务器'
|
|
||||||
},
|
|
||||||
button: '登录'
|
|
||||||
},
|
|
||||||
collections: {
|
collections: {
|
||||||
conversations: '$t(meConversations:heading)',
|
conversations: '$t(meConversations:heading)',
|
||||||
bookmarks: '$t(meBookmarks:heading)',
|
bookmarks: '$t(meBookmarks:heading)',
|
||||||
favourites: '$t(meFavourites:heading)',
|
favourites: '$t(meFavourites:heading)',
|
||||||
lists: '$t(meLists:heading)',
|
lists: '$t(meLists:heading)',
|
||||||
announcements: '$t(sharedAnnouncements:heading)'
|
announcements: {
|
||||||
|
heading: '$t(sharedAnnouncements:heading)',
|
||||||
|
content: {
|
||||||
|
unread: '{{amount}} 条未读公告',
|
||||||
|
read: '无未读公告'
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
settings: '$t(meSettings:heading)',
|
settings: '$t(meSettings:heading)',
|
||||||
logout: {
|
logout: {
|
||||||
|
@ -19,24 +19,30 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
browser: {
|
browser: {
|
||||||
heading: '打开链接',
|
heading: '外部链接',
|
||||||
options: {
|
options: {
|
||||||
internal: '应用内',
|
internal: '应用内打开',
|
||||||
external: '系统浏览器',
|
external: '浏览器打开',
|
||||||
cancel: '$t(common:buttons.cancel)'
|
cancel: '$t(common:buttons.cancel)'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
remote: {
|
remote: {
|
||||||
heading: '$t(meSettingsUpdateRemote:heading)',
|
heading: '$t(meSettingsUpdateRemote:heading)',
|
||||||
description: '外站只能看不能玩'
|
description: '外站只能浏览不能玩'
|
||||||
},
|
},
|
||||||
cache: {
|
cache: {
|
||||||
heading: '清空缓存',
|
heading: '清空缓存',
|
||||||
empty: '暂无缓存'
|
empty: '暂无缓存'
|
||||||
},
|
},
|
||||||
|
support: {
|
||||||
|
heading: '赞助 tooot 开发'
|
||||||
|
},
|
||||||
|
review: {
|
||||||
|
heading: '给 tooot 打分'
|
||||||
|
},
|
||||||
analytics: {
|
analytics: {
|
||||||
heading: '帮助我们改进',
|
heading: '帮助我们改进',
|
||||||
description: '允许我们收集不与用户相关联的使用信息'
|
description: '收集不与用户相关联的使用信息'
|
||||||
},
|
},
|
||||||
version: '版本 v{{version}}'
|
version: '版本 v{{version}}'
|
||||||
}
|
}
|
||||||
|
@ -1,45 +1,3 @@
|
|||||||
export default {
|
export default {
|
||||||
heading: '外站链接',
|
heading: '外站社区'
|
||||||
content: {
|
|
||||||
language: {
|
|
||||||
heading: '切换语言',
|
|
||||||
options: {
|
|
||||||
zh: '简体中文',
|
|
||||||
en: 'English',
|
|
||||||
cancel: '$t(common:buttons.cancel)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
theme: {
|
|
||||||
heading: '应用外观',
|
|
||||||
options: {
|
|
||||||
auto: '跟随系统',
|
|
||||||
light: '浅色模式',
|
|
||||||
dark: '深色模式',
|
|
||||||
cancel: '$t(common:buttons.cancel)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
browser: {
|
|
||||||
heading: '打开链接',
|
|
||||||
options: {
|
|
||||||
internal: '应用内',
|
|
||||||
external: '系统浏览器',
|
|
||||||
cancel: '$t(common:buttons.cancel)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
remote: {
|
|
||||||
heading: '外站链接',
|
|
||||||
description: '外站只能看不能玩'
|
|
||||||
},
|
|
||||||
cache: {
|
|
||||||
heading: '清空缓存'
|
|
||||||
},
|
|
||||||
analytics: {
|
|
||||||
heading: '帮助我们改进',
|
|
||||||
description: '允许我们收集不与用户相关联的使用信息'
|
|
||||||
},
|
|
||||||
copyrights: {
|
|
||||||
heading: '版权信息'
|
|
||||||
},
|
|
||||||
version: '版本 v{{version}}'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
7
src/i18n/zh-Hans/screens/meSwitch.ts
Normal file
7
src/i18n/zh-Hans/screens/meSwitch.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default {
|
||||||
|
heading: '切换账号',
|
||||||
|
content: {
|
||||||
|
existing: '选择已有账号',
|
||||||
|
new: '登录新社区'
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,3 @@
|
|||||||
export default {
|
export default {
|
||||||
heading: '通知',
|
heading: '通知'
|
||||||
content: {}
|
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,5 @@ export default {
|
|||||||
left: '跨站关注',
|
left: '跨站关注',
|
||||||
right: '外站嘟嘟'
|
right: '外站嘟嘟'
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
content: {}
|
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,10 @@
|
|||||||
export default {
|
export default {
|
||||||
heading: {
|
|
||||||
loading: '加载中…',
|
|
||||||
error: '加载错误'
|
|
||||||
},
|
|
||||||
content: {
|
content: {
|
||||||
created_at: '加入时间:{{date}}',
|
created_at: '加入时间:{{date}}',
|
||||||
summary: {
|
summary: {
|
||||||
statuses_count: '{{count}} 条嘟文',
|
statuses_count: '{{count}} 条嘟文',
|
||||||
following_count: '关注 {{count}} 人',
|
following_count: '关注 {{count}} 人',
|
||||||
followers_count: '被 {{count}} 人关注'
|
followers_count: '被 {{count}} 人关注'
|
||||||
},
|
|
||||||
segments: {
|
|
||||||
left: '所有嘟嘟',
|
|
||||||
middle: '嘟嘟和回复',
|
|
||||||
right: '所有媒体'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
export default {
|
export default {
|
||||||
heading: '公告',
|
heading: '公告',
|
||||||
content: {}
|
content: {
|
||||||
|
published: '发布于 <0 />',
|
||||||
|
button: {
|
||||||
|
read: '已读',
|
||||||
|
unread: '标记已读'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
144
src/i18n/zh-Hans/screens/sharedCompose.ts
Normal file
144
src/i18n/zh-Hans/screens/sharedCompose.ts
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
export default {
|
||||||
|
heading: {
|
||||||
|
left: {
|
||||||
|
button: '退出编辑',
|
||||||
|
alert: {
|
||||||
|
title: '确认退出编辑?',
|
||||||
|
buttons: {
|
||||||
|
exit: '退出编辑',
|
||||||
|
continue: '继续编辑'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
button: {
|
||||||
|
default: '发嘟嘟',
|
||||||
|
conversation: '发私信',
|
||||||
|
reply: '发布回复',
|
||||||
|
edit: '发嘟嘟'
|
||||||
|
},
|
||||||
|
alert: {
|
||||||
|
title: '发布失败',
|
||||||
|
button: '返回重试'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
root: {
|
||||||
|
header: {
|
||||||
|
postingAs: '用 @{{acct}}@{{domain}} 发布',
|
||||||
|
spoilerInput: {
|
||||||
|
placeholder: '折叠部分的警告信息'
|
||||||
|
},
|
||||||
|
textInput: {
|
||||||
|
placeholder: '想说点什么'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
attachments: {
|
||||||
|
sensitive: '标记媒体为敏感内容'
|
||||||
|
},
|
||||||
|
poll: {
|
||||||
|
option: {
|
||||||
|
placeholder: {
|
||||||
|
single: '单选项',
|
||||||
|
multiple: '多选项'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
heading: '可选项',
|
||||||
|
options: {
|
||||||
|
single: '单选',
|
||||||
|
multiple: '多选',
|
||||||
|
cancel: '$t(common:buttons.cancel)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expiration: {
|
||||||
|
heading: '有效期',
|
||||||
|
options: {
|
||||||
|
'300': '5分钟',
|
||||||
|
'1800': '30分钟',
|
||||||
|
'3600': '1小时',
|
||||||
|
'21600': '6小时',
|
||||||
|
'86400': '1天',
|
||||||
|
'259200': '3天',
|
||||||
|
'604800': '7天',
|
||||||
|
cancel: '$t(common:buttons.cancel)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
attachment: {
|
||||||
|
actions: {
|
||||||
|
options: {
|
||||||
|
library: '从相册上传',
|
||||||
|
photo: '拍摄上传',
|
||||||
|
cancel: '$t(common:buttons.cancel)'
|
||||||
|
},
|
||||||
|
library: {
|
||||||
|
alert: {
|
||||||
|
title: '无读取权限',
|
||||||
|
message: '需要相片权限才能上传附件',
|
||||||
|
buttons: {
|
||||||
|
settings: '去系统设置',
|
||||||
|
cancel: '取消上传'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
photo: {
|
||||||
|
alert: {
|
||||||
|
title: '无拍照权限',
|
||||||
|
message: '需要相机权限才能上传附件',
|
||||||
|
buttons: {
|
||||||
|
settings: '去系统设置',
|
||||||
|
cancel: '取消上传'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
failed: {
|
||||||
|
alert: {
|
||||||
|
title: '上传失败',
|
||||||
|
button: '返回重试'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
visibility: {
|
||||||
|
title: '嘟文可见范围',
|
||||||
|
options: {
|
||||||
|
public: '公开',
|
||||||
|
unlisted: '不公开',
|
||||||
|
private: '仅关注者',
|
||||||
|
direct: '私信',
|
||||||
|
cancel: '$t(common:buttons.cancel)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editAttachment: {
|
||||||
|
header: {
|
||||||
|
left: '取消修改',
|
||||||
|
right: {
|
||||||
|
button: '应用修改',
|
||||||
|
succeed: {
|
||||||
|
title: '修改成功',
|
||||||
|
button: '好的'
|
||||||
|
},
|
||||||
|
failed: {
|
||||||
|
title: '修改失败',
|
||||||
|
button: '返回重试'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
altText: {
|
||||||
|
heading: '为附件添加文字说明',
|
||||||
|
placeholder:
|
||||||
|
'你可以为附件添加文字说明,以便更多人可以查看他们(包括视力障碍或视力受损人士)。\n\n优质的描述应该简洁明了,但要准确地描述照片中的内容,以便用户理解其含义。'
|
||||||
|
},
|
||||||
|
imageFocus: '在预览图上拖动圆圈,以选择缩略图的焦点'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
src/i18n/zh-Hans/screens/sharedRelationships.ts
Normal file
8
src/i18n/zh-Hans/screens/sharedRelationships.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export default {
|
||||||
|
heading: {
|
||||||
|
segments: {
|
||||||
|
left: '关注中',
|
||||||
|
right: '关注者'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
src/i18n/zh-Hans/screens/sharedSearch.ts
Normal file
32
src/i18n/zh-Hans/screens/sharedSearch.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
export default {
|
||||||
|
heading: '对话',
|
||||||
|
content: {
|
||||||
|
header: {
|
||||||
|
prefix: '搜索',
|
||||||
|
placeholder: '些什么'
|
||||||
|
},
|
||||||
|
empty: {
|
||||||
|
general:
|
||||||
|
'输入关键词搜索<bold>$t(sharedSearch:content.sections.accounts)</bold>、<bold>$t(sharedSearch:content.sections.hashtags)</bold>或者<bold>$t(sharedSearch:content.sections.statuses)</bold>',
|
||||||
|
advanced: {
|
||||||
|
header: '高级搜索格式',
|
||||||
|
example: {
|
||||||
|
account:
|
||||||
|
'$t(sharedSearch:content.header.prefix)$t(sharedSearch:content.sections.accounts)',
|
||||||
|
hashtag:
|
||||||
|
'$t(sharedSearch:content.header.prefix)$t(sharedSearch:content.sections.hashtags)',
|
||||||
|
statusLink:
|
||||||
|
'$t(sharedSearch:content.header.prefix)指定$t(sharedSearch:content.sections.statuses)',
|
||||||
|
accountLink:
|
||||||
|
'$t(sharedSearch:content.header.prefix)$t(sharedSearch:content.sections.accounts)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
accounts: '用户',
|
||||||
|
hashtags: '话题标签',
|
||||||
|
statuses: '嘟文'
|
||||||
|
},
|
||||||
|
notFound: '找不到 <bold>{{searchTerm}}</bold> 相关的 {{type}}'
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,3 @@
|
|||||||
export default {
|
export default {
|
||||||
heading: '对话',
|
heading: '对话'
|
||||||
content: {}
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import ScreenMeRoot from '@screens/Me/Root'
|
|||||||
import ScreenMeListsList from '@screens/Me/Root/Lists/List'
|
import ScreenMeListsList from '@screens/Me/Root/Lists/List'
|
||||||
import ScreenMeSettings from '@screens/Me/Settings'
|
import ScreenMeSettings from '@screens/Me/Settings'
|
||||||
import ScreenMeSwitch from '@screens/Me/Switch'
|
import ScreenMeSwitch from '@screens/Me/Switch'
|
||||||
import UpdateRemote from '@screens/Me/UpdateRemote'
|
import ScreenMeUpdateRemote from '@screens/Me/UpdateRemote'
|
||||||
import sharedScreens from '@screens/Shared/sharedScreens'
|
import sharedScreens from '@screens/Shared/sharedScreens'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -89,7 +89,9 @@ const ScreenMe: React.FC = () => {
|
|||||||
headerTitle: t('meListsList:heading', { list: route.params.title }),
|
headerTitle: t('meListsList:heading', { list: route.params.title }),
|
||||||
...(Platform.OS === 'android' && {
|
...(Platform.OS === 'android' && {
|
||||||
headerCenter: () => (
|
headerCenter: () => (
|
||||||
<HeaderCenter content={t('meListsList:heading')} />
|
<HeaderCenter
|
||||||
|
content={t('meListsList:heading', { list: route.params.title })}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||||
@ -110,7 +112,7 @@ const ScreenMe: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name='Screen-Me-Settings-UpdateRemote'
|
name='Screen-Me-Settings-UpdateRemote'
|
||||||
component={UpdateRemote}
|
component={ScreenMeUpdateRemote}
|
||||||
options={({ navigation }: any) => ({
|
options={({ navigation }: any) => ({
|
||||||
headerTitle: t('meSettingsUpdateRemote:heading'),
|
headerTitle: t('meSettingsUpdateRemote:heading'),
|
||||||
...(Platform.OS === 'android' && {
|
...(Platform.OS === 'android' && {
|
||||||
@ -127,17 +129,11 @@ const ScreenMe: React.FC = () => {
|
|||||||
options={({ navigation }: any) => ({
|
options={({ navigation }: any) => ({
|
||||||
stackPresentation: 'fullScreenModal',
|
stackPresentation: 'fullScreenModal',
|
||||||
headerShown: false,
|
headerShown: false,
|
||||||
headerTitle: t('meSettings:heading'),
|
|
||||||
...(Platform.OS === 'android' && {
|
|
||||||
headerCenter: () => (
|
|
||||||
<HeaderCenter content={t('meSettings:heading')} />
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{sharedScreens(Stack)}
|
{sharedScreens(Stack as any)}
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
|
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
import { useAnnouncementQuery } from '@utils/queryHooks/announcement'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
|
||||||
import { useAnnouncementQuery } from '@utils/queryHooks/announcement'
|
|
||||||
|
|
||||||
const Collections: React.FC = () => {
|
const Collections: React.FC = () => {
|
||||||
const { t } = useTranslation('meRoot')
|
const { t } = useTranslation('meRoot')
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
@ -15,9 +14,9 @@ const Collections: React.FC = () => {
|
|||||||
if (data) {
|
if (data) {
|
||||||
const amount = data.filter(announcement => !announcement.read).length
|
const amount = data.filter(announcement => !announcement.read).length
|
||||||
if (amount) {
|
if (amount) {
|
||||||
return `${amount} 条未读公告`
|
return t('content.collections.announcements.content.unread', { amount })
|
||||||
} else {
|
} else {
|
||||||
return '无未读公告'
|
return t('content.collections.announcements.content.read')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [data])
|
}, [data])
|
||||||
@ -51,7 +50,7 @@ const Collections: React.FC = () => {
|
|||||||
<MenuRow
|
<MenuRow
|
||||||
iconFront='Clipboard'
|
iconFront='Clipboard'
|
||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
title={t('content.collections.announcements')}
|
title={t('content.collections.announcements.heading')}
|
||||||
content={announcementContent}
|
content={announcementContent}
|
||||||
loading={isFetching}
|
loading={isFetching}
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
|
@ -116,10 +116,12 @@ const ScreenMeSettings: React.FC = () => {
|
|||||||
cancelButtonIndex: i18n.languages.length
|
cancelButtonIndex: i18n.languages.length
|
||||||
},
|
},
|
||||||
buttonIndex => {
|
buttonIndex => {
|
||||||
|
if (buttonIndex < i18n.languages.length) {
|
||||||
haptics('Success')
|
haptics('Success')
|
||||||
dispatch(changeLanguage(availableLanguages[buttonIndex]))
|
dispatch(changeLanguage(availableLanguages[buttonIndex]))
|
||||||
i18n.changeLanguage(availableLanguages[buttonIndex])
|
i18n.changeLanguage(availableLanguages[buttonIndex])
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -226,7 +228,7 @@ const ScreenMeSettings: React.FC = () => {
|
|||||||
onPress={() => Linking.openURL('https://www.patreon.com/xmflsct')}
|
onPress={() => Linking.openURL('https://www.patreon.com/xmflsct')}
|
||||||
/>
|
/>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
title={t('content.copyrights.heading')}
|
title={t('content.review.heading')}
|
||||||
content={
|
content={
|
||||||
<Icon
|
<Icon
|
||||||
name='Star'
|
name='Star'
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { HeaderCenter, HeaderLeft } from '@components/Header'
|
import { HeaderCenter, HeaderLeft } from '@components/Header'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Platform, StyleSheet } from 'react-native'
|
import { Platform, StyleSheet } from 'react-native'
|
||||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||||
import ScreenMeSwitchRoot from './Switch/Root'
|
import ScreenMeSwitchRoot from './Switch/Root'
|
||||||
@ -7,6 +8,7 @@ import ScreenMeSwitchRoot from './Switch/Root'
|
|||||||
const Stack = createNativeStackNavigator()
|
const Stack = createNativeStackNavigator()
|
||||||
|
|
||||||
const ScreenMeSwitch: React.FC = ({ navigation }) => {
|
const ScreenMeSwitch: React.FC = ({ navigation }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
|
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
|
||||||
@ -15,9 +17,9 @@ const ScreenMeSwitch: React.FC = ({ navigation }) => {
|
|||||||
name='Screen-Me-Switch-Root'
|
name='Screen-Me-Switch-Root'
|
||||||
component={ScreenMeSwitchRoot}
|
component={ScreenMeSwitchRoot}
|
||||||
options={{
|
options={{
|
||||||
headerTitle: '切换账号',
|
headerTitle: t('meSwitch:heading'),
|
||||||
...(Platform.OS === 'android' && {
|
...(Platform.OS === 'android' && {
|
||||||
headerCenter: () => <HeaderCenter content='切换账号' />
|
headerCenter: () => <HeaderCenter content={t('meSwitch:heading')} />
|
||||||
}),
|
}),
|
||||||
headerLeft: () => (
|
headerLeft: () => (
|
||||||
<HeaderLeft content='X' onPress={() => navigation.goBack()} />
|
<HeaderLeft content='X' onPress={() => navigation.goBack()} />
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
KeyboardAvoidingView,
|
KeyboardAvoidingView,
|
||||||
Platform,
|
Platform,
|
||||||
@ -59,7 +60,8 @@ const AccountButton: React.FC<Props> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ScreenMeSwitchRoot = () => {
|
const ScreenMeSwitchRoot: React.FC = () => {
|
||||||
|
const { t } = useTranslation('meSwitch')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const localInstances = useSelector(getLocalInstances)
|
const localInstances = useSelector(getLocalInstances)
|
||||||
const localActiveIndex = useSelector(getLocalActiveIndex)
|
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||||
@ -70,15 +72,11 @@ const ScreenMeSwitchRoot = () => {
|
|||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
>
|
>
|
||||||
<ScrollView keyboardShouldPersistTaps='handled'>
|
<ScrollView keyboardShouldPersistTaps='handled'>
|
||||||
<View style={styles.firstSection}>
|
<View
|
||||||
|
style={[styles.firstSection, { borderBottomColor: theme.border }]}
|
||||||
|
>
|
||||||
<Text style={[styles.header, { color: theme.primary }]}>
|
<Text style={[styles.header, { color: theme.primary }]}>
|
||||||
登录新的服务器
|
{t('content.existing')}
|
||||||
</Text>
|
|
||||||
<ComponentInstance type='local' disableHeaderImage goBack />
|
|
||||||
</View>
|
|
||||||
<View style={[styles.secondSection, { borderTopColor: theme.border }]}>
|
|
||||||
<Text style={[styles.header, { color: theme.primary }]}>
|
|
||||||
选择已有账号
|
|
||||||
</Text>
|
</Text>
|
||||||
<View style={styles.accountButtons}>
|
<View style={styles.accountButtons}>
|
||||||
{localInstances.length
|
{localInstances.length
|
||||||
@ -93,6 +91,13 @@ const ScreenMeSwitchRoot = () => {
|
|||||||
: null}
|
: null}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.secondSection}>
|
||||||
|
<Text style={[styles.header, { color: theme.primary }]}>
|
||||||
|
{t('content.new')}
|
||||||
|
</Text>
|
||||||
|
<ComponentInstance type='local' disableHeaderImage goBack />
|
||||||
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
)
|
)
|
||||||
@ -105,12 +110,13 @@ const styles = StyleSheet.create({
|
|||||||
paddingVertical: StyleConstants.Spacing.S
|
paddingVertical: StyleConstants.Spacing.S
|
||||||
},
|
},
|
||||||
firstSection: {
|
firstSection: {
|
||||||
marginTop: StyleConstants.Spacing.S
|
marginTop: StyleConstants.Spacing.S,
|
||||||
|
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||||
|
paddingBottom: StyleConstants.Spacing.S,
|
||||||
|
borderBottomWidth: StyleSheet.hairlineWidth
|
||||||
},
|
},
|
||||||
secondSection: {
|
secondSection: {
|
||||||
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
paddingTop: StyleConstants.Spacing.M
|
||||||
paddingTop: StyleConstants.Spacing.M,
|
|
||||||
borderTopWidth: StyleSheet.hairlineWidth
|
|
||||||
},
|
},
|
||||||
accountButtons: {
|
accountButtons: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
@ -3,7 +3,7 @@ import React from 'react'
|
|||||||
import { KeyboardAvoidingView, Platform } from 'react-native'
|
import { KeyboardAvoidingView, Platform } from 'react-native'
|
||||||
import { ScrollView } from 'react-native-gesture-handler'
|
import { ScrollView } from 'react-native-gesture-handler'
|
||||||
|
|
||||||
const UpdateRemote: React.FC = () => {
|
const ScreenMeUpdateRemote: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
@ -16,4 +16,4 @@ const UpdateRemote: React.FC = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UpdateRemote
|
export default ScreenMeUpdateRemote
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import BottomSheet from '@components/BottomSheet'
|
import BottomSheet from '@components/BottomSheet'
|
||||||
import { HeaderRight } from '@components/Header'
|
import { HeaderRight } from '@components/Header'
|
||||||
import Timeline from '@components/Timelines/Timeline'
|
import Timeline from '@components/Timelines/Timeline'
|
||||||
import HeaderActionsAccount from '@components/Timelines/Timeline/Shared/HeaderActions/ActionsAccount'
|
import HeaderActionsAccount from '@components/Timelines/Timeline/Shared/HeaderActions/Account'
|
||||||
|
import HeaderActionsShare from '@components/Timelines/Timeline/Shared/HeaderActions/Share'
|
||||||
import { useAccountQuery } from '@utils/queryHooks/account'
|
import { useAccountQuery } from '@utils/queryHooks/account'
|
||||||
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
@ -97,6 +98,12 @@ const ScreenSharedAccount: React.FC<SharedAccountProp> = ({
|
|||||||
setBottomSheetVisible={setBottomSheetVisible}
|
setBottomSheetVisible={setBottomSheetVisible}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<HeaderActionsShare
|
||||||
|
url={account.url}
|
||||||
|
type='account'
|
||||||
|
setBottomSheetVisible={setBottomSheetVisible}
|
||||||
|
/>
|
||||||
</BottomSheet>
|
</BottomSheet>
|
||||||
</AccountContext.Provider>
|
</AccountContext.Provider>
|
||||||
)
|
)
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const AccountInformationSwitch: React.FC = () => {
|
const AccountInformationSwitch: React.FC = () => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
type='text'
|
type='text'
|
||||||
content='切换账号'
|
content={t('meSwitch:heading')}
|
||||||
onPress={() => navigation.navigate('Screen-Me-Switch')}
|
onPress={() => navigation.navigate('Screen-Me-Switch')}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -92,5 +92,4 @@ const styles = StyleSheet.create({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// export default React.memo(AccountNav, (_, next) => next.account === undefined)
|
export default React.memo(AccountNav, (_, next) => next.account === undefined)
|
||||||
export default AccountNav
|
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
Dimensions,
|
Dimensions,
|
||||||
Image,
|
Image,
|
||||||
@ -33,7 +33,7 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
|
|||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const bottomTabBarHeight = useBottomTabBarHeight()
|
const bottomTabBarHeight = useBottomTabBarHeight()
|
||||||
const [index, setIndex] = useState(0)
|
const [index, setIndex] = useState(0)
|
||||||
const { t, i18n } = useTranslation()
|
const { t } = useTranslation('sharedAnnouncements')
|
||||||
|
|
||||||
const query = useAnnouncementQuery({
|
const query = useAnnouncementQuery({
|
||||||
showAll,
|
showAll,
|
||||||
@ -80,7 +80,10 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Text style={[styles.published, { color: theme.secondary }]}>
|
<Text style={[styles.published, { color: theme.secondary }]}>
|
||||||
发布于 <RelativeTime date={item.published_at} />
|
<Trans
|
||||||
|
i18nKey='sharedAnnouncements:content.published'
|
||||||
|
components={[<RelativeTime date={item.published_at} />]}
|
||||||
|
/>
|
||||||
</Text>
|
</Text>
|
||||||
<ScrollView style={styles.scrollView} showsVerticalScrollIndicator>
|
<ScrollView style={styles.scrollView} showsVerticalScrollIndicator>
|
||||||
<ParseHTML
|
<ParseHTML
|
||||||
@ -145,7 +148,9 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
|
|||||||
) : null}
|
) : null}
|
||||||
<Button
|
<Button
|
||||||
type='text'
|
type='text'
|
||||||
content={item.read ? '已读' : '标记阅读'}
|
content={
|
||||||
|
item.read ? t('content.button.read') : t('content.button.unread')
|
||||||
|
}
|
||||||
loading={mutation.isLoading}
|
loading={mutation.isLoading}
|
||||||
disabled={item.read}
|
disabled={item.read}
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
|
@ -9,6 +9,7 @@ import { getLocalAccount } from '@utils/slices/instancesSlice'
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useEffect, useReducer, useState } from 'react'
|
import React, { useCallback, useEffect, useReducer, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Keyboard,
|
Keyboard,
|
||||||
@ -35,6 +36,7 @@ const Compose: React.FC<SharedComposeProp> = ({
|
|||||||
route: { params },
|
route: { params },
|
||||||
navigation
|
navigation
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation('sharedCompose')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
@ -114,17 +116,11 @@ const Compose: React.FC<SharedComposeProp> = ({
|
|||||||
(composeState.spoiler.active ? composeState.spoiler.count : 0) +
|
(composeState.spoiler.active ? composeState.spoiler.count : 0) +
|
||||||
composeState.text.count
|
composeState.text.count
|
||||||
|
|
||||||
const postButtonText = {
|
|
||||||
conversation: '回复私信',
|
|
||||||
reply: '发布回复',
|
|
||||||
edit: '发嘟嘟'
|
|
||||||
}
|
|
||||||
|
|
||||||
const headerLeft = useCallback(
|
const headerLeft = useCallback(
|
||||||
() => (
|
() => (
|
||||||
<HeaderLeft
|
<HeaderLeft
|
||||||
type='text'
|
type='text'
|
||||||
content='退出编辑'
|
content={t('heading.left.button')}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (
|
if (
|
||||||
totalTextCount === 0 &&
|
totalTextCount === 0 &&
|
||||||
@ -134,13 +130,16 @@ const Compose: React.FC<SharedComposeProp> = ({
|
|||||||
navigation.goBack()
|
navigation.goBack()
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
Alert.alert('确认取消编辑?', '', [
|
Alert.alert(t('heading.left.alert.title'), undefined, [
|
||||||
{
|
{
|
||||||
text: '退出编辑',
|
text: t('heading.left.alert.buttons.exit'),
|
||||||
style: 'destructive',
|
style: 'destructive',
|
||||||
onPress: () => navigation.goBack()
|
onPress: () => navigation.goBack()
|
||||||
},
|
},
|
||||||
{ text: '继续编辑', style: 'cancel' }
|
{
|
||||||
|
text: t('heading.left.alert.buttons.continue'),
|
||||||
|
style: 'cancel'
|
||||||
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -168,7 +167,11 @@ const Compose: React.FC<SharedComposeProp> = ({
|
|||||||
() => (
|
() => (
|
||||||
<HeaderRight
|
<HeaderRight
|
||||||
type='text'
|
type='text'
|
||||||
content={params?.type ? postButtonText[params.type] : '发嘟嘟'}
|
content={
|
||||||
|
params?.type
|
||||||
|
? t(`heading.right.button.${params.type}`)
|
||||||
|
: t('heading.right.button.default')
|
||||||
|
}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
composeDispatch({ type: 'posting', payload: true })
|
composeDispatch({ type: 'posting', payload: true })
|
||||||
|
|
||||||
@ -190,9 +193,9 @@ const Compose: React.FC<SharedComposeProp> = ({
|
|||||||
.catch(() => {
|
.catch(() => {
|
||||||
haptics('Error')
|
haptics('Error')
|
||||||
composeDispatch({ type: 'posting', payload: false })
|
composeDispatch({ type: 'posting', payload: false })
|
||||||
Alert.alert('发布失败', '', [
|
Alert.alert(t('heading.right.alert.title'), undefined, [
|
||||||
{
|
{
|
||||||
text: '返回重试'
|
text: t('heading.right.alert.button')
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
@ -8,6 +8,7 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useState
|
useState
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Alert, KeyboardAvoidingView, Platform } from 'react-native'
|
import { Alert, KeyboardAvoidingView, Platform } from 'react-native'
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||||
@ -32,6 +33,7 @@ const ComposeEditAttachment: React.FC<Props> = ({
|
|||||||
navigation
|
navigation
|
||||||
}) => {
|
}) => {
|
||||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
const { composeState, composeDispatch } = useContext(ComposeContext)
|
||||||
|
const { t } = useTranslation('sharedCompose')
|
||||||
const theAttachment = composeState.attachments.uploads[index].remote!
|
const theAttachment = composeState.attachments.uploads[index].remote!
|
||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
@ -71,7 +73,7 @@ const ComposeEditAttachment: React.FC<Props> = ({
|
|||||||
() => (
|
() => (
|
||||||
<HeaderLeft
|
<HeaderLeft
|
||||||
type='text'
|
type='text'
|
||||||
content='取消'
|
content={t('content.editAttachment.header.left')}
|
||||||
onPress={() => navigation.goBack()}
|
onPress={() => navigation.goBack()}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@ -81,7 +83,7 @@ const ComposeEditAttachment: React.FC<Props> = ({
|
|||||||
() => (
|
() => (
|
||||||
<HeaderRight
|
<HeaderRight
|
||||||
type='text'
|
type='text'
|
||||||
content='应用'
|
content={t('content.editAttachment.header.right.button')}
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (!altText && focus.current.x === 0 && focus.current.y === 0) {
|
if (!altText && focus.current.x === 0 && focus.current.y === 0) {
|
||||||
@ -105,24 +107,36 @@ const ComposeEditAttachment: React.FC<Props> = ({
|
|||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
haptics('Success')
|
haptics('Success')
|
||||||
Alert.alert('修改成功', '', [
|
Alert.alert(
|
||||||
|
t('content.editAttachment.header.right.succeed.title'),
|
||||||
|
undefined,
|
||||||
|
[
|
||||||
{
|
{
|
||||||
text: '好的',
|
text: t(
|
||||||
|
'content.editAttachment.header.right.succeed.button'
|
||||||
|
),
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
navigation.goBack()
|
navigation.goBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
]
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setIsSubmitting(false)
|
setIsSubmitting(false)
|
||||||
haptics('Error')
|
haptics('Error')
|
||||||
Alert.alert('修改失败', '', [
|
Alert.alert(
|
||||||
|
t('content.editAttachment.header.right.failed.title'),
|
||||||
|
undefined,
|
||||||
|
[
|
||||||
{
|
{
|
||||||
text: '返回重试',
|
text: t(
|
||||||
|
'content.editAttachment.header.right.failed.button'
|
||||||
|
),
|
||||||
style: 'cancel'
|
style: 'cancel'
|
||||||
}
|
}
|
||||||
])
|
]
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { MutableRefObject, useContext } from 'react'
|
import React, { MutableRefObject, useContext } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Dimensions, Image, StyleSheet, Text, View } from 'react-native'
|
import { Dimensions, Image, StyleSheet, Text, View } from 'react-native'
|
||||||
import { PanGestureHandler } from 'react-native-gesture-handler'
|
import { PanGestureHandler } from 'react-native-gesture-handler'
|
||||||
import Animated, {
|
import Animated, {
|
||||||
@ -23,6 +24,7 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ComposeEditAttachmentImage: React.FC<Props> = ({ index, focus }) => {
|
const ComposeEditAttachmentImage: React.FC<Props> = ({ index, focus }) => {
|
||||||
|
const { t } = useTranslation('sharedCompose')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
const { composeState } = useContext(ComposeContext)
|
const { composeState } = useContext(ComposeContext)
|
||||||
@ -149,7 +151,7 @@ const ComposeEditAttachmentImage: React.FC<Props> = ({ index, focus }) => {
|
|||||||
</PanGestureHandler>
|
</PanGestureHandler>
|
||||||
</View>
|
</View>
|
||||||
<Text style={[styles.imageFocusText, { color: theme.primary }]}>
|
<Text style={[styles.imageFocusText, { color: theme.primary }]}>
|
||||||
在预览图上拖动圆圈,以选择缩略图的焦点。
|
{t('content.editAttachment.content.imageFocus')}
|
||||||
</Text>
|
</Text>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -8,6 +8,7 @@ import React, {
|
|||||||
useContext,
|
useContext,
|
||||||
useMemo
|
useMemo
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { ScrollView, StyleSheet, Text, TextInput, View } from 'react-native'
|
import { ScrollView, StyleSheet, Text, TextInput, View } from 'react-native'
|
||||||
import ComposeContext from '../utils/createContext'
|
import ComposeContext from '../utils/createContext'
|
||||||
import ComposeEditAttachmentImage from './Image'
|
import ComposeEditAttachmentImage from './Image'
|
||||||
@ -28,6 +29,7 @@ const ComposeEditAttachmentRoot: React.FC<Props> = ({
|
|||||||
altText,
|
altText,
|
||||||
setAltText
|
setAltText
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation('sharedCompose')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { composeState } = useContext(ComposeContext)
|
const { composeState } = useContext(ComposeContext)
|
||||||
const theAttachment = composeState.attachments.uploads[index].remote!
|
const theAttachment = composeState.attachments.uploads[index].remote!
|
||||||
@ -41,6 +43,8 @@ const ComposeEditAttachmentRoot: React.FC<Props> = ({
|
|||||||
const video = composeState.attachments.uploads[index]
|
const video = composeState.attachments.uploads[index]
|
||||||
return (
|
return (
|
||||||
<AttachmentVideo
|
<AttachmentVideo
|
||||||
|
total={1}
|
||||||
|
index={0}
|
||||||
sensitiveShown={false}
|
sensitiveShown={false}
|
||||||
video={
|
video={
|
||||||
video.local
|
video.local
|
||||||
@ -62,7 +66,7 @@ const ComposeEditAttachmentRoot: React.FC<Props> = ({
|
|||||||
{mediaDisplay}
|
{mediaDisplay}
|
||||||
<View style={styles.altTextContainer}>
|
<View style={styles.altTextContainer}>
|
||||||
<Text style={[styles.altTextInputHeading, { color: theme.primary }]}>
|
<Text style={[styles.altTextInputHeading, { color: theme.primary }]}>
|
||||||
为附件添加文字说明
|
{t('content.editAttachment.content.altText.heading')}
|
||||||
</Text>
|
</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[
|
style={[
|
||||||
@ -74,9 +78,7 @@ const ComposeEditAttachmentRoot: React.FC<Props> = ({
|
|||||||
maxLength={1500}
|
maxLength={1500}
|
||||||
multiline
|
multiline
|
||||||
onChangeText={e => setAltText(e)}
|
onChangeText={e => setAltText(e)}
|
||||||
placeholder={
|
placeholder={t('content.editAttachment.content.altText.placeholder')}
|
||||||
'你可以为附件添加文字说明,以便更多人可以查看他们(包括视力障碍或视力受损人士)。\n\n优质的描述应该简洁明了,但要准确地描述照片中的内容,以便用户理解其含义。'
|
|
||||||
}
|
|
||||||
placeholderTextColor={theme.secondary}
|
placeholderTextColor={theme.secondary}
|
||||||
scrollEnabled
|
scrollEnabled
|
||||||
value={altText}
|
value={altText}
|
||||||
|
@ -7,7 +7,7 @@ import { forEach, groupBy, sortBy } from 'lodash'
|
|||||||
import React, { useCallback, useContext, useEffect, useMemo } from 'react'
|
import React, { useCallback, useContext, useEffect, useMemo } from 'react'
|
||||||
import { View, FlatList, StyleSheet } from 'react-native'
|
import { View, FlatList, StyleSheet } from 'react-native'
|
||||||
import { Chase } from 'react-native-animated-spinkit'
|
import { Chase } from 'react-native-animated-spinkit'
|
||||||
import ComposeActions from './Actions'
|
import ComposeActions from './Root/Actions'
|
||||||
import ComposePosting from './Posting'
|
import ComposePosting from './Posting'
|
||||||
import ComposeRootFooter from './Root/Footer'
|
import ComposeRootFooter from './Root/Footer'
|
||||||
import ComposeRootHeader from './Root/Header'
|
import ComposeRootHeader from './Root/Header'
|
||||||
|
@ -4,13 +4,15 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useContext, useMemo } from 'react'
|
import React, { useCallback, useContext, useMemo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Pressable, StyleSheet, View } from 'react-native'
|
import { Pressable, StyleSheet, View } from 'react-native'
|
||||||
import addAttachment from './/addAttachment'
|
import ComposeContext from '../utils/createContext'
|
||||||
import ComposeContext from './utils/createContext'
|
import addAttachment from './Footer/addAttachment'
|
||||||
|
|
||||||
const ComposeActions: React.FC = () => {
|
const ComposeActions: React.FC = () => {
|
||||||
const { showActionSheetWithOptions } = useActionSheet()
|
const { showActionSheetWithOptions } = useActionSheet()
|
||||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
const { composeState, composeDispatch } = useContext(ComposeContext)
|
||||||
|
const { t } = useTranslation('sharedCompose')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
const attachmentColor = useMemo(() => {
|
const attachmentColor = useMemo(() => {
|
||||||
@ -71,7 +73,14 @@ const ComposeActions: React.FC = () => {
|
|||||||
if (!composeState.visibilityLock) {
|
if (!composeState.visibilityLock) {
|
||||||
showActionSheetWithOptions(
|
showActionSheetWithOptions(
|
||||||
{
|
{
|
||||||
options: ['公开', '不公开', '仅关注着', '私信', '取消'],
|
title: t('content.root.actions.visibility.title'),
|
||||||
|
options: [
|
||||||
|
t('content.root.actions.visibility.options.public'),
|
||||||
|
t('content.root.actions.visibility.options.unlisted'),
|
||||||
|
t('content.root.actions.visibility.options.private'),
|
||||||
|
t('content.root.actions.visibility.options.direct'),
|
||||||
|
t('content.root.actions.visibility.options.cancel')
|
||||||
|
],
|
||||||
cancelButtonIndex: 4
|
cancelButtonIndex: 4
|
||||||
},
|
},
|
||||||
buttonIndex => {
|
buttonIndex => {
|
@ -1,8 +1,8 @@
|
|||||||
import React, { useContext } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import ComposeAttachments from '@screens/Shared/Compose/Attachments'
|
import ComposeAttachments from '@screens/Shared/Compose/Root/Footer/Attachments'
|
||||||
import ComposeEmojis from '@screens/Shared/Compose/Emojis'
|
import ComposeEmojis from '@screens/Shared/Compose/Root/Footer/Emojis'
|
||||||
import ComposePoll from '@screens/Shared/Compose/Poll'
|
import ComposePoll from '@screens/Shared/Compose/Root/Footer/Poll'
|
||||||
import ComposeReply from '@screens/Shared/Compose/Reply'
|
import ComposeReply from '@screens/Shared/Compose/Root/Footer/Reply'
|
||||||
import ComposeContext from '@screens/Shared/Compose//utils/createContext'
|
import ComposeContext from '@screens/Shared/Compose//utils/createContext'
|
||||||
|
|
||||||
const ComposeRootFooter: React.FC = () => {
|
const ComposeRootFooter: React.FC = () => {
|
||||||
|
@ -13,6 +13,7 @@ import React, {
|
|||||||
useMemo,
|
useMemo,
|
||||||
useRef
|
useRef
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
FlatList,
|
FlatList,
|
||||||
Image,
|
Image,
|
||||||
@ -22,15 +23,16 @@ import {
|
|||||||
View
|
View
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import { Chase } from 'react-native-animated-spinkit'
|
import { Chase } from 'react-native-animated-spinkit'
|
||||||
|
import ComposeContext from '../../utils/createContext'
|
||||||
|
import { ExtendedAttachment } from '../../utils/types'
|
||||||
import addAttachment from './addAttachment'
|
import addAttachment from './addAttachment'
|
||||||
import ComposeContext from './utils/createContext'
|
|
||||||
import { ExtendedAttachment } from './utils/types'
|
|
||||||
|
|
||||||
const DEFAULT_HEIGHT = 200
|
const DEFAULT_HEIGHT = 200
|
||||||
|
|
||||||
const ComposeAttachments: React.FC = () => {
|
const ComposeAttachments: React.FC = () => {
|
||||||
const { showActionSheetWithOptions } = useActionSheet()
|
const { showActionSheetWithOptions } = useActionSheet()
|
||||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
const { composeState, composeDispatch } = useContext(ComposeContext)
|
||||||
|
const { t } = useTranslation('sharedCompose')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
|
|
||||||
@ -234,7 +236,7 @@ const ComposeAttachments: React.FC = () => {
|
|||||||
color={theme.primary}
|
color={theme.primary}
|
||||||
/>
|
/>
|
||||||
<Text style={[styles.sensitiveText, { color: theme.primary }]}>
|
<Text style={[styles.sensitiveText, { color: theme.primary }]}>
|
||||||
标记媒体为敏感内容
|
{t('content.root.footer.attachments.sensitive')}
|
||||||
</Text>
|
</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<FlatList
|
<FlatList
|
@ -10,8 +10,8 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
View
|
View
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import ComposeContext from './utils/createContext'
|
import ComposeContext from '../../utils/createContext'
|
||||||
import updateText from './updateText'
|
import updateText from '../../updateText'
|
||||||
|
|
||||||
const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
|
const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
|
||||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
const { composeState, composeDispatch } = useContext(ComposeContext)
|
@ -5,8 +5,9 @@ import { useActionSheet } from '@expo/react-native-action-sheet'
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useContext, useEffect, useState } from 'react'
|
import React, { useContext, useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { StyleSheet, TextInput, View } from 'react-native'
|
import { StyleSheet, TextInput, View } from 'react-native'
|
||||||
import ComposeContext from './utils/createContext'
|
import ComposeContext from '../../utils/createContext'
|
||||||
|
|
||||||
const ComposePoll: React.FC = () => {
|
const ComposePoll: React.FC = () => {
|
||||||
const { showActionSheetWithOptions } = useActionSheet()
|
const { showActionSheetWithOptions } = useActionSheet()
|
||||||
@ -16,18 +17,9 @@ const ComposePoll: React.FC = () => {
|
|||||||
},
|
},
|
||||||
composeDispatch
|
composeDispatch
|
||||||
} = useContext(ComposeContext)
|
} = useContext(ComposeContext)
|
||||||
|
const { t } = useTranslation('sharedCompose')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
const expireMapping: { [key: string]: string } = {
|
|
||||||
'300': '5分钟',
|
|
||||||
'1800': '30分钟',
|
|
||||||
'3600': '1小时',
|
|
||||||
'21600': '6小时',
|
|
||||||
'86400': '1天',
|
|
||||||
'259200': '3天',
|
|
||||||
'604800': '7天'
|
|
||||||
}
|
|
||||||
|
|
||||||
const [firstRender, setFirstRender] = useState(true)
|
const [firstRender, setFirstRender] = useState(true)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFirstRender(false)
|
setFirstRender(false)
|
||||||
@ -63,7 +55,11 @@ const ComposePoll: React.FC = () => {
|
|||||||
color: hasConflict ? theme.red : theme.primary
|
color: hasConflict ? theme.red : theme.primary
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
placeholder={`选项`}
|
placeholder={
|
||||||
|
multiple
|
||||||
|
? t('content.root.footer.poll.option.placeholder.multiple')
|
||||||
|
: t('content.root.footer.poll.option.placeholder.single')
|
||||||
|
}
|
||||||
placeholderTextColor={theme.secondary}
|
placeholderTextColor={theme.secondary}
|
||||||
maxLength={50}
|
maxLength={50}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -110,12 +106,20 @@ const ComposePoll: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
title='可选项'
|
title={t('content.root.footer.poll.multiple.heading')}
|
||||||
content={multiple ? '多选' : '单选'}
|
content={
|
||||||
|
multiple
|
||||||
|
? t('content.root.footer.poll.multiple.options.multiple')
|
||||||
|
: t('content.root.footer.poll.multiple.options.single')
|
||||||
|
}
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
showActionSheetWithOptions(
|
showActionSheetWithOptions(
|
||||||
{
|
{
|
||||||
options: ['单选', '多选', '取消'],
|
options: [
|
||||||
|
t('content.root.footer.poll.multiple.options.single'),
|
||||||
|
t('content.root.footer.poll.multiple.options.multiple'),
|
||||||
|
t('content.root.footer.poll.multiple.options.cancel')
|
||||||
|
],
|
||||||
cancelButtonIndex: 2
|
cancelButtonIndex: 2
|
||||||
},
|
},
|
||||||
index =>
|
index =>
|
||||||
@ -129,22 +133,36 @@ const ComposePoll: React.FC = () => {
|
|||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
/>
|
/>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
title='有效期'
|
title={t('content.root.footer.poll.expiration.heading')}
|
||||||
content={expireMapping[expire]}
|
content={t(`content.root.footer.poll.expiration.options.${expire}`)}
|
||||||
onPress={() =>
|
onPress={() => {
|
||||||
|
const expirations: [
|
||||||
|
'300',
|
||||||
|
'1800',
|
||||||
|
'3600',
|
||||||
|
'21600',
|
||||||
|
'86400',
|
||||||
|
'259200',
|
||||||
|
'604800'
|
||||||
|
] = ['300', '1800', '3600', '21600', '86400', '259200', '604800']
|
||||||
showActionSheetWithOptions(
|
showActionSheetWithOptions(
|
||||||
{
|
{
|
||||||
options: [...Object.values(expireMapping), '取消'],
|
options: [
|
||||||
|
...expirations.map(e =>
|
||||||
|
t(`content.root.footer.poll.expiration.options.${e}`)
|
||||||
|
),
|
||||||
|
t('content.root.footer.poll.expiration.options.cancel')
|
||||||
|
],
|
||||||
cancelButtonIndex: 7
|
cancelButtonIndex: 7
|
||||||
},
|
},
|
||||||
index =>
|
index =>
|
||||||
index < 7 &&
|
index < 7 &&
|
||||||
composeDispatch({
|
composeDispatch({
|
||||||
type: 'poll',
|
type: 'poll',
|
||||||
payload: { expire: Object.keys(expireMapping)[index] }
|
payload: { expire: expirations[index] }
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}}
|
||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
@ -1,5 +1,5 @@
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import ComposeContext from './utils/createContext'
|
import ComposeContext from '../../utils/createContext'
|
||||||
import React, { useContext } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { StyleSheet, View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
import TimelineDefault from '@root/components/Timelines/Timeline/Default'
|
import TimelineDefault from '@root/components/Timelines/Timeline/Default'
|
||||||
@ -12,12 +12,7 @@ const ComposeReply: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.base, { borderTopColor: theme.border }]}>
|
<View style={[styles.base, { borderTopColor: theme.border }]}>
|
||||||
<TimelineDefault
|
<TimelineDefault item={replyToStatus!} disableDetails disableOnPress />
|
||||||
item={replyToStatus!}
|
|
||||||
index={0}
|
|
||||||
disableDetails
|
|
||||||
disableOnPress
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -5,8 +5,9 @@ import { ImageInfo } from 'expo-image-picker/build/ImagePicker.types'
|
|||||||
import * as VideoThumbnails from 'expo-video-thumbnails'
|
import * as VideoThumbnails from 'expo-video-thumbnails'
|
||||||
import { Dispatch } from 'react'
|
import { Dispatch } from 'react'
|
||||||
import { Alert, Linking } from 'react-native'
|
import { Alert, Linking } from 'react-native'
|
||||||
import { ComposeAction } from './utils/types'
|
import { ComposeAction } from '../../utils/types'
|
||||||
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
||||||
|
import i18next from 'i18next'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
composeDispatch: Dispatch<ComposeAction>
|
composeDispatch: Dispatch<ComposeAction>
|
||||||
@ -75,6 +76,27 @@ const addAttachment = async ({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uploadFailed = () => {
|
||||||
|
composeDispatch({
|
||||||
|
type: 'attachment/upload/fail',
|
||||||
|
payload: hash
|
||||||
|
})
|
||||||
|
Alert.alert(
|
||||||
|
i18next.t(
|
||||||
|
'sharedCompose:content.root.actions.attachment.failed.alert.title'
|
||||||
|
),
|
||||||
|
undefined,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: i18next.t(
|
||||||
|
'sharedCompose:content.root.actions.attachment.failed.alert.button'
|
||||||
|
),
|
||||||
|
onPress: () => {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', {
|
formData.append('file', {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -96,35 +118,27 @@ const addAttachment = async ({
|
|||||||
payload: { remote: res, local: result }
|
payload: { remote: res, local: result }
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
composeDispatch({
|
uploadFailed()
|
||||||
type: 'attachment/upload/fail',
|
|
||||||
payload: hash
|
|
||||||
})
|
|
||||||
Alert.alert('上传失败', '', [
|
|
||||||
{
|
|
||||||
text: '返回重试',
|
|
||||||
onPress: () => {}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
composeDispatch({
|
uploadFailed()
|
||||||
type: 'attachment/upload/fail',
|
|
||||||
payload: hash
|
|
||||||
})
|
|
||||||
Alert.alert('上传失败', '', [
|
|
||||||
{
|
|
||||||
text: '返回重试',
|
|
||||||
onPress: () => {}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
showActionSheetWithOptions(
|
showActionSheetWithOptions(
|
||||||
{
|
{
|
||||||
options: ['从相册选取', '现照', '取消'],
|
options: [
|
||||||
|
i18next.t(
|
||||||
|
'sharedCompose:content.root.actions.attachment.actions.options.library'
|
||||||
|
),
|
||||||
|
i18next.t(
|
||||||
|
'sharedCompose:content.root.actions.attachment.actions.options.photo'
|
||||||
|
),
|
||||||
|
i18next.t(
|
||||||
|
'sharedCompose:content.root.actions.attachment.actions.options.cancel'
|
||||||
|
)
|
||||||
|
],
|
||||||
cancelButtonIndex: 2
|
cancelButtonIndex: 2
|
||||||
},
|
},
|
||||||
async buttonIndex => {
|
async buttonIndex => {
|
||||||
@ -133,18 +147,30 @@ const addAttachment = async ({
|
|||||||
status
|
status
|
||||||
} = await ImagePicker.requestMediaLibraryPermissionsAsync()
|
} = await ImagePicker.requestMediaLibraryPermissionsAsync()
|
||||||
if (status !== 'granted') {
|
if (status !== 'granted') {
|
||||||
Alert.alert('🈚️读取权限', '需要相片权限才能上传照片', [
|
Alert.alert(
|
||||||
|
i18next.t(
|
||||||
|
'sharedCompose:content.root.actions.attachment.actions.library.alert.title'
|
||||||
|
),
|
||||||
|
i18next.t(
|
||||||
|
'sharedCompose:content.root.actions.attachment.actions.library.alert.message'
|
||||||
|
),
|
||||||
|
[
|
||||||
{
|
{
|
||||||
text: '取消',
|
text: i18next.t(
|
||||||
|
'sharedCompose:content.root.actions.attachment.actions.library.alert.buttons.cancel'
|
||||||
|
),
|
||||||
style: 'cancel',
|
style: 'cancel',
|
||||||
onPress: () => {}
|
onPress: () => {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: '去系统设置',
|
text: i18next.t(
|
||||||
|
'sharedCompose:content.root.actions.attachment.actions.library.alert.buttons.settings'
|
||||||
|
),
|
||||||
style: 'default',
|
style: 'default',
|
||||||
onPress: () => Linking.openURL('app-settings:')
|
onPress: () => Linking.openURL('app-settings:')
|
||||||
}
|
}
|
||||||
])
|
]
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
const result = await ImagePicker.launchImageLibraryAsync({
|
const result = await ImagePicker.launchImageLibraryAsync({
|
||||||
mediaTypes: ImagePicker.MediaTypeOptions.All,
|
mediaTypes: ImagePicker.MediaTypeOptions.All,
|
||||||
@ -158,18 +184,30 @@ const addAttachment = async ({
|
|||||||
} else if (buttonIndex === 1) {
|
} else if (buttonIndex === 1) {
|
||||||
const { status } = await ImagePicker.requestCameraPermissionsAsync()
|
const { status } = await ImagePicker.requestCameraPermissionsAsync()
|
||||||
if (status !== 'granted') {
|
if (status !== 'granted') {
|
||||||
Alert.alert('🈚️读取权限', '需要相机权限才能上传照片', [
|
Alert.alert(
|
||||||
|
i18next.t(
|
||||||
|
'sharedCompose:content.root.actions.attachment.actions.photo.alert.title'
|
||||||
|
),
|
||||||
|
i18next.t(
|
||||||
|
'sharedCompose:content.root.actions.attachment.actions.photo.alert.message'
|
||||||
|
),
|
||||||
|
[
|
||||||
{
|
{
|
||||||
text: '取消',
|
text: i18next.t(
|
||||||
|
'sharedCompose:content.root.actions.attachment.actions.photo.alert.buttons.cancel'
|
||||||
|
),
|
||||||
style: 'cancel',
|
style: 'cancel',
|
||||||
onPress: () => {}
|
onPress: () => {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: '去系统设置',
|
text: i18next.t(
|
||||||
|
'sharedCompose:content.root.actions.attachment.actions.photo.alert.buttons.settings'
|
||||||
|
),
|
||||||
style: 'default',
|
style: 'default',
|
||||||
onPress: () => Linking.openURL('app-settings:')
|
onPress: () => Linking.openURL('app-settings:')
|
||||||
}
|
}
|
||||||
])
|
]
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
const result = await ImagePicker.launchCameraAsync({
|
const result = await ImagePicker.launchCameraAsync({
|
||||||
mediaTypes: ImagePicker.MediaTypeOptions.All,
|
mediaTypes: ImagePicker.MediaTypeOptions.All,
|
@ -1,45 +1,15 @@
|
|||||||
import { useAccountQuery } from '@utils/queryHooks/account'
|
|
||||||
import {
|
import {
|
||||||
getLocalActiveIndex,
|
getLocalActiveIndex,
|
||||||
getLocalInstances,
|
getLocalInstances
|
||||||
InstanceLocal
|
|
||||||
} from '@utils/slices/instancesSlice'
|
} from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
|
||||||
import React, { useContext } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { StyleSheet, Text, View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
import { Chase } from 'react-native-animated-spinkit'
|
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import ComposeSpoilerInput from '../SpoilerInput'
|
|
||||||
import ComposeTextInput from '../TextInput'
|
|
||||||
import ComposeContext from '../utils/createContext'
|
import ComposeContext from '../utils/createContext'
|
||||||
|
import PostingAs from './Header/PostingAs'
|
||||||
const PostingAs: React.FC<{
|
import ComposeSpoilerInput from './Header/SpoilerInput'
|
||||||
id: Mastodon.Account['id']
|
import ComposeTextInput from './Header/TextInput'
|
||||||
domain: InstanceLocal['url']
|
|
||||||
}> = ({ id, domain }) => {
|
|
||||||
const { theme } = useTheme()
|
|
||||||
|
|
||||||
const { data, status } = useAccountQuery({ id })
|
|
||||||
|
|
||||||
switch (status) {
|
|
||||||
case 'loading':
|
|
||||||
return (
|
|
||||||
<Chase
|
|
||||||
size={StyleConstants.Font.LineHeight.M - 2}
|
|
||||||
color={theme.secondary}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
case 'success':
|
|
||||||
return (
|
|
||||||
<Text style={[styles.postingAsText, { color: theme.secondary }]}>
|
|
||||||
用 @{data?.acct}@{domain} 发布
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
default:
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ComposeRootHeader: React.FC = () => {
|
const ComposeRootHeader: React.FC = () => {
|
||||||
const { composeState } = useContext(ComposeContext)
|
const { composeState } = useContext(ComposeContext)
|
||||||
@ -68,9 +38,6 @@ const styles = StyleSheet.create({
|
|||||||
postingAs: {
|
postingAs: {
|
||||||
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||||
marginTop: StyleConstants.Spacing.S
|
marginTop: StyleConstants.Spacing.S
|
||||||
},
|
|
||||||
postingAsText: {
|
|
||||||
...StyleConstants.FontStyle.S
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
44
src/screens/Shared/Compose/Root/Header/PostingAs.tsx
Normal file
44
src/screens/Shared/Compose/Root/Header/PostingAs.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { useAccountQuery } from '@utils/queryHooks/account'
|
||||||
|
import { InstanceLocal } from '@utils/slices/instancesSlice'
|
||||||
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { StyleSheet, Text } from 'react-native'
|
||||||
|
import { Chase } from 'react-native-animated-spinkit'
|
||||||
|
|
||||||
|
const ComposePostingAs: React.FC<{
|
||||||
|
id: Mastodon.Account['id']
|
||||||
|
domain: InstanceLocal['url']
|
||||||
|
}> = ({ id, domain }) => {
|
||||||
|
const { t } = useTranslation('sharedCompose')
|
||||||
|
const { theme } = useTheme()
|
||||||
|
|
||||||
|
const { data, status } = useAccountQuery({ id })
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case 'loading':
|
||||||
|
return (
|
||||||
|
<Chase
|
||||||
|
size={StyleConstants.Font.LineHeight.M - 2}
|
||||||
|
color={theme.secondary}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'success':
|
||||||
|
return (
|
||||||
|
<Text style={[styles.text, { color: theme.secondary }]}>
|
||||||
|
{t('content.root.header.postingAs', { acct: data?.acct, domain })}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
text: {
|
||||||
|
...StyleConstants.FontStyle.S
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default ComposePostingAs
|
@ -1,12 +1,14 @@
|
|||||||
import React, { useContext } from 'react'
|
|
||||||
import { StyleSheet, Text, TextInput } from 'react-native'
|
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import formatText from './formatText'
|
import React, { useContext } from 'react'
|
||||||
import ComposeContext from './utils/createContext'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { StyleSheet, Text, TextInput } from 'react-native'
|
||||||
|
import formatText from '../../formatText'
|
||||||
|
import ComposeContext from '../../utils/createContext'
|
||||||
|
|
||||||
const ComposeSpoilerInput: React.FC = () => {
|
const ComposeSpoilerInput: React.FC = () => {
|
||||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
const { composeState, composeDispatch } = useContext(ComposeContext)
|
||||||
|
const { t } = useTranslation('sharedCompose')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -23,7 +25,7 @@ const ComposeSpoilerInput: React.FC = () => {
|
|||||||
autoFocus
|
autoFocus
|
||||||
enablesReturnKeyAutomatically
|
enablesReturnKeyAutomatically
|
||||||
multiline
|
multiline
|
||||||
placeholder='折叠部分的警告信息'
|
placeholder={t('content.root.header.spoilerInput.placeholder')}
|
||||||
placeholderTextColor={theme.secondary}
|
placeholderTextColor={theme.secondary}
|
||||||
onChangeText={content =>
|
onChangeText={content =>
|
||||||
formatText({
|
formatText({
|
@ -1,12 +1,14 @@
|
|||||||
import React, { useContext } from 'react'
|
|
||||||
import { StyleSheet, Text, TextInput } from 'react-native'
|
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import formatText from './formatText'
|
import React, { useContext } from 'react'
|
||||||
import ComposeContext from './utils/createContext'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { StyleSheet, Text, TextInput } from 'react-native'
|
||||||
|
import formatText from '../../formatText'
|
||||||
|
import ComposeContext from '../../utils/createContext'
|
||||||
|
|
||||||
const ComposeTextInput: React.FC = () => {
|
const ComposeTextInput: React.FC = () => {
|
||||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
const { composeState, composeDispatch } = useContext(ComposeContext)
|
||||||
|
const { t } = useTranslation('sharedCompose')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -23,7 +25,7 @@ const ComposeTextInput: React.FC = () => {
|
|||||||
autoFocus
|
autoFocus
|
||||||
enablesReturnKeyAutomatically
|
enablesReturnKeyAutomatically
|
||||||
multiline
|
multiline
|
||||||
placeholder='想说点什么'
|
placeholder={t('content.root.header.textInput.placeholder')}
|
||||||
placeholderTextColor={theme.secondary}
|
placeholderTextColor={theme.secondary}
|
||||||
onChangeText={content =>
|
onChangeText={content =>
|
||||||
formatText({
|
formatText({
|
@ -1,6 +1,6 @@
|
|||||||
import ComponentAccount from '@components/Account'
|
import ComponentAccount from '@components/Account'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import ComponentHashtag from '@components/Timelines/Hashtag'
|
import ComponentHashtag from '@components/Hashtag'
|
||||||
import React, { Dispatch, useCallback } from 'react'
|
import React, { Dispatch, useCallback } from 'react'
|
||||||
import updateText from '../updateText'
|
import updateText from '../updateText'
|
||||||
import { ComposeAction, ComposeState } from '../utils/types'
|
import { ComposeAction, ComposeState } from '../utils/types'
|
||||||
|
10
src/screens/Shared/Compose/utils/types.d.ts
vendored
10
src/screens/Shared/Compose/utils/types.d.ts
vendored
@ -39,15 +39,7 @@ export type ComposeState = {
|
|||||||
'3': string | undefined
|
'3': string | undefined
|
||||||
}
|
}
|
||||||
multiple: boolean
|
multiple: boolean
|
||||||
expire:
|
expire: '300' | '1800' | '3600' | '21600' | '86400' | '259200' | '604800'
|
||||||
| '300'
|
|
||||||
| '1800'
|
|
||||||
| '3600'
|
|
||||||
| '21600'
|
|
||||||
| '86400'
|
|
||||||
| '259200'
|
|
||||||
| '604800'
|
|
||||||
| string
|
|
||||||
}
|
}
|
||||||
attachments: {
|
attachments: {
|
||||||
sensitive: boolean
|
sensitive: boolean
|
||||||
|
@ -2,6 +2,7 @@ import SegmentedControl from '@react-native-community/segmented-control'
|
|||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Dimensions, StyleSheet, View } from 'react-native'
|
import { Dimensions, StyleSheet, View } from 'react-native'
|
||||||
import { TabView } from 'react-native-tab-view'
|
import { TabView } from 'react-native-tab-view'
|
||||||
import RelationshipsList from './Relationships/List'
|
import RelationshipsList from './Relationships/List'
|
||||||
@ -12,6 +13,7 @@ const ScreenSharedRelationships: React.FC<SharedRelationshipsProp> = ({
|
|||||||
params: { account, initialType }
|
params: { account, initialType }
|
||||||
}
|
}
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation('sharedRelationships')
|
||||||
const { mode } = useTheme()
|
const { mode } = useTheme()
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
|
|
||||||
@ -23,7 +25,7 @@ const ScreenSharedRelationships: React.FC<SharedRelationshipsProp> = ({
|
|||||||
<View style={styles.segmentsContainer}>
|
<View style={styles.segmentsContainer}>
|
||||||
<SegmentedControl
|
<SegmentedControl
|
||||||
appearance={mode}
|
appearance={mode}
|
||||||
values={['关注中', '关注者']}
|
values={[t('heading.segments.left'), t('heading.segments.right')]}
|
||||||
selectedIndex={segment}
|
selectedIndex={segment}
|
||||||
onChange={({ nativeEvent }) =>
|
onChange={({ nativeEvent }) =>
|
||||||
setSegment(nativeEvent.selectedSegmentIndex)
|
setSegment(nativeEvent.selectedSegmentIndex)
|
||||||
|
@ -2,7 +2,7 @@ import ComponentAccount from '@components/Account'
|
|||||||
import ComponentSeparator from '@components/Separator'
|
import ComponentSeparator from '@components/Separator'
|
||||||
import TimelineEmpty from '@components/Timelines/Timeline/Empty'
|
import TimelineEmpty from '@components/Timelines/Timeline/Empty'
|
||||||
import TimelineEnd from '@components/Timelines/Timeline/End'
|
import TimelineEnd from '@components/Timelines/Timeline/End'
|
||||||
import { useScrollToTop } from '@react-navigation/native'
|
import { useNavigation, useScrollToTop } from '@react-navigation/native'
|
||||||
import { useRelationshipsQuery } from '@utils/queryHooks/relationships'
|
import { useRelationshipsQuery } from '@utils/queryHooks/relationships'
|
||||||
import React, { useCallback, useMemo, useRef } from 'react'
|
import React, { useCallback, useMemo, useRef } from 'react'
|
||||||
import { RefreshControl, StyleSheet } from 'react-native'
|
import { RefreshControl, StyleSheet } from 'react-native'
|
||||||
@ -14,6 +14,7 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const RelationshipsList: React.FC<Props> = ({ id, type }) => {
|
const RelationshipsList: React.FC<Props> = ({ id, type }) => {
|
||||||
|
const navigation = useNavigation()
|
||||||
const {
|
const {
|
||||||
status,
|
status,
|
||||||
data,
|
data,
|
||||||
@ -42,7 +43,14 @@ const RelationshipsList: React.FC<Props> = ({ id, type }) => {
|
|||||||
|
|
||||||
const keyExtractor = useCallback(({ id }) => id, [])
|
const keyExtractor = useCallback(({ id }) => id, [])
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
({ item }) => <ComponentAccount account={item} />,
|
({ item }) => (
|
||||||
|
<ComponentAccount
|
||||||
|
account={item}
|
||||||
|
onPress={() =>
|
||||||
|
navigation.push('Screen-Shared-Account', { account: item })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
const flItemEmptyComponent = useMemo(
|
const flItemEmptyComponent = useMemo(
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import ComponentHashtag from '@components/Timelines/Hashtag'
|
import ComponentAccount from '@components/Account'
|
||||||
|
import ComponentHashtag from '@components/Hashtag'
|
||||||
|
import ComponentSeparator from '@components/Separator'
|
||||||
|
import TimelineDefault from '@components/Timelines/Timeline/Default'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import ComponentAccount from '@root/components/Account'
|
|
||||||
import ComponentSeparator from '@root/components/Separator'
|
|
||||||
import TimelineDefault from '@root/components/Timelines/Timeline/Default'
|
|
||||||
import { useSearchQuery } from '@utils/queryHooks/search'
|
import { useSearchQuery } from '@utils/queryHooks/search'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
KeyboardAvoidingView,
|
KeyboardAvoidingView,
|
||||||
Platform,
|
Platform,
|
||||||
@ -22,6 +23,7 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ScreenSharedSearch: React.FC<Props> = ({ searchTerm }) => {
|
const ScreenSharedSearch: React.FC<Props> = ({ searchTerm }) => {
|
||||||
|
const { t } = useTranslation('sharedSearch')
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { status, data, refetch } = useSearchQuery({
|
const { status, data, refetch } = useSearchQuery({
|
||||||
@ -32,6 +34,11 @@ const ScreenSharedSearch: React.FC<Props> = ({ searchTerm }) => {
|
|||||||
const [setctionData, setSectionData] = useState<
|
const [setctionData, setSectionData] = useState<
|
||||||
{ title: string; data: any }[]
|
{ title: string; data: any }[]
|
||||||
>([])
|
>([])
|
||||||
|
const mapKeyToTranslations = {
|
||||||
|
accounts: t('content.sections.accounts'),
|
||||||
|
hashtags: t('content.sections.hashtags'),
|
||||||
|
statuses: t('content.sections.statuses')
|
||||||
|
}
|
||||||
useEffect(
|
useEffect(
|
||||||
() =>
|
() =>
|
||||||
data &&
|
data &&
|
||||||
@ -40,6 +47,8 @@ const ScreenSharedSearch: React.FC<Props> = ({ searchTerm }) => {
|
|||||||
.map(key => ({
|
.map(key => ({
|
||||||
title: key,
|
title: key,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
translation: mapKeyToTranslations[key],
|
||||||
|
// @ts-ignore
|
||||||
data: data[key]
|
data: data[key]
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
@ -63,8 +72,8 @@ const ScreenSharedSearch: React.FC<Props> = ({ searchTerm }) => {
|
|||||||
}
|
}
|
||||||
}, [searchTerm])
|
}, [searchTerm])
|
||||||
|
|
||||||
const listEmpty = useMemo(
|
const listEmpty = useMemo(() => {
|
||||||
() => (
|
return (
|
||||||
<View style={styles.emptyBase}>
|
<View style={styles.emptyBase}>
|
||||||
<View>
|
<View>
|
||||||
{status === 'loading' ? (
|
{status === 'loading' ? (
|
||||||
@ -83,67 +92,70 @@ const ScreenSharedSearch: React.FC<Props> = ({ searchTerm }) => {
|
|||||||
{ color: theme.primary }
|
{ color: theme.primary }
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
输入关键词搜索<Text style={styles.emptyFontBold}>用户</Text>、
|
<Trans
|
||||||
<Text style={styles.emptyFontBold}>话题标签</Text>或者
|
i18nKey='sharedSearch:content.empty.general'
|
||||||
<Text style={styles.emptyFontBold}>嘟文</Text>
|
components={{ bold: <Text style={styles.emptyFontBold} /> }}
|
||||||
|
/>
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[styles.emptyAdvanced, { color: theme.primary }]}>
|
<Text style={[styles.emptyAdvanced, { color: theme.primary }]}>
|
||||||
高级搜索格式
|
{t('content.empty.advanced.header')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[styles.emptyAdvanced, { color: theme.primary }]}>
|
<Text style={[styles.emptyAdvanced, { color: theme.primary }]}>
|
||||||
<Text style={{ color: theme.secondary }}>@username@domain</Text>
|
<Text style={{ color: theme.secondary }}>@username@domain</Text>
|
||||||
{' '}
|
{' '}
|
||||||
搜索用户
|
{t('content.empty.advanced.example.account')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[styles.emptyAdvanced, { color: theme.primary }]}>
|
<Text style={[styles.emptyAdvanced, { color: theme.primary }]}>
|
||||||
<Text style={{ color: theme.secondary }}>#example</Text>
|
<Text style={{ color: theme.secondary }}>#example</Text>
|
||||||
{' '}搜索话题标签
|
{' '}
|
||||||
|
{t('content.empty.advanced.example.hashtag')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[styles.emptyAdvanced, { color: theme.primary }]}>
|
<Text style={[styles.emptyAdvanced, { color: theme.primary }]}>
|
||||||
<Text style={{ color: theme.secondary }}>URL</Text>
|
<Text style={{ color: theme.secondary }}>URL</Text>
|
||||||
{' '}搜索指定嘟文
|
{' '}
|
||||||
|
{t('content.empty.advanced.example.statusLink')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[styles.emptyAdvanced, { color: theme.primary }]}>
|
<Text style={[styles.emptyAdvanced, { color: theme.primary }]}>
|
||||||
<Text style={{ color: theme.secondary }}>URL</Text>
|
<Text style={{ color: theme.secondary }}>URL</Text>
|
||||||
{' '}搜索指定用户
|
{' '}
|
||||||
|
{t('content.empty.advanced.example.accountLink')}
|
||||||
</Text>
|
</Text>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
),
|
|
||||||
[status]
|
|
||||||
)
|
)
|
||||||
|
}, [status])
|
||||||
const sectionHeader = useCallback(
|
const sectionHeader = useCallback(
|
||||||
({ section: { title } }) => (
|
({ section: { translation } }) => (
|
||||||
<View
|
<View
|
||||||
style={[styles.sectionHeader, { backgroundColor: theme.background }]}
|
style={[styles.sectionHeader, { backgroundColor: theme.background }]}
|
||||||
>
|
>
|
||||||
<Text style={[styles.sectionHeaderText, { color: theme.primary }]}>
|
<Text style={[styles.sectionHeaderText, { color: theme.primary }]}>
|
||||||
{title}
|
{translation}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
),
|
),
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
const sectionFooter = useCallback(
|
const sectionFooter = useCallback(
|
||||||
({ section: { data, title } }) =>
|
({ section: { data, translation } }) =>
|
||||||
!data.length ? (
|
!data.length ? (
|
||||||
<View
|
<View
|
||||||
style={[styles.sectionFooter, { backgroundColor: theme.background }]}
|
style={[styles.sectionFooter, { backgroundColor: theme.background }]}
|
||||||
>
|
>
|
||||||
<Text style={[styles.sectionFooterText, { color: theme.secondary }]}>
|
<Text style={[styles.sectionFooterText, { color: theme.secondary }]}>
|
||||||
找不到{' '}
|
<Trans
|
||||||
<Text style={{ fontWeight: StyleConstants.Font.Weight.Bold }}>
|
i18nKey='sharedSearch:content.notFound'
|
||||||
{searchTerm}
|
values={{ searchTerm, type: translation }}
|
||||||
</Text>{' '}
|
components={{ bold: <Text style={styles.emptyFontBold} /> }}
|
||||||
相关的{title}
|
/>
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
) : null,
|
) : null,
|
||||||
[searchTerm]
|
[searchTerm]
|
||||||
)
|
)
|
||||||
const listItem = useCallback(({ item, section, index }) => {
|
const listItem = useCallback(({ item, section }) => {
|
||||||
switch (section.title) {
|
switch (section.title) {
|
||||||
case 'accounts':
|
case 'accounts':
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { HeaderLeft } from '@components/Header'
|
import { HeaderCenter, HeaderLeft } from '@components/Header'
|
||||||
import { ParseEmojis } from '@components/Parse'
|
import { ParseEmojis } from '@components/Parse'
|
||||||
import { StackNavigationState, TypedNavigator } from '@react-navigation/native'
|
import { StackNavigationState, TypedNavigator } from '@react-navigation/native'
|
||||||
import { StackScreenProps } from '@react-navigation/stack'
|
import { StackScreenProps } from '@react-navigation/stack'
|
||||||
@ -15,7 +15,7 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
|||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { StyleSheet, Text, View } from 'react-native'
|
import { Platform, StyleSheet, Text, View } from 'react-native'
|
||||||
import { TextInput } from 'react-native-gesture-handler'
|
import { TextInput } from 'react-native-gesture-handler'
|
||||||
import { NativeStackNavigationOptions } from 'react-native-screens/lib/typescript'
|
import { NativeStackNavigationOptions } from 'react-native-screens/lib/typescript'
|
||||||
import {
|
import {
|
||||||
@ -211,7 +211,7 @@ const sharedScreens = (
|
|||||||
color: theme.primary
|
color: theme.primary
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
children='搜索'
|
children={t('sharedSearch:content.header.prefix')}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -233,7 +233,7 @@ const sharedScreens = (
|
|||||||
onSubmitEditing={({ nativeEvent: { text } }) =>
|
onSubmitEditing={({ nativeEvent: { text } }) =>
|
||||||
setSearchTerm(text)
|
setSearchTerm(text)
|
||||||
}
|
}
|
||||||
placeholder={'些什么'}
|
placeholder={t('sharedSearch:content.header.placeholder')}
|
||||||
placeholderTextColor={theme.secondary}
|
placeholderTextColor={theme.secondary}
|
||||||
returnKeyType='go'
|
returnKeyType='go'
|
||||||
/>
|
/>
|
||||||
@ -248,7 +248,10 @@ const sharedScreens = (
|
|||||||
name='Screen-Shared-Toot'
|
name='Screen-Shared-Toot'
|
||||||
component={ScreenSharedToot}
|
component={ScreenSharedToot}
|
||||||
options={({ navigation }: SharedTootProp) => ({
|
options={({ navigation }: SharedTootProp) => ({
|
||||||
title: t('sharedToot:heading'),
|
headerTitle: t('sharedToot:heading'),
|
||||||
|
...(Platform.OS === 'android' && {
|
||||||
|
headerCenter: () => <HeaderCenter content={t('sharedToot:heading')} />
|
||||||
|
}),
|
||||||
headerLeft: () => <HeaderLeft onPress={() => navigation.goBack()} />
|
headerLeft: () => <HeaderLeft onPress={() => navigation.goBack()} />
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
Reference in New Issue
Block a user