mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Merge branch 'main' into l10n_main
This commit is contained in:
42
src/App.tsx
42
src/App.tsx
@ -1,4 +1,31 @@
|
||||
import { ActionSheetProvider } from '@expo/react-native-action-sheet'
|
||||
import '@formatjs/intl-getcanonicallocales/polyfill'
|
||||
import '@formatjs/intl-locale/polyfill'
|
||||
import '@formatjs/intl-pluralrules/polyfill'
|
||||
import '@formatjs/intl-pluralrules/locale-data/de'
|
||||
import '@formatjs/intl-pluralrules/locale-data/en'
|
||||
import '@formatjs/intl-pluralrules/locale-data/ko'
|
||||
import '@formatjs/intl-pluralrules/locale-data/vi'
|
||||
import '@formatjs/intl-pluralrules/locale-data/zh'
|
||||
import '@formatjs/intl-numberformat/polyfill'
|
||||
import '@formatjs/intl-numberformat/locale-data/de'
|
||||
import '@formatjs/intl-numberformat/locale-data/en'
|
||||
import '@formatjs/intl-numberformat/locale-data/ko'
|
||||
import '@formatjs/intl-numberformat/locale-data/vi'
|
||||
import '@formatjs/intl-numberformat/locale-data/zh'
|
||||
import '@formatjs/intl-datetimeformat/polyfill'
|
||||
import '@formatjs/intl-datetimeformat/locale-data/de'
|
||||
import '@formatjs/intl-datetimeformat/locale-data/en'
|
||||
import '@formatjs/intl-datetimeformat/locale-data/ko'
|
||||
import '@formatjs/intl-datetimeformat/locale-data/vi'
|
||||
import '@formatjs/intl-datetimeformat/locale-data/zh'
|
||||
import '@formatjs/intl-datetimeformat/add-all-tz'
|
||||
import '@formatjs/intl-relativetimeformat/polyfill'
|
||||
import '@formatjs/intl-relativetimeformat/locale-data/de'
|
||||
import '@formatjs/intl-relativetimeformat/locale-data/en'
|
||||
import '@formatjs/intl-relativetimeformat/locale-data/ko'
|
||||
import '@formatjs/intl-relativetimeformat/locale-data/vi'
|
||||
import '@formatjs/intl-relativetimeformat/locale-data/zh'
|
||||
import queryClient from '@helpers/queryClient'
|
||||
import i18n from '@root/i18n/i18n'
|
||||
import Screens from '@root/Screens'
|
||||
@ -6,7 +33,9 @@ import audio from '@root/startup/audio'
|
||||
import dev from '@root/startup/dev'
|
||||
import log from '@root/startup/log'
|
||||
import netInfo from '@root/startup/netInfo'
|
||||
import push from '@root/startup/push'
|
||||
import sentry from '@root/startup/sentry'
|
||||
import timezone from '@root/startup/timezone'
|
||||
import { persistor, store } from '@root/store'
|
||||
import AccessibilityManager from '@utils/accessibility/AccessibilityManager'
|
||||
import {
|
||||
@ -19,11 +48,12 @@ import * as SplashScreen from 'expo-splash-screen'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { AppState, LogBox, Platform } from 'react-native'
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
||||
import 'react-native-image-keyboard'
|
||||
import { enableFreeze } from 'react-native-screens'
|
||||
import { QueryClientProvider } from 'react-query'
|
||||
import { Provider } from 'react-redux'
|
||||
import { PersistGate } from 'redux-persist/integration/react'
|
||||
import push from './startup/push'
|
||||
import { IntlProvider } from 'react-intl'
|
||||
|
||||
Platform.select({
|
||||
android: LogBox.ignoreLogs(['Setting a timer for a long period of time'])
|
||||
@ -33,6 +63,7 @@ dev()
|
||||
sentry()
|
||||
audio()
|
||||
push()
|
||||
timezone()
|
||||
enableFreeze(true)
|
||||
|
||||
const App: React.FC = () => {
|
||||
@ -91,13 +122,18 @@ const App: React.FC = () => {
|
||||
const language = getSettingsLanguage(store.getState())
|
||||
if (!language) {
|
||||
store.dispatch(changeLanguage('en'))
|
||||
i18n.changeLanguage('en')
|
||||
} else {
|
||||
i18n.changeLanguage(language)
|
||||
}
|
||||
i18n.changeLanguage(language)
|
||||
|
||||
return (
|
||||
<ActionSheetProvider>
|
||||
<AccessibilityManager>
|
||||
<ThemeManager>
|
||||
<Screens localCorrupt={localCorrupt} />
|
||||
<IntlProvider locale={language}>
|
||||
<Screens localCorrupt={localCorrupt} />
|
||||
</IntlProvider>
|
||||
</ThemeManager>
|
||||
</AccessibilityManager>
|
||||
</ActionSheetProvider>
|
||||
|
@ -1,24 +0,0 @@
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Text } from 'react-native'
|
||||
import TimeAgo from 'react-timeago'
|
||||
// @ts-ignore
|
||||
import buildFormatter from 'react-timeago/lib/formatters/buildFormatter'
|
||||
|
||||
export interface Props {
|
||||
date: string | number
|
||||
}
|
||||
|
||||
const RelativeTime: React.FC<Props> = ({ date }) => {
|
||||
const { t } = useTranslation('componentRelativeTime')
|
||||
|
||||
return (
|
||||
<TimeAgo
|
||||
date={date}
|
||||
component={Text}
|
||||
formatter={buildFormatter(t('strings', { returnObjects: true }))}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default RelativeTime
|
@ -102,6 +102,7 @@ const TimelineDefault = React.memo(
|
||||
queryKey={disableOnPress ? undefined : queryKey}
|
||||
rootQueryKey={disableOnPress ? undefined : rootQueryKey}
|
||||
status={actualStatus}
|
||||
highlighted={highlighted}
|
||||
/>
|
||||
</View>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Button from '@components/Button'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { Video } from 'expo-av'
|
||||
import { ResizeMode, Video, VideoFullscreenUpdate } from 'expo-av'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
AppState,
|
||||
@ -110,15 +110,14 @@ const AttachmentVideo: React.FC<Props> = ({
|
||||
source: { uri: video.url }
|
||||
}
|
||||
: {
|
||||
resizeMode: 'cover',
|
||||
resizeMode: ResizeMode.COVER,
|
||||
posterSource: { uri: video.preview_url },
|
||||
posterStyle: { resizeMode: 'cover' }
|
||||
posterStyle: { resizeMode: ResizeMode.COVER }
|
||||
})}
|
||||
useNativeControls={false}
|
||||
onFullscreenUpdate={async event => {
|
||||
if (
|
||||
event.fullscreenUpdate ===
|
||||
Video.FULLSCREEN_UPDATE_PLAYER_DID_DISMISS
|
||||
event.fullscreenUpdate === VideoFullscreenUpdate.PLAYER_DID_DISMISS
|
||||
) {
|
||||
if (gifv) {
|
||||
await videoPlayer.current?.pauseAsync()
|
||||
|
@ -5,10 +5,10 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
export interface Props {
|
||||
status: Pick<
|
||||
Mastodon.Status,
|
||||
'content' | 'spoiler_text' | 'emojis' | 'mentions' | 'tags'
|
||||
>
|
||||
status: Pick<Mastodon.Status, 'content' | 'spoiler_text' | 'emojis'> & {
|
||||
mentions?: Mastodon.Status['mentions']
|
||||
tags?: Mastodon.Status['tags']
|
||||
}
|
||||
numberOfLines?: number
|
||||
highlighted?: boolean
|
||||
disableDetails?: boolean
|
||||
|
@ -18,9 +18,15 @@ export interface Props {
|
||||
queryKey?: QueryKeyTimeline
|
||||
rootQueryKey?: QueryKeyTimeline
|
||||
status: Mastodon.Status
|
||||
highlighted: boolean
|
||||
}
|
||||
|
||||
const TimelineHeaderDefault = ({ queryKey, rootQueryKey, status }: Props) => {
|
||||
const TimelineHeaderDefault = ({
|
||||
queryKey,
|
||||
rootQueryKey,
|
||||
status,
|
||||
highlighted
|
||||
}: Props) => {
|
||||
const { t } = useTranslation('componentTimeline')
|
||||
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>()
|
||||
const { colors } = useTheme()
|
||||
@ -40,6 +46,7 @@ const TimelineHeaderDefault = ({ queryKey, rootQueryKey, status }: Props) => {
|
||||
<HeaderSharedCreated
|
||||
created_at={status.created_at}
|
||||
edited_at={status.edited_at}
|
||||
highlighted={highlighted}
|
||||
/>
|
||||
<HeaderSharedVisibility visibility={status.visibility} />
|
||||
<HeaderSharedMuted muted={status.muted} />
|
||||
|
@ -1,25 +1,43 @@
|
||||
import Icon from '@components/Icon'
|
||||
import RelativeTime from '@components/RelativeTime'
|
||||
import CustomText from '@components/Text'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FormattedDate, FormattedRelativeTime, FormattedTime } from 'react-intl'
|
||||
|
||||
export interface Props {
|
||||
created_at: Mastodon.Status['created_at'] | number
|
||||
edited_at?: Mastodon.Status['edited_at']
|
||||
highlighted?: boolean
|
||||
}
|
||||
|
||||
const HeaderSharedCreated = React.memo(
|
||||
({ created_at, edited_at }: Props) => {
|
||||
({ created_at, edited_at, highlighted = false }: Props) => {
|
||||
const { t } = useTranslation('componentTimeline')
|
||||
const { colors } = useTheme()
|
||||
|
||||
const actualTime = edited_at || created_at
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomText fontStyle='S' style={{ color: colors.secondary }}>
|
||||
<RelativeTime date={edited_at || created_at} />
|
||||
{highlighted ? (
|
||||
<>
|
||||
<FormattedDate
|
||||
value={new Date(actualTime)}
|
||||
dateStyle='medium'
|
||||
timeStyle='short'
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<FormattedRelativeTime
|
||||
value={
|
||||
-(new Date().getTime() - new Date(actualTime).getTime()) / 1000
|
||||
}
|
||||
updateIntervalInSeconds={1}
|
||||
/>
|
||||
)}
|
||||
</CustomText>
|
||||
{edited_at ? (
|
||||
<Icon
|
||||
|
@ -15,6 +15,30 @@ const HeaderSharedVisibility = React.memo(
|
||||
const { colors } = useTheme()
|
||||
|
||||
switch (visibility) {
|
||||
case 'public':
|
||||
return (
|
||||
<Icon
|
||||
accessibilityLabel={t(
|
||||
'shared.header.shared.visibility.private.accessibilityLabel'
|
||||
)}
|
||||
name='Globe'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={colors.secondary}
|
||||
style={styles.visibility}
|
||||
/>
|
||||
)
|
||||
case 'unlisted':
|
||||
return (
|
||||
<Icon
|
||||
accessibilityLabel={t(
|
||||
'shared.header.shared.visibility.private.accessibilityLabel'
|
||||
)}
|
||||
name='Unlock'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={colors.secondary}
|
||||
style={styles.visibility}
|
||||
/>
|
||||
)
|
||||
case 'private':
|
||||
return (
|
||||
<Icon
|
||||
|
@ -4,7 +4,6 @@ import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { ParseEmojis } from '@components/Parse'
|
||||
import RelativeTime from '@components/RelativeTime'
|
||||
import CustomText from '@components/Text'
|
||||
import {
|
||||
MutationVarsTimelineUpdateStatusProperty,
|
||||
@ -17,6 +16,7 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { maxBy } from 'lodash'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { FormattedRelativeTime } from 'react-intl'
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { useQueryClient } from 'react-query'
|
||||
|
||||
@ -158,7 +158,16 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
<CustomText fontStyle='S' style={{ color: colors.secondary }}>
|
||||
<Trans
|
||||
i18nKey='componentTimeline:shared.poll.meta.expiration.until'
|
||||
components={[<RelativeTime date={poll.expires_at} />]}
|
||||
components={[
|
||||
<FormattedRelativeTime
|
||||
value={
|
||||
(new Date(poll.expires_at).getTime() -
|
||||
new Date().getTime()) /
|
||||
1000
|
||||
}
|
||||
updateIntervalInSeconds={1}
|
||||
/>
|
||||
]}
|
||||
/>
|
||||
</CustomText>
|
||||
)
|
||||
|
@ -2,7 +2,10 @@ import analytics from '@components/analytics'
|
||||
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
||||
import * as ImageManipulator from 'expo-image-manipulator'
|
||||
import * as ImagePicker from 'expo-image-picker'
|
||||
import { ImageInfo } from 'expo-image-picker/build/ImagePicker.types'
|
||||
import {
|
||||
ImageInfo,
|
||||
UIImagePickerPresentationStyle
|
||||
} from 'expo-image-picker/build/ImagePicker.types'
|
||||
import i18next from 'i18next'
|
||||
import { Alert, Linking, Platform } from 'react-native'
|
||||
|
||||
@ -39,7 +42,7 @@ const mediaSelector = async ({
|
||||
{ resize }
|
||||
])
|
||||
}
|
||||
resolve(newResult)
|
||||
resolve({ ...newResult, cancelled: false })
|
||||
} else {
|
||||
resolve(result)
|
||||
}
|
||||
@ -94,8 +97,8 @@ const mediaSelector = async ({
|
||||
exif: false,
|
||||
presentationStyle:
|
||||
Platform.OS === 'ios' && parseInt(Platform.Version) < 13
|
||||
? 0
|
||||
: -2
|
||||
? UIImagePickerPresentationStyle.FULL_SCREEN
|
||||
: UIImagePickerPresentationStyle.AUTOMATIC
|
||||
})
|
||||
|
||||
if (!result.cancelled) {
|
||||
|
@ -34,6 +34,7 @@ const openLink = async (url: string, navigation?: any) => {
|
||||
// @ts-ignore
|
||||
navigation.push(page, options)
|
||||
} else {
|
||||
// @ts-ignore
|
||||
navigationRef.navigate(page, options)
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"strings": {
|
||||
"prefixAgo": "",
|
||||
"prefixFromNow": "",
|
||||
"suffixAgo": "her",
|
||||
"suffixFromNow": "",
|
||||
"seconds": "%d Sekunden",
|
||||
"minute": "etwa eine Minute",
|
||||
"minutes": "%d Minuten",
|
||||
"hour": "etwa eine Stunde",
|
||||
"hours": "etwa %d Stunden",
|
||||
"day": "1 Tag",
|
||||
"days": "%d Tage",
|
||||
"month": "etwa 1 Monat",
|
||||
"months": "%d Monate",
|
||||
"year": "etwa 1 Jahr",
|
||||
"years": "%d Jahre",
|
||||
"wordSeparator": ""
|
||||
}
|
||||
}
|
@ -62,8 +62,8 @@
|
||||
"history": {
|
||||
"accessibilityLabel": "Dieser Tröt wurde {{count}} mal bearbeitet",
|
||||
"accessibilityHint": "Für den vollständigen Verlauf auswählen",
|
||||
"text": "{{count}} bearbeitet",
|
||||
"text_plural": "{{count}} mal bearbeitet"
|
||||
"text_one": "{{count}} bearbeitet",
|
||||
"text_other": "{{count}} mal bearbeitet"
|
||||
}
|
||||
},
|
||||
"attachment": {
|
||||
@ -219,10 +219,10 @@
|
||||
"refresh": "Aktualisieren"
|
||||
},
|
||||
"count": {
|
||||
"voters": "{{count}} Benutzer haben abgestimmt",
|
||||
"voters_plural": "{{count}} Benutzer haben abgestimmt",
|
||||
"votes": "{{count}} Stimmen",
|
||||
"votes_plural": "{{count}} Stimmen"
|
||||
"voters_one": "{{count}} Benutzer haben abgestimmt",
|
||||
"voters_other": "{{count}} Benutzer haben abgestimmt",
|
||||
"votes_one": "{{count}} Stimmen",
|
||||
"votes_other": "{{count}} Stimmen"
|
||||
},
|
||||
"expiration": {
|
||||
"expired": "Abstimmung abgelaufen",
|
||||
|
@ -13,6 +13,5 @@ export default {
|
||||
componentMediaSelector: require('./components/mediaSelector'),
|
||||
componentParse: require('./components/parse'),
|
||||
componentRelationship: require('./components/relationship'),
|
||||
componentRelativeTime: require('./components/relativeTime'),
|
||||
componentTimeline: require('./components/timeline')
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"buttons": {
|
||||
"OK": "OK",
|
||||
"apply": "Apply",
|
||||
"cancel": "Cancel"
|
||||
},
|
||||
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"strings": {
|
||||
"prefixAgo": "",
|
||||
"prefixFromNow": "",
|
||||
"suffixAgo": "ago",
|
||||
"suffixFromNow": "",
|
||||
"seconds": "%d seconds",
|
||||
"minute": "about a minute",
|
||||
"minutes": "%d minutes",
|
||||
"hour": "about an hour",
|
||||
"hours": "about %d hours",
|
||||
"day": "a day",
|
||||
"days": "%d days",
|
||||
"month": "about a month",
|
||||
"months": "%d months",
|
||||
"year": "about a year",
|
||||
"years": "%d years",
|
||||
"wordSeparator": " "
|
||||
}
|
||||
}
|
@ -62,8 +62,8 @@
|
||||
"history": {
|
||||
"accessibilityLabel": "This toot has been edited {{count}} times",
|
||||
"accessibilityHint": "Tap to view the full edit history",
|
||||
"text": "{{count}} edit",
|
||||
"text_plural": "{{count}} edits"
|
||||
"text_one": "{{count}} edit",
|
||||
"text_other": "{{count}} edits"
|
||||
}
|
||||
},
|
||||
"attachment": {
|
||||
@ -219,10 +219,10 @@
|
||||
"refresh": "Refresh"
|
||||
},
|
||||
"count": {
|
||||
"voters": "{{count}} user voted",
|
||||
"voters_plural": "{{count}} users voted",
|
||||
"votes": "{{count}} vote",
|
||||
"votes_plural": "{{count}} votes"
|
||||
"voters_one": "{{count}} user voted",
|
||||
"voters_other": "{{count}} users voted",
|
||||
"votes_one": "{{count}} vote",
|
||||
"votes_other": "{{count}} votes"
|
||||
},
|
||||
"expiration": {
|
||||
"expired": "Vote expired",
|
||||
|
@ -42,7 +42,13 @@
|
||||
"placeholder": "Spoiler warning message"
|
||||
},
|
||||
"textInput": {
|
||||
"placeholder": "What's on your mind"
|
||||
"placeholder": "What's on your mind",
|
||||
"keyboardImage": {
|
||||
"exceedMaximum": {
|
||||
"title": "Maximum attachments amount reached",
|
||||
"OK": "$t(common:buttons.OK)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
@ -136,8 +142,8 @@
|
||||
"accessibilityHint": "Open emoji selection panel, swipe horizontally to change page"
|
||||
}
|
||||
},
|
||||
"drafts": "Draft ({{count}})",
|
||||
"drafts_plural": "Drafts ({{count}})"
|
||||
"drafts_one": "Draft ({{count}})",
|
||||
"drafts_other": "Drafts ({{count}})"
|
||||
},
|
||||
"editAttachment": {
|
||||
"header": {
|
||||
|
@ -116,8 +116,8 @@
|
||||
},
|
||||
"fields": {
|
||||
"title": "Metadata",
|
||||
"total": "{{count}} field",
|
||||
"total_plural": "{{count}} fields"
|
||||
"total_one": "{{count}} field",
|
||||
"total_other": "{{count}} fields"
|
||||
},
|
||||
"visibility": {
|
||||
"title": "Posting Visibility",
|
||||
@ -281,6 +281,7 @@
|
||||
"accessibilityLabel": "Actions for user {{user}}",
|
||||
"accessibilityHint": "You can mute, block, report or share this user"
|
||||
},
|
||||
"followed_by": " is following you",
|
||||
"moved": "User moved",
|
||||
"created_at": "Registered on: {{date}}",
|
||||
"summary": {
|
||||
|
@ -13,6 +13,5 @@ export default {
|
||||
componentMediaSelector: require('./components/mediaSelector'),
|
||||
componentParse: require('./components/parse'),
|
||||
componentRelationship: require('./components/relationship'),
|
||||
componentRelativeTime: require('./components/relativeTime'),
|
||||
componentTimeline: require('./components/timeline')
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"strings": {
|
||||
"prefixAgo": "",
|
||||
"prefixFromNow": "",
|
||||
"suffixAgo": "전",
|
||||
"suffixFromNow": "",
|
||||
"seconds": "%d초",
|
||||
"minute": "약 1분",
|
||||
"minutes": "%d분",
|
||||
"hour": "약 1시간",
|
||||
"hours": "약 %d시간",
|
||||
"day": "하루",
|
||||
"days": "%d일",
|
||||
"month": "약 1달",
|
||||
"months": "%d달",
|
||||
"year": "약 1년",
|
||||
"years": "%d년",
|
||||
"wordSeparator": ""
|
||||
}
|
||||
}
|
@ -196,10 +196,10 @@
|
||||
"refresh": "새로고침"
|
||||
},
|
||||
"count": {
|
||||
"voters": "{{count}}명의 사용자가 투표",
|
||||
"voters_plural": "{{count}}명의 사용자가 투표",
|
||||
"votes": "{{count}} 투표",
|
||||
"votes_plural": "{{count}} 투표"
|
||||
"voters_one": "{{count}}명의 사용자가 투표",
|
||||
"voters_other": "{{count}}명의 사용자가 투표",
|
||||
"votes_one": "{{count}} 투표",
|
||||
"votes_other": "{{count}} 투표"
|
||||
},
|
||||
"expiration": {
|
||||
"expired": "투표 종료됨",
|
||||
|
@ -134,8 +134,8 @@
|
||||
"accessibilityHint": "이모지 선택 패널 열기, 가로로 스와이프해서 페이지를 바꿀 수 있어요"
|
||||
}
|
||||
},
|
||||
"drafts": "초안 ({{count}})",
|
||||
"drafts_plural": "초안 ({{count}})"
|
||||
"drafts_one": "초안 ({{count}})",
|
||||
"drafts_other": "초안 ({{count}})"
|
||||
},
|
||||
"editAttachment": {
|
||||
"header": {
|
||||
|
@ -113,8 +113,8 @@
|
||||
},
|
||||
"fields": {
|
||||
"title": "메타데이터",
|
||||
"total": "{{count}}개 필드",
|
||||
"total_plural": "{{count}}개 필드"
|
||||
"total_one": "{{count}}개 필드",
|
||||
"total_other": "{{count}}개 필드"
|
||||
},
|
||||
"visibility": {
|
||||
"title": "공개 범위",
|
||||
|
@ -13,6 +13,5 @@ export default {
|
||||
componentMediaSelector: require('./components/mediaSelector'),
|
||||
componentParse: require('./components/parse'),
|
||||
componentRelationship: require('./components/relationship'),
|
||||
componentRelativeTime: require('./components/relativeTime'),
|
||||
componentTimeline: require('./components/timeline')
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"strings": {
|
||||
"prefixAgo": "",
|
||||
"prefixFromNow": "",
|
||||
"suffixAgo": " trước",
|
||||
"suffixFromNow": "",
|
||||
"seconds": "%d giây",
|
||||
"minute": "một phút",
|
||||
"minutes": "%d phút",
|
||||
"hour": "một giờ",
|
||||
"hours": "khoảng %d giờ",
|
||||
"day": "một ngày",
|
||||
"days": "%d ngày",
|
||||
"month": "khoảng một tháng",
|
||||
"months": "%d tháng",
|
||||
"year": "khoảng một năm",
|
||||
"years": "%d năm",
|
||||
"wordSeparator": ""
|
||||
}
|
||||
}
|
@ -13,6 +13,5 @@ export default {
|
||||
componentMediaSelector: require('./components/mediaSelector'),
|
||||
componentParse: require('./components/parse'),
|
||||
componentRelationship: require('./components/relationship'),
|
||||
componentRelativeTime: require('./components/relativeTime'),
|
||||
componentTimeline: require('./components/timeline')
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"strings": {
|
||||
"prefixAgo": "",
|
||||
"prefixFromNow": "",
|
||||
"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": ""
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"strings": {
|
||||
"prefixAgo": "",
|
||||
"prefixFromNow": "",
|
||||
"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": ""
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ import analytics from '@components/analytics'
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import { ParseHTML } from '@components/Parse'
|
||||
import RelativeTime from '@components/RelativeTime'
|
||||
import CustomText from '@components/Text'
|
||||
import { BlurView } from '@react-native-community/blur'
|
||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
@ -15,6 +14,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { FormattedRelativeTime } from 'react-intl'
|
||||
import { Dimensions, Platform, Pressable, StyleSheet, View } from 'react-native'
|
||||
import { Circle } from 'react-native-animated-spinkit'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
@ -91,7 +91,17 @@ const ScreenAnnouncements: React.FC<
|
||||
>
|
||||
<Trans
|
||||
i18nKey='screenAnnouncements:content.published'
|
||||
components={[<RelativeTime date={item.published_at} />]}
|
||||
components={[
|
||||
<FormattedRelativeTime
|
||||
value={
|
||||
-(
|
||||
new Date().getTime() -
|
||||
new Date(item.published_at).getTime()
|
||||
) / 1000
|
||||
}
|
||||
updateIntervalInSeconds={1}
|
||||
/>
|
||||
]}
|
||||
/>
|
||||
</CustomText>
|
||||
<ScrollView
|
||||
|
@ -1,4 +1,3 @@
|
||||
import apiInstance from '@api/instance'
|
||||
import analytics from '@components/analytics'
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
|
@ -5,6 +5,7 @@ import Icon from '@components/Icon'
|
||||
import CustomText from '@components/Text'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
@ -17,8 +18,10 @@ import React, {
|
||||
useRef
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FlatList, Image, Pressable, StyleSheet, View } from 'react-native'
|
||||
import { FlatList, Pressable, StyleSheet, View } from 'react-native'
|
||||
import { Circle } from 'react-native-animated-spinkit'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeContext from '../../utils/createContext'
|
||||
import { ExtendedAttachment } from '../../utils/types'
|
||||
import chooseAndUploadAttachment from './addAttachment'
|
||||
@ -33,9 +36,14 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
||||
const { showActionSheetWithOptions } = useActionSheet()
|
||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
||||
const { t } = useTranslation('screenCompose')
|
||||
const { colors, mode } = useTheme()
|
||||
const { colors } = useTheme()
|
||||
const navigation = useNavigation<any>()
|
||||
|
||||
const maxAttachments = useSelector(
|
||||
getInstanceConfigurationStatusMaxAttachments,
|
||||
() => true
|
||||
)
|
||||
|
||||
const flatListRef = useRef<FlatList>(null)
|
||||
|
||||
const sensitiveOnPress = useCallback(() => {
|
||||
@ -124,7 +132,7 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
||||
width: calculateWidth(item)
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
<FastImage
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
source={{
|
||||
uri: item.local?.local_thumbnail || item.remote?.preview_url
|
||||
@ -320,7 +328,9 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
||||
item.local?.uri || item.remote?.url || Math.random().toString()
|
||||
}
|
||||
ListFooterComponent={
|
||||
composeState.attachments.uploads.length < 4 ? listFooter : null
|
||||
composeState.attachments.uploads.length < maxAttachments
|
||||
? listFooter
|
||||
: null
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
|
@ -1,17 +1,25 @@
|
||||
import CustomText from '@components/Text'
|
||||
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { TextInput } from 'react-native'
|
||||
import { Alert, TextInput } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import formatText from '../../formatText'
|
||||
import ComposeContext from '../../utils/createContext'
|
||||
import { uploadAttachment } from '../Footer/addAttachment'
|
||||
|
||||
const ComposeTextInput: React.FC = () => {
|
||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
||||
const { t } = useTranslation('screenCompose')
|
||||
const { colors, mode } = useTheme()
|
||||
|
||||
const maxAttachments = useSelector(
|
||||
getInstanceConfigurationStatusMaxAttachments,
|
||||
() => true
|
||||
)
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
keyboardAppearance={mode}
|
||||
@ -54,6 +62,36 @@ const ComposeTextInput: React.FC = () => {
|
||||
}}
|
||||
ref={composeState.textInputFocus.refs.text}
|
||||
scrollEnabled={false}
|
||||
onImageChange={({ nativeEvent }) => {
|
||||
if (composeState.attachments.uploads.length >= maxAttachments) {
|
||||
Alert.alert(
|
||||
t(
|
||||
'content.root.header.textInput.keyboardImage.exceedMaximum.title'
|
||||
),
|
||||
undefined,
|
||||
[
|
||||
{
|
||||
text: t(
|
||||
'content.root.header.textInput.keyboardImage.exceedMaximum.OK'
|
||||
),
|
||||
style: 'default'
|
||||
}
|
||||
]
|
||||
)
|
||||
return
|
||||
}
|
||||
if (nativeEvent.linkUri) {
|
||||
uploadAttachment({
|
||||
composeDispatch,
|
||||
imageInfo: {
|
||||
uri: nativeEvent.linkUri,
|
||||
type: 'image',
|
||||
width: 100,
|
||||
height: 100
|
||||
}
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CustomText>{composeState.text.formatted}</CustomText>
|
||||
</TextInput>
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||
import React, { useState, useCallback } from 'react'
|
||||
import { Animated, Dimensions, StyleSheet } from 'react-native'
|
||||
import usePanResponder from '../hooks/usePanResponder'
|
||||
@ -17,11 +18,11 @@ const SCREEN_WIDTH = SCREEN.width
|
||||
const SCREEN_HEIGHT = SCREEN.height
|
||||
|
||||
type Props = {
|
||||
imageSrc: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
|
||||
imageSrc: RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
|
||||
onRequestClose: () => void
|
||||
onZoom: (isZoomed: boolean) => void
|
||||
onLongPress: (
|
||||
image: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
|
||||
image: RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
|
||||
) => void
|
||||
delayLongPress: number
|
||||
swipeToCloseEnabled?: boolean
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||
import React, { createRef, useCallback, useRef, useState } from 'react'
|
||||
import {
|
||||
Animated,
|
||||
@ -31,11 +32,11 @@ const SCREEN_WIDTH = SCREEN.width
|
||||
const SCREEN_HEIGHT = SCREEN.height
|
||||
|
||||
type Props = {
|
||||
imageSrc: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
|
||||
imageSrc: RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
|
||||
onRequestClose: () => void
|
||||
onZoom: (scaled: boolean) => void
|
||||
onLongPress: (
|
||||
image: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
|
||||
image: RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
|
||||
) => void
|
||||
swipeToCloseEnabled?: boolean
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Icon from '@components/Icon'
|
||||
import CustomText from '@components/Text'
|
||||
import { useRelationshipQuery } from '@utils/queryHooks/relationship'
|
||||
import {
|
||||
getInstanceAccount,
|
||||
getInstanceUri
|
||||
@ -7,6 +8,7 @@ import {
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { PlaceholderLine } from 'rn-placeholder'
|
||||
@ -20,6 +22,7 @@ const AccountInformationAccount: React.FC<Props> = ({
|
||||
account,
|
||||
localInstance
|
||||
}) => {
|
||||
const { t } = useTranslation('screenTabs')
|
||||
const { colors } = useTheme()
|
||||
const instanceAccount = useSelector(
|
||||
getInstanceAccount,
|
||||
@ -27,6 +30,11 @@ const AccountInformationAccount: React.FC<Props> = ({
|
||||
)
|
||||
const instanceUri = useSelector(getInstanceUri)
|
||||
|
||||
const { data: relationship } = useRelationshipQuery({
|
||||
id: account?.id || '',
|
||||
options: { enabled: account !== undefined }
|
||||
})
|
||||
|
||||
const movedContent = useMemo(() => {
|
||||
if (account?.moved) {
|
||||
return (
|
||||
@ -65,6 +73,11 @@ const AccountInformationAccount: React.FC<Props> = ({
|
||||
@{localInstance ? instanceAccount?.acct : account?.acct}
|
||||
{localInstance ? `@${instanceUri}` : null}
|
||||
</CustomText>
|
||||
{relationship?.followed_by ? (
|
||||
<CustomText fontStyle='M' style={{ color: colors.secondary }}>
|
||||
{t('shared.account.followed_by')}
|
||||
</CustomText>
|
||||
) : null}
|
||||
{movedContent}
|
||||
{account?.locked ? (
|
||||
<Icon
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Audio } from 'expo-av'
|
||||
import { Audio, InterruptionModeAndroid, InterruptionModeIOS } from 'expo-av'
|
||||
import log from './log'
|
||||
|
||||
const audio = () => {
|
||||
log('log', 'audio', 'setting audio playback default options')
|
||||
Audio.setAudioModeAsync({
|
||||
playsInSilentModeIOS: true,
|
||||
interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DUCK_OTHERS,
|
||||
interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DUCK_OTHERS
|
||||
interruptionModeIOS: InterruptionModeIOS.DuckOthers,
|
||||
interruptionModeAndroid: InterruptionModeAndroid.DuckOthers
|
||||
})
|
||||
}
|
||||
|
||||
|
12
src/startup/timezone.ts
Normal file
12
src/startup/timezone.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import * as Localization from 'expo-localization'
|
||||
import log from './log'
|
||||
|
||||
const timezone = () => {
|
||||
log('log', 'Timezone', Localization.timezone)
|
||||
if ('__setDefaultTimeZone' in Intl.DateTimeFormat) {
|
||||
// @ts-ignore
|
||||
Intl.DateTimeFormat.__setDefaultTimeZone(Localization.timezone)
|
||||
}
|
||||
}
|
||||
|
||||
export default timezone
|
Reference in New Issue
Block a user