mirror of
				https://github.com/tooot-app/app
				synced 2025-06-05 22:19:13 +02:00 
			
		
		
		
	Updates
This commit is contained in:
		
										
											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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user