mirror of
https://github.com/tooot-app/app
synced 2025-04-24 23:18:47 +02:00
Updates
This commit is contained in:
parent
813f6b57c4
commit
f977fdfa8b
Binary file not shown.
Before Width: | Height: | Size: 1.4 KiB |
BIN
assets/icon.png
BIN
assets/icon.png
Binary file not shown.
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 33 KiB |
Binary file not shown.
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 69 KiB |
@ -154,7 +154,7 @@ const GracefullyImage: React.FC<Props> = ({
|
|||||||
children={children}
|
children={children}
|
||||||
style={[style, dimension && { ...dimension }]}
|
style={[style, dimension && { ...dimension }]}
|
||||||
{...(onPress
|
{...(onPress
|
||||||
? !imageVisible
|
? hidden
|
||||||
? { disabled: true }
|
? { disabled: true }
|
||||||
: { onPress }
|
: { onPress }
|
||||||
: { disabled: true })}
|
: { disabled: true })}
|
||||||
|
@ -244,6 +244,7 @@ const ComponentInstance: React.FC<Props> = ({
|
|||||||
{type === 'local' && appData ? (
|
{type === 'local' && appData ? (
|
||||||
<InstanceAuth
|
<InstanceAuth
|
||||||
instanceDomain={instanceDomain!}
|
instanceDomain={instanceDomain!}
|
||||||
|
instanceUri={instanceQuery.data!.uri}
|
||||||
appData={appData}
|
appData={appData}
|
||||||
goBack={goBack}
|
goBack={goBack}
|
||||||
/>
|
/>
|
||||||
|
@ -8,12 +8,14 @@ import { useDispatch } from 'react-redux'
|
|||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
instanceDomain: string
|
instanceDomain: string
|
||||||
|
// Domain can be different than uri
|
||||||
|
instanceUri: Mastodon.Instance['uri']
|
||||||
appData: InstanceLocal['appData']
|
appData: InstanceLocal['appData']
|
||||||
goBack?: boolean
|
goBack?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const InstanceAuth = React.memo(
|
const InstanceAuth = React.memo(
|
||||||
({ instanceDomain, appData, goBack }: Props) => {
|
({ instanceDomain, instanceUri, appData, goBack }: Props) => {
|
||||||
let redirectUri: string
|
let redirectUri: string
|
||||||
switch (Constants.manifest.releaseChannel) {
|
switch (Constants.manifest.releaseChannel) {
|
||||||
case 'production':
|
case 'production':
|
||||||
@ -70,6 +72,7 @@ const InstanceAuth = React.memo(
|
|||||||
localAddInstance({
|
localAddInstance({
|
||||||
url: instanceDomain,
|
url: instanceDomain,
|
||||||
token: accessToken,
|
token: accessToken,
|
||||||
|
uri: instanceUri,
|
||||||
appData
|
appData
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Image, StyleSheet, Text } from 'react-native'
|
import { StyleSheet, Text } from 'react-native'
|
||||||
|
import { Image } from 'react-native-expo-image-cache'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
|
||||||
@ -49,10 +50,7 @@ const ParseEmojis: React.FC<Props> = ({
|
|||||||
<Text key={i}>
|
<Text key={i}>
|
||||||
{/* When emoji starts a paragraph, lineHeight will break */}
|
{/* When emoji starts a paragraph, lineHeight will break */}
|
||||||
{i === 0 ? <Text> </Text> : null}
|
{i === 0 ? <Text> </Text> : null}
|
||||||
<Image
|
<Image uri={emojis[emojiIndex].url} style={[styles.image]} />
|
||||||
source={{ uri: emojis[emojiIndex].url }}
|
|
||||||
style={[styles.image]}
|
|
||||||
/>
|
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -68,7 +68,7 @@ const renderNode = ({
|
|||||||
mention => mention.url === href
|
mention => mention.url === href
|
||||||
)
|
)
|
||||||
const differentAccount = routeParams?.account
|
const differentAccount = routeParams?.account
|
||||||
? routeParams.account.id !== mentions[accountIndex].id
|
? routeParams.account.id !== mentions[accountIndex]?.id
|
||||||
: true
|
: true
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
|
@ -5,22 +5,20 @@ import TimeAgo from 'react-timeago'
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import buildFormatter from 'react-timeago/lib/formatters/buildFormatter'
|
import buildFormatter from 'react-timeago/lib/formatters/buildFormatter'
|
||||||
|
|
||||||
import zh from '@root/i18n/zh/components/relativeTime'
|
|
||||||
import en from '@root/i18n/en/components/relativeTime'
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
date: string
|
date: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const RelativeTime: React.FC<Props> = ({ date }) => {
|
const RelativeTime: React.FC<Props> = ({ date }) => {
|
||||||
const { i18n } = useTranslation()
|
const { t } = useTranslation('relativeTime')
|
||||||
const mapLanguageToTranslation: { [key: string]: Object } = {
|
|
||||||
'zh-CN': zh,
|
|
||||||
'en-US': en
|
|
||||||
}
|
|
||||||
const formatter = buildFormatter(mapLanguageToTranslation[i18n.language])
|
|
||||||
|
|
||||||
return <TimeAgo date={date} formatter={formatter} component={Text} />
|
return (
|
||||||
|
<TimeAgo
|
||||||
|
date={date}
|
||||||
|
component={Text}
|
||||||
|
formatter={buildFormatter(t('strings', { returnObjects: true }))}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RelativeTime
|
export default RelativeTime
|
||||||
|
@ -56,6 +56,7 @@ const Timeline: React.FC<Props> = ({
|
|||||||
refetch,
|
refetch,
|
||||||
isSuccess,
|
isSuccess,
|
||||||
isFetching,
|
isFetching,
|
||||||
|
isLoading,
|
||||||
hasPreviousPage,
|
hasPreviousPage,
|
||||||
fetchPreviousPage,
|
fetchPreviousPage,
|
||||||
isFetchingPreviousPage,
|
isFetchingPreviousPage,
|
||||||
@ -170,7 +171,8 @@ const Timeline: React.FC<Props> = ({
|
|||||||
<RefreshControl
|
<RefreshControl
|
||||||
{...(Platform.OS === 'android' && { enabled: true })}
|
{...(Platform.OS === 'android' && { enabled: true })}
|
||||||
refreshing={
|
refreshing={
|
||||||
isFetchingPreviousPage || (isFetching && !isFetchingNextPage)
|
isFetchingPreviousPage ||
|
||||||
|
(isFetching && !isFetchingNextPage && !isLoading)
|
||||||
}
|
}
|
||||||
onRefresh={() => {
|
onRefresh={() => {
|
||||||
if (hasPreviousPage) {
|
if (hasPreviousPage) {
|
||||||
@ -192,7 +194,13 @@ const Timeline: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
[hasPreviousPage, isFetchingPreviousPage, isFetching, isFetchingNextPage]
|
[
|
||||||
|
hasPreviousPage,
|
||||||
|
isFetchingPreviousPage,
|
||||||
|
isFetching,
|
||||||
|
isFetchingNextPage,
|
||||||
|
isLoading
|
||||||
|
]
|
||||||
)
|
)
|
||||||
const onScrollToIndexFailed = useCallback(error => {
|
const onScrollToIndexFailed = useCallback(error => {
|
||||||
const offset = error.averageItemLength * error.index
|
const offset = error.averageItemLength * error.index
|
||||||
@ -209,10 +217,10 @@ const Timeline: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
ref={flRef}
|
ref={flRef}
|
||||||
windowSize={11}
|
windowSize={8}
|
||||||
data={flattenData}
|
data={flattenData}
|
||||||
initialNumToRender={5}
|
initialNumToRender={3}
|
||||||
maxToRenderPerBatch={5}
|
maxToRenderPerBatch={3}
|
||||||
style={styles.flatList}
|
style={styles.flatList}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
onEndReached={onEndReached}
|
onEndReached={onEndReached}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
|
import { ParseEmojis } from '@components/Parse'
|
||||||
import RelativeTime from '@components/RelativeTime'
|
import RelativeTime from '@components/RelativeTime'
|
||||||
import { ParseEmojis } from '@root/components/Parse'
|
import { toast } from '@components/toast'
|
||||||
import { toast } from '@root/components/toast'
|
|
||||||
import {
|
import {
|
||||||
QueryKeyTimeline,
|
QueryKeyTimeline,
|
||||||
useTimelineMutation
|
useTimelineMutation
|
||||||
@ -32,7 +32,7 @@ const TimelinePoll: React.FC<Props> = ({
|
|||||||
sameAccount
|
sameAccount
|
||||||
}) => {
|
}) => {
|
||||||
const { mode, theme } = useTheme()
|
const { mode, theme } = useTheme()
|
||||||
const { t, i18n } = useTranslation('timeline')
|
const { t } = useTranslation('timeline')
|
||||||
|
|
||||||
const [allOptions, setAllOptions] = useState(
|
const [allOptions, setAllOptions] = useState(
|
||||||
new Array(poll.options.length).fill(false)
|
new Array(poll.options.length).fill(false)
|
||||||
@ -220,7 +220,7 @@ const TimelinePoll: React.FC<Props> = ({
|
|||||||
<Icon
|
<Icon
|
||||||
style={styles.optionSelection}
|
style={styles.optionSelection}
|
||||||
name={isSelected(index)}
|
name={isSelected(index)}
|
||||||
size={StyleConstants.Font.Size.L}
|
size={StyleConstants.Font.Size.M}
|
||||||
color={theme.primary}
|
color={theme.primary}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.optionText}>
|
<Text style={styles.optionText}>
|
||||||
@ -275,6 +275,7 @@ const styles = StyleSheet.create({
|
|||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
optionSelection: {
|
optionSelection: {
|
||||||
|
paddingTop: StyleConstants.Font.LineHeight.M - StyleConstants.Font.Size.M,
|
||||||
marginRight: StyleConstants.Spacing.S
|
marginRight: StyleConstants.Spacing.S
|
||||||
},
|
},
|
||||||
optionPercentage: {
|
optionPercentage: {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
export default {
|
export default {
|
||||||
common: require('./common').default
|
common: require('./common').default,
|
||||||
|
|
||||||
|
relativeTime: require('./components/relativeTime').default
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
const strings = {
|
export default {
|
||||||
prefixAgo: null,
|
strings: {
|
||||||
prefixFromNow: null,
|
prefixAgo: null,
|
||||||
suffixAgo: 'ago',
|
prefixFromNow: null,
|
||||||
suffixFromNow: 'from now',
|
suffixAgo: 'ago',
|
||||||
seconds: '%d seconds',
|
suffixFromNow: 'from now',
|
||||||
minute: 'about a minute',
|
seconds: '%d seconds',
|
||||||
minutes: '%d minutes',
|
minute: 'about a minute',
|
||||||
hour: 'about an hour',
|
minutes: '%d minutes',
|
||||||
hours: 'about %d hours',
|
hour: 'about an hour',
|
||||||
day: 'a day',
|
hours: 'about %d hours',
|
||||||
days: '%d days',
|
day: 'a day',
|
||||||
month: 'about a month',
|
days: '%d days',
|
||||||
months: '%d months',
|
month: 'about a month',
|
||||||
year: 'about a year',
|
months: '%d months',
|
||||||
years: '%d years',
|
year: 'about a year',
|
||||||
wordSeparator: ' '
|
years: '%d years',
|
||||||
|
wordSeparator: ' '
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default strings
|
|
||||||
|
@ -1,32 +1,20 @@
|
|||||||
|
import { store } from '@root/store'
|
||||||
|
import { getSettingsLanguage, supportedLngs } from '@utils/slices/settingsSlice'
|
||||||
import i18next from 'i18next'
|
import i18next from 'i18next'
|
||||||
import { initReactI18next } from 'react-i18next'
|
import { initReactI18next } from 'react-i18next'
|
||||||
import * as Localization from 'expo-localization'
|
|
||||||
|
|
||||||
import zh from '@root/i18n/zh/_all'
|
|
||||||
import en from '@root/i18n/en/_all'
|
import en from '@root/i18n/en/_all'
|
||||||
import {
|
import zh_Hans from '@root/i18n/zh-Hans/_all'
|
||||||
changeLanguage,
|
|
||||||
getSettingsLanguage
|
|
||||||
} from '@utils/slices/settingsSlice'
|
|
||||||
import { store } from '@root/store'
|
|
||||||
|
|
||||||
if (!getSettingsLanguage(store.getState())) {
|
|
||||||
const deviceLocal = Localization.locale
|
|
||||||
if (deviceLocal.startsWith('zh')) {
|
|
||||||
store.dispatch(changeLanguage('zh-CN'))
|
|
||||||
} else {
|
|
||||||
store.dispatch(changeLanguage('en-US'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i18next.use(initReactI18next).init({
|
i18next.use(initReactI18next).init({
|
||||||
lng: 'zh-CN',
|
lng: getSettingsLanguage(store.getState()),
|
||||||
fallbackLng: 'en-US',
|
fallbackLng: 'en',
|
||||||
supportedLngs: ['zh-CN', 'en-US'],
|
supportedLngs: supportedLngs,
|
||||||
|
|
||||||
ns: ['common'],
|
ns: ['common'],
|
||||||
defaultNS: 'common',
|
defaultNS: 'common',
|
||||||
|
|
||||||
resources: { 'zh-CN': zh, 'en-US': en },
|
resources: { 'zh-Hans': zh_Hans, en },
|
||||||
|
|
||||||
saveMissing: true,
|
saveMissing: true,
|
||||||
missingKeyHandler: (lng, ns, key, fallbackValue) => {
|
missingKeyHandler: (lng, ns, key, fallbackValue) => {
|
||||||
|
@ -21,5 +21,6 @@ export default {
|
|||||||
sharedAnnouncements: require('./screens/sharedAnnouncements').default,
|
sharedAnnouncements: require('./screens/sharedAnnouncements').default,
|
||||||
|
|
||||||
relationship: require('./components/relationship').default,
|
relationship: require('./components/relationship').default,
|
||||||
|
relativeTime: require('./components/relativeTime').default,
|
||||||
timeline: require('./components/timeline').default
|
timeline: require('./components/timeline').default
|
||||||
}
|
}
|
21
src/i18n/zh-Hans/components/relativeTime.ts
Normal file
21
src/i18n/zh-Hans/components/relativeTime.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export default {
|
||||||
|
strings: {
|
||||||
|
prefixAgo: null,
|
||||||
|
prefixFromNow: null,
|
||||||
|
suffixAgo: '之前',
|
||||||
|
suffixFromNow: '之后',
|
||||||
|
seconds: '%d秒',
|
||||||
|
minute: '1分钟',
|
||||||
|
minutes: '%d分钟',
|
||||||
|
hour: '1小时',
|
||||||
|
hours: '%d小时',
|
||||||
|
day: '1天',
|
||||||
|
days: '%d天',
|
||||||
|
month: '1个月',
|
||||||
|
months: '%d月',
|
||||||
|
year: '大约1年',
|
||||||
|
years: '%d年',
|
||||||
|
|
||||||
|
wordSeparator: ''
|
||||||
|
}
|
||||||
|
}
|
@ -4,8 +4,8 @@ export default {
|
|||||||
language: {
|
language: {
|
||||||
heading: '切换语言',
|
heading: '切换语言',
|
||||||
options: {
|
options: {
|
||||||
zh: '简体中文',
|
'en': 'English',
|
||||||
en: 'English',
|
'zh-Hans': '简体中文',
|
||||||
cancel: '$t(common:buttons.cancel)'
|
cancel: '$t(common:buttons.cancel)'
|
||||||
}
|
}
|
||||||
},
|
},
|
@ -1,21 +0,0 @@
|
|||||||
const strings = {
|
|
||||||
prefixAgo: null,
|
|
||||||
prefixFromNow: null,
|
|
||||||
suffixAgo: '之前',
|
|
||||||
suffixFromNow: '之后',
|
|
||||||
seconds: '%d秒',
|
|
||||||
minute: '1分钟',
|
|
||||||
minutes: '%d分钟',
|
|
||||||
hour: '1小时',
|
|
||||||
hours: '%d小时',
|
|
||||||
day: '1天',
|
|
||||||
days: '%d天',
|
|
||||||
month: '1个月',
|
|
||||||
months: '%d月',
|
|
||||||
year: '大约1年',
|
|
||||||
years: '%d年',
|
|
||||||
|
|
||||||
wordSeparator: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
export default strings
|
|
@ -98,33 +98,28 @@ const ScreenMeSettings: React.FC = () => {
|
|||||||
title={t('content.language.heading')}
|
title={t('content.language.heading')}
|
||||||
content={t(`content.language.options.${settingsLanguage}`)}
|
content={t(`content.language.options.${settingsLanguage}`)}
|
||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
onPress={() =>
|
onPress={() => {
|
||||||
|
const availableLanguages = Object.keys(
|
||||||
|
i18n.services.resourceStore.data
|
||||||
|
)
|
||||||
showActionSheetWithOptions(
|
showActionSheetWithOptions(
|
||||||
{
|
{
|
||||||
title: t('content.language.heading'),
|
title: t('content.language.heading'),
|
||||||
options: [
|
options: [
|
||||||
t('content.language.options.zh'),
|
...availableLanguages.map(language =>
|
||||||
t('content.language.options.en'),
|
t(`content.language.options.${language}`)
|
||||||
|
),
|
||||||
t('content.language.options.cancel')
|
t('content.language.options.cancel')
|
||||||
],
|
],
|
||||||
cancelButtonIndex: 2
|
cancelButtonIndex: i18n.languages.length
|
||||||
},
|
},
|
||||||
buttonIndex => {
|
buttonIndex => {
|
||||||
switch (buttonIndex) {
|
haptics('Success')
|
||||||
case 0:
|
dispatch(changeLanguage(availableLanguages[buttonIndex]))
|
||||||
haptics('Success')
|
i18n.changeLanguage(availableLanguages[buttonIndex])
|
||||||
dispatch(changeLanguage('zh-CN'))
|
|
||||||
i18n.changeLanguage('zh-CN')
|
|
||||||
break
|
|
||||||
case 1:
|
|
||||||
haptics('Success')
|
|
||||||
dispatch(changeLanguage('en-US'))
|
|
||||||
i18n.changeLanguage('en-US')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
title={t('content.theme.heading')}
|
title={t('content.theme.heading')}
|
||||||
@ -229,7 +224,7 @@ const ScreenMeSettings: React.FC = () => {
|
|||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
/>
|
/>
|
||||||
<Text style={[styles.version, { color: theme.secondary }]}>
|
<Text style={[styles.version, { color: theme.secondary }]}>
|
||||||
{t('content.version', { version: '1.0.0' })}
|
{t('content.version', { version: Constants.manifest.version })}
|
||||||
</Text>
|
</Text>
|
||||||
</MenuContainer>
|
</MenuContainer>
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ export interface Props {
|
|||||||
|
|
||||||
const AccountInformationCreated = forwardRef<ShimmerPlaceholder, Props>(
|
const AccountInformationCreated = forwardRef<ShimmerPlaceholder, Props>(
|
||||||
({ account }, ref) => {
|
({ account }, ref) => {
|
||||||
|
const { i18n } = useTranslation()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { t } = useTranslation('sharedAccount')
|
const { t } = useTranslation('sharedAccount')
|
||||||
const ShimmerPlaceholder = createShimmerPlaceholder(LinearGradient)
|
const ShimmerPlaceholder = createShimmerPlaceholder(LinearGradient)
|
||||||
@ -26,7 +27,11 @@ const AccountInformationCreated = forwardRef<ShimmerPlaceholder, Props>(
|
|||||||
width={StyleConstants.Font.Size.S * 8}
|
width={StyleConstants.Font.Size.S * 8}
|
||||||
height={StyleConstants.Font.LineHeight.S}
|
height={StyleConstants.Font.LineHeight.S}
|
||||||
style={{ marginBottom: StyleConstants.Spacing.M }}
|
style={{ marginBottom: StyleConstants.Spacing.M }}
|
||||||
shimmerColors={[theme.shimmerDefault, theme.shimmerHighlight, theme.shimmerDefault]}
|
shimmerColors={[
|
||||||
|
theme.shimmerDefault,
|
||||||
|
theme.shimmerHighlight,
|
||||||
|
theme.shimmerDefault
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<View style={styles.created}>
|
<View style={styles.created}>
|
||||||
<Icon
|
<Icon
|
||||||
@ -42,11 +47,14 @@ const AccountInformationCreated = forwardRef<ShimmerPlaceholder, Props>(
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('content.created_at', {
|
{t('content.created_at', {
|
||||||
date: new Date(account?.created_at!).toLocaleDateString('zh-CN', {
|
date: new Date(account?.created_at!).toLocaleDateString(
|
||||||
year: 'numeric',
|
i18n.language,
|
||||||
month: 'long',
|
{
|
||||||
day: 'numeric'
|
year: 'numeric',
|
||||||
})
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
}
|
||||||
|
)
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
@ -28,7 +28,7 @@ const AccountInformationFields = React.memo(
|
|||||||
size={'M'}
|
size={'M'}
|
||||||
emojis={account.emojis}
|
emojis={account.emojis}
|
||||||
showFullLink
|
showFullLink
|
||||||
numberOfLines={3}
|
numberOfLines={5}
|
||||||
/>
|
/>
|
||||||
{field.verified_at ? (
|
{field.verified_at ? (
|
||||||
<Icon
|
<Icon
|
||||||
@ -45,7 +45,7 @@ const AccountInformationFields = React.memo(
|
|||||||
size={'M'}
|
size={'M'}
|
||||||
emojis={account.emojis}
|
emojis={account.emojis}
|
||||||
showFullLink
|
showFullLink
|
||||||
numberOfLines={3}
|
numberOfLines={5}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
@ -123,19 +123,28 @@ const Compose: React.FC<SharedComposeProp> = ({
|
|||||||
<HeaderLeft
|
<HeaderLeft
|
||||||
type='text'
|
type='text'
|
||||||
content='退出编辑'
|
content='退出编辑'
|
||||||
onPress={() =>
|
onPress={() => {
|
||||||
Alert.alert('确认取消编辑?', '', [
|
if (
|
||||||
{
|
totalTextCount === 0 &&
|
||||||
text: '退出编辑',
|
composeState.attachments.uploads.length === 0 &&
|
||||||
style: 'destructive',
|
composeState.poll.active === false
|
||||||
onPress: () => navigation.goBack()
|
) {
|
||||||
},
|
navigation.goBack()
|
||||||
{ text: '继续编辑', style: 'cancel' }
|
return
|
||||||
])
|
} else {
|
||||||
}
|
Alert.alert('确认取消编辑?', '', [
|
||||||
|
{
|
||||||
|
text: '退出编辑',
|
||||||
|
style: 'destructive',
|
||||||
|
onPress: () => navigation.goBack()
|
||||||
|
},
|
||||||
|
{ text: '继续编辑', style: 'cancel' }
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
[]
|
[totalTextCount]
|
||||||
)
|
)
|
||||||
const headerCenter = useCallback(
|
const headerCenter = useCallback(
|
||||||
() => (
|
() => (
|
||||||
|
@ -1,17 +1,77 @@
|
|||||||
|
import { useAccountQuery } from '@utils/queryHooks/account'
|
||||||
|
import {
|
||||||
|
getLocalActiveIndex,
|
||||||
|
getLocalInstances,
|
||||||
|
InstanceLocal
|
||||||
|
} from '@utils/slices/instancesSlice'
|
||||||
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useContext } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import ComposeSpoilerInput from '@screens/Shared/Compose/SpoilerInput'
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
import ComposeTextInput from '@screens/Shared/Compose/TextInput'
|
import { Chase } from 'react-native-animated-spinkit'
|
||||||
import ComposeContext from '@screens/Shared/Compose//utils/createContext'
|
import { useSelector } from 'react-redux'
|
||||||
|
import ComposeSpoilerInput from '../SpoilerInput'
|
||||||
|
import ComposeTextInput from '../TextInput'
|
||||||
|
import ComposeContext from '../utils/createContext'
|
||||||
|
|
||||||
|
const PostingAs: React.FC<{
|
||||||
|
id: Mastodon.Account['id']
|
||||||
|
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)
|
||||||
|
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||||
|
const localInstances = useSelector(getLocalInstances)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{localActiveIndex !== null &&
|
||||||
|
localInstances.length &&
|
||||||
|
localInstances.length > 1 && (
|
||||||
|
<View style={styles.postingAs}>
|
||||||
|
<PostingAs
|
||||||
|
id={localInstances[localActiveIndex].account.id}
|
||||||
|
domain={localInstances[localActiveIndex].uri}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
{composeState.spoiler.active ? <ComposeSpoilerInput /> : null}
|
{composeState.spoiler.active ? <ComposeSpoilerInput /> : null}
|
||||||
<ComposeTextInput />
|
<ComposeTextInput />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
postingAs: {
|
||||||
|
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||||
|
marginTop: StyleConstants.Spacing.S
|
||||||
|
},
|
||||||
|
postingAsText: {
|
||||||
|
...StyleConstants.FontStyle.S
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default ComposeRootHeader
|
export default ComposeRootHeader
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
Image,
|
Image,
|
||||||
Platform,
|
Platform,
|
||||||
Share,
|
Share,
|
||||||
|
StatusBar,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
Text
|
Text
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
@ -57,20 +58,23 @@ const ScreenSharedImagesViewer: React.FC<SharedImagesViewerProp> = ({
|
|||||||
|
|
||||||
const component = useCallback(
|
const component = useCallback(
|
||||||
() => (
|
() => (
|
||||||
<ImageViewer
|
<>
|
||||||
index={initialIndex}
|
<StatusBar barStyle='light-content' />
|
||||||
imageUrls={imageUrls}
|
<ImageViewer
|
||||||
pageAnimateTime={250}
|
index={initialIndex}
|
||||||
enableSwipeDown={true}
|
imageUrls={imageUrls}
|
||||||
useNativeDriver={true}
|
pageAnimateTime={250}
|
||||||
swipeDownThreshold={100}
|
enableSwipeDown={true}
|
||||||
renderIndicator={() => <></>}
|
useNativeDriver={true}
|
||||||
saveToLocalByLongPress={false}
|
swipeDownThreshold={100}
|
||||||
onSwipeDown={() => navigation.goBack()}
|
renderIndicator={() => <></>}
|
||||||
style={{ flex: 1, marginBottom: 44 + safeAreaInsets.bottom }}
|
saveToLocalByLongPress={false}
|
||||||
onChange={index => index !== undefined && setCurrentIndex(index)}
|
onSwipeDown={() => navigation.goBack()}
|
||||||
renderImage={props => <TheImage {...props} imageUrls={imageUrls} />}
|
style={{ flex: 1, marginBottom: 44 + safeAreaInsets.bottom }}
|
||||||
/>
|
onChange={index => index !== undefined && setCurrentIndex(index)}
|
||||||
|
renderImage={props => <TheImage {...props} imageUrls={imageUrls} />}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
),
|
),
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
@ -85,7 +89,9 @@ const ScreenSharedImagesViewer: React.FC<SharedImagesViewerProp> = ({
|
|||||||
}, [currentIndex])
|
}, [currentIndex])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack.Navigator screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}>
|
<Stack.Navigator
|
||||||
|
screenOptions={{ headerHideShadow: true, headerTopInsetEnabled: false }}
|
||||||
|
>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name='Screen-Shared-ImagesViewer-Root'
|
name='Screen-Shared-ImagesViewer-Root'
|
||||||
component={component}
|
component={component}
|
||||||
|
@ -150,7 +150,7 @@ const ScreenSharedSearch: React.FC<Props> = ({ searchTerm }) => {
|
|||||||
<ComponentAccount
|
<ComponentAccount
|
||||||
account={item}
|
account={item}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
navigation.push('Screen-Shared-Account', { item })
|
navigation.push('Screen-Shared-Account', { account: item })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -201,16 +201,27 @@ const sharedScreens = (
|
|||||||
// https://github.com/react-navigation/react-navigation/issues/6746#issuecomment-583897436
|
// https://github.com/react-navigation/react-navigation/issues/6746#issuecomment-583897436
|
||||||
headerCenter: () => (
|
headerCenter: () => (
|
||||||
<View style={styles.searchBar}>
|
<View style={styles.searchBar}>
|
||||||
<Text
|
<TextInput
|
||||||
style={{ ...StyleConstants.FontStyle.M, color: theme.primary }}
|
editable={false}
|
||||||
>
|
children={
|
||||||
搜索
|
<Text
|
||||||
</Text>
|
style={[
|
||||||
|
styles.textInput,
|
||||||
|
{
|
||||||
|
color: theme.primary
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
children='搜索'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={[
|
style={[
|
||||||
styles.textInput,
|
styles.textInput,
|
||||||
{
|
{
|
||||||
color: theme.primary
|
flex: 1,
|
||||||
|
color: theme.primary,
|
||||||
|
paddingLeft: StyleConstants.Spacing.XS
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
autoFocus
|
autoFocus
|
||||||
@ -251,10 +262,7 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
},
|
},
|
||||||
textInput: {
|
textInput: {
|
||||||
...StyleConstants.FontStyle.M,
|
fontSize: StyleConstants.Font.Size.M
|
||||||
paddingLeft: StyleConstants.Spacing.XS,
|
|
||||||
marginBottom:
|
|
||||||
(StyleConstants.Font.LineHeight.M - StyleConstants.Font.Size.M) / 2
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import analytics from '@components/analytics'
|
|||||||
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
|
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||||
import { RootState } from '@root/store'
|
import { RootState } from '@root/store'
|
||||||
import * as AuthSession from 'expo-auth-session'
|
import * as AuthSession from 'expo-auth-session'
|
||||||
|
import * as Localization from 'expo-localization'
|
||||||
|
|
||||||
export type InstanceLocal = {
|
export type InstanceLocal = {
|
||||||
appData: {
|
appData: {
|
||||||
@ -11,6 +12,7 @@ export type InstanceLocal = {
|
|||||||
}
|
}
|
||||||
url: string
|
url: string
|
||||||
token: string
|
token: string
|
||||||
|
uri: Mastodon.Instance['uri']
|
||||||
account: {
|
account: {
|
||||||
id: Mastodon.Account['id']
|
id: Mastodon.Account['id']
|
||||||
preferences: Mastodon.Preferences
|
preferences: Mastodon.Preferences
|
||||||
@ -50,10 +52,12 @@ export const localAddInstance = createAsyncThunk(
|
|||||||
async ({
|
async ({
|
||||||
url,
|
url,
|
||||||
token,
|
token,
|
||||||
|
uri,
|
||||||
appData
|
appData
|
||||||
}: {
|
}: {
|
||||||
url: InstanceLocal['url']
|
url: InstanceLocal['url']
|
||||||
token: InstanceLocal['token']
|
token: InstanceLocal['token']
|
||||||
|
uri: Mastodon.Instance['uri']
|
||||||
appData: InstanceLocal['appData']
|
appData: InstanceLocal['appData']
|
||||||
}): Promise<{ type: 'add' | 'overwrite'; data: InstanceLocal }> => {
|
}): Promise<{ type: 'add' | 'overwrite'; data: InstanceLocal }> => {
|
||||||
const { store } = require('@root/store')
|
const { store } = require('@root/store')
|
||||||
@ -101,6 +105,7 @@ export const localAddInstance = createAsyncThunk(
|
|||||||
appData,
|
appData,
|
||||||
url,
|
url,
|
||||||
token,
|
token,
|
||||||
|
uri,
|
||||||
account: {
|
account: {
|
||||||
id,
|
id,
|
||||||
preferences
|
preferences
|
||||||
@ -159,7 +164,7 @@ export const instancesInitialState: InstancesState = {
|
|||||||
instances: []
|
instances: []
|
||||||
},
|
},
|
||||||
remote: {
|
remote: {
|
||||||
url: 'm.cmx.im'
|
url: Localization.locale.includes('zh') ? 'm.cmx.im' : 'mastodon.social'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
|
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||||
import { RootState } from '@root/store'
|
import { RootState } from '@root/store'
|
||||||
import * as Analytics from 'expo-firebase-analytics'
|
import * as Analytics from 'expo-firebase-analytics'
|
||||||
|
import * as Localization from 'expo-localization'
|
||||||
|
|
||||||
|
export const supportedLngs = ['zh-Hans', 'en']
|
||||||
|
|
||||||
export type SettingsState = {
|
export type SettingsState = {
|
||||||
language: 'zh-CN' | 'en-US' | undefined
|
language: 'zh-Hans' | 'en'
|
||||||
theme: 'light' | 'dark' | 'auto'
|
theme: 'light' | 'dark' | 'auto'
|
||||||
browser: 'internal' | 'external'
|
browser: 'internal' | 'external'
|
||||||
analytics: boolean
|
analytics: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const settingsInitialState = {
|
export const settingsInitialState = {
|
||||||
language: undefined,
|
language: Localization.locale,
|
||||||
theme: 'auto',
|
theme: 'auto',
|
||||||
browser: 'internal',
|
browser: 'internal',
|
||||||
analytics: true
|
analytics: true
|
||||||
|
Loading…
x
Reference in New Issue
Block a user