mirror of https://github.com/tooot-app/app
commit
84deb2ba58
|
@ -14,11 +14,11 @@ name: "CodeQL"
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
pull_request:
|
# pull_request:
|
||||||
# The branches below must be a subset of the branches above
|
# # The branches below must be a subset of the branches above
|
||||||
branches: [ main ]
|
# branches: [ main ]
|
||||||
schedule:
|
# schedule:
|
||||||
- cron: '35 4 * * 4'
|
# - cron: '35 4 * * 4'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
|
|
10
README.md
10
README.md
|
@ -6,10 +6,12 @@
|
||||||
|
|
||||||
## Special thanks
|
## Special thanks
|
||||||
|
|
||||||
@forenta for German translation
|
[@forenta](https://github.com/forenta) for German translation
|
||||||
|
|
||||||
@andrigamerita for Italian translation
|
[@andrigamerita](https://github.com/andrigamerita) for Italian translation
|
||||||
|
|
||||||
@hellojaccc for Korean translation
|
[@hellojaccc](https://github.com/hellojaccc) for Korean translation
|
||||||
|
|
||||||
@duy@mas.to for Vietnamese translation
|
[@luizpicolo](https://github.com/luizpicolo) for Brazilian Portuguese
|
||||||
|
|
||||||
|
[@duy@mas.to](https://mas.to/@duy) for Vietnamese translation
|
||||||
|
|
|
@ -29,7 +29,19 @@ module.exports = function (api) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
presets: ['babel-preset-expo'],
|
presets: [
|
||||||
|
'babel-preset-expo',
|
||||||
|
[
|
||||||
|
'@babel/preset-react',
|
||||||
|
{
|
||||||
|
importSource: '@welldone-software/why-did-you-render',
|
||||||
|
runtime: 'automatic',
|
||||||
|
development:
|
||||||
|
process.env.NODE_ENV === 'development' ||
|
||||||
|
process.env.BABEL_ENV === 'development'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
plugins
|
plugins
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
39
package.json
39
package.json
|
@ -4,7 +4,7 @@
|
||||||
"native": "220508",
|
"native": "220508",
|
||||||
"major": 4,
|
"major": 4,
|
||||||
"minor": 0,
|
"minor": 0,
|
||||||
"patch": 3,
|
"patch": 4,
|
||||||
"expo": "45.0.0"
|
"expo": "45.0.0"
|
||||||
},
|
},
|
||||||
"description": "tooot app for Mastodon",
|
"description": "tooot app for Mastodon",
|
||||||
|
@ -26,12 +26,12 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/react-native-action-sheet": "3.13.0",
|
"@expo/react-native-action-sheet": "3.13.0",
|
||||||
"@formatjs/intl-datetimeformat": "^5.0.2",
|
"@formatjs/intl-datetimeformat": "^6.0.1",
|
||||||
"@formatjs/intl-getcanonicallocales": "^1.9.2",
|
"@formatjs/intl-getcanonicallocales": "^2.0.1",
|
||||||
"@formatjs/intl-locale": "^2.4.47",
|
"@formatjs/intl-locale": "^3.0.1",
|
||||||
"@formatjs/intl-numberformat": "^7.4.3",
|
"@formatjs/intl-numberformat": "^8.0.1",
|
||||||
"@formatjs/intl-pluralrules": "^4.3.3",
|
"@formatjs/intl-pluralrules": "^5.0.1",
|
||||||
"@formatjs/intl-relativetimeformat": "^10.0.1",
|
"@formatjs/intl-relativetimeformat": "^11.0.1",
|
||||||
"@neverdull-agency/expo-unlimited-secure-store": "1.0.10",
|
"@neverdull-agency/expo-unlimited-secure-store": "1.0.10",
|
||||||
"@react-native-async-storage/async-storage": "1.17.4",
|
"@react-native-async-storage/async-storage": "1.17.4",
|
||||||
"@react-native-community/blur": "3.6.0",
|
"@react-native-community/blur": "3.6.0",
|
||||||
|
@ -42,7 +42,7 @@
|
||||||
"@react-navigation/native": "6.0.10",
|
"@react-navigation/native": "6.0.10",
|
||||||
"@react-navigation/native-stack": "6.6.2",
|
"@react-navigation/native-stack": "6.6.2",
|
||||||
"@react-navigation/stack": "6.2.1",
|
"@react-navigation/stack": "6.2.1",
|
||||||
"@reduxjs/toolkit": "1.8.1",
|
"@reduxjs/toolkit": "1.8.2",
|
||||||
"@sentry/react-native": "3.4.2",
|
"@sentry/react-native": "3.4.2",
|
||||||
"@sharcoux/slider": "6.0.3",
|
"@sharcoux/slider": "6.0.3",
|
||||||
"axios": "0.27.2",
|
"axios": "0.27.2",
|
||||||
|
@ -68,13 +68,13 @@
|
||||||
"expo-updates": "0.13.1",
|
"expo-updates": "0.13.1",
|
||||||
"expo-video-thumbnails": "6.3.0",
|
"expo-video-thumbnails": "6.3.0",
|
||||||
"expo-web-browser": "10.2.0",
|
"expo-web-browser": "10.2.0",
|
||||||
"i18next": "21.8.2",
|
"i18next": "21.8.8",
|
||||||
"li": "1.3.0",
|
"li": "1.3.0",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"react-i18next": "11.16.9",
|
"react-i18next": "11.17.0",
|
||||||
"react-intl": "^5.25.1",
|
"react-intl": "^6.0.3",
|
||||||
"react-native": "0.68.2",
|
"react-native": "0.68.2",
|
||||||
"react-native-animated-spinkit": "1.5.2",
|
"react-native-animated-spinkit": "1.5.2",
|
||||||
"react-native-base64": "^0.2.1",
|
"react-native-base64": "^0.2.1",
|
||||||
|
@ -93,8 +93,8 @@
|
||||||
"react-native-svg": "12.3.0",
|
"react-native-svg": "12.3.0",
|
||||||
"react-native-swipe-list-view": "3.2.9",
|
"react-native-swipe-list-view": "3.2.9",
|
||||||
"react-native-tab-view": "3.1.1",
|
"react-native-tab-view": "3.1.1",
|
||||||
"react-query": "3.39.0",
|
"react-query": "3.39.1",
|
||||||
"react-redux": "8.0.1",
|
"react-redux": "8.0.2",
|
||||||
"redux-persist": "6.0.0",
|
"redux-persist": "6.0.0",
|
||||||
"rn-placeholder": "3.0.3",
|
"rn-placeholder": "3.0.3",
|
||||||
"sentry-expo": "4.1.1",
|
"sentry-expo": "4.1.1",
|
||||||
|
@ -102,14 +102,15 @@
|
||||||
"valid-url": "1.0.9"
|
"valid-url": "1.0.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.17.10",
|
"@babel/core": "7.18.2",
|
||||||
"@babel/plugin-proposal-optional-chaining": "7.16.7",
|
"@babel/plugin-proposal-optional-chaining": "7.17.12",
|
||||||
"@babel/preset-typescript": "7.16.7",
|
"@babel/preset-react": "^7.17.12",
|
||||||
|
"@babel/preset-typescript": "7.17.12",
|
||||||
"@expo/config": "6.0.24",
|
"@expo/config": "6.0.24",
|
||||||
"@types/lodash": "4.14.182",
|
"@types/lodash": "4.14.182",
|
||||||
"@types/react": "17.0.43",
|
"@types/react": "17.0.43",
|
||||||
"@types/react-dom": "17.0.14",
|
"@types/react-dom": "17.0.14",
|
||||||
"@types/react-native": "0.67.6",
|
"@types/react-native": "0.67.7",
|
||||||
"@types/react-native-base64": "^0.2.0",
|
"@types/react-native-base64": "^0.2.0",
|
||||||
"@types/react-native-share-menu": "^5.0.2",
|
"@types/react-native-share-menu": "^5.0.2",
|
||||||
"@types/react-timeago": "4.1.3",
|
"@types/react-timeago": "4.1.3",
|
||||||
|
@ -122,7 +123,7 @@
|
||||||
"patch-package": "6.4.7",
|
"patch-package": "6.4.7",
|
||||||
"postinstall-postinstall": "2.1.0",
|
"postinstall-postinstall": "2.1.0",
|
||||||
"react-native-clean-project": "4.0.1",
|
"react-native-clean-project": "4.0.1",
|
||||||
"typescript": "4.6.4"
|
"typescript": "4.7.3"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@types/react": "17.0.43",
|
"@types/react": "17.0.43",
|
||||||
|
@ -150,4 +151,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -341,6 +341,7 @@ declare namespace Mastodon {
|
||||||
| 'favourite'
|
| 'favourite'
|
||||||
| 'poll'
|
| 'poll'
|
||||||
| 'status'
|
| 'status'
|
||||||
|
| 'update'
|
||||||
created_at: string
|
created_at: string
|
||||||
account: Account
|
account: Account
|
||||||
|
|
||||||
|
@ -375,10 +376,12 @@ declare namespace Mastodon {
|
||||||
endpoint: string
|
endpoint: string
|
||||||
alerts: {
|
alerts: {
|
||||||
follow: boolean
|
follow: boolean
|
||||||
|
follow_request: boolean
|
||||||
favourite: boolean
|
favourite: boolean
|
||||||
reblog: boolean
|
reblog: boolean
|
||||||
mention: boolean
|
mention: boolean
|
||||||
poll: boolean
|
poll: boolean
|
||||||
|
status: boolean
|
||||||
}
|
}
|
||||||
server_key: string
|
server_key: string
|
||||||
}
|
}
|
||||||
|
|
42
src/App.tsx
42
src/App.tsx
|
@ -1,31 +1,4 @@
|
||||||
import { ActionSheetProvider } from '@expo/react-native-action-sheet'
|
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 queryClient from '@helpers/queryClient'
|
||||||
import i18n from '@root/i18n/i18n'
|
import i18n from '@root/i18n/i18n'
|
||||||
import Screens from '@root/Screens'
|
import Screens from '@root/Screens'
|
||||||
|
@ -44,10 +17,9 @@ import {
|
||||||
} from '@utils/slices/settingsSlice'
|
} from '@utils/slices/settingsSlice'
|
||||||
import ThemeManager from '@utils/styles/ThemeManager'
|
import ThemeManager from '@utils/styles/ThemeManager'
|
||||||
import 'expo-asset'
|
import 'expo-asset'
|
||||||
import * as Notifications from 'expo-notifications'
|
|
||||||
import * as SplashScreen from 'expo-splash-screen'
|
import * as SplashScreen from 'expo-splash-screen'
|
||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
import { AppState, LogBox, Platform } from 'react-native'
|
import { LogBox, Platform } from 'react-native'
|
||||||
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
||||||
import 'react-native-image-keyboard'
|
import 'react-native-image-keyboard'
|
||||||
import { enableFreeze } from 'react-native-screens'
|
import { enableFreeze } from 'react-native-screens'
|
||||||
|
@ -71,18 +43,6 @@ const App: React.FC = () => {
|
||||||
log('log', 'App', 'rendering App')
|
log('log', 'App', 'rendering App')
|
||||||
const [localCorrupt, setLocalCorrupt] = useState<string>()
|
const [localCorrupt, setLocalCorrupt] = useState<string>()
|
||||||
|
|
||||||
const appStateEffect = useCallback(() => {
|
|
||||||
Notifications.setBadgeCountAsync(0)
|
|
||||||
Notifications.dismissAllNotificationsAsync()
|
|
||||||
}, [])
|
|
||||||
useEffect(() => {
|
|
||||||
const appStateListener = AppState.addEventListener('change', appStateEffect)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
appStateListener.remove()
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const delaySplash = async () => {
|
const delaySplash = async () => {
|
||||||
log('log', 'App', 'delay splash')
|
log('log', 'App', 'delay splash')
|
||||||
|
|
|
@ -19,7 +19,7 @@ export type Params = {
|
||||||
|
|
||||||
export const TOOOT_API_DOMAIN = mapEnvironment({
|
export const TOOOT_API_DOMAIN = mapEnvironment({
|
||||||
release: 'api.tooot.app',
|
release: 'api.tooot.app',
|
||||||
candidate: 'api.tooot.app',
|
candidate: 'api-candidate.tooot.app',
|
||||||
development: 'api-development.tooot.app'
|
development: 'api-development.tooot.app'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ export interface Props {
|
||||||
|
|
||||||
strokeWidth?: number
|
strokeWidth?: number
|
||||||
size?: 'S' | 'M' | 'L'
|
size?: 'S' | 'M' | 'L'
|
||||||
|
fontBold?: boolean
|
||||||
spacing?: 'XS' | 'S' | 'M' | 'L'
|
spacing?: 'XS' | 'S' | 'M' | 'L'
|
||||||
round?: boolean
|
round?: boolean
|
||||||
overlay?: boolean
|
overlay?: boolean
|
||||||
|
@ -48,6 +49,7 @@ const Button: React.FC<Props> = ({
|
||||||
disabled = false,
|
disabled = false,
|
||||||
strokeWidth,
|
strokeWidth,
|
||||||
size = 'M',
|
size = 'M',
|
||||||
|
fontBold = false,
|
||||||
spacing = 'S',
|
spacing = 'S',
|
||||||
round = false,
|
round = false,
|
||||||
overlay = false,
|
overlay = false,
|
||||||
|
@ -122,6 +124,7 @@ const Button: React.FC<Props> = ({
|
||||||
StyleConstants.Font.Size[size] * (size === 'L' ? 1.25 : 1),
|
StyleConstants.Font.Size[size] * (size === 'L' ? 1.25 : 1),
|
||||||
opacity: loading ? 0 : 1
|
opacity: loading ? 0 : 1
|
||||||
}}
|
}}
|
||||||
|
fontWeight={fontBold ? 'Bold' : 'Normal'}
|
||||||
children={content}
|
children={content}
|
||||||
testID='text'
|
testID='text'
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import { useAppDispatch } from '@root/store'
|
import { useAppDispatch } from '@root/store'
|
||||||
|
import { InstanceLatest } from '@utils/migrations/instances/migration'
|
||||||
import { TabMeStackNavigationProp } from '@utils/navigation/navigators'
|
import { TabMeStackNavigationProp } from '@utils/navigation/navigators'
|
||||||
import addInstance from '@utils/slices/instances/add'
|
import addInstance from '@utils/slices/instances/add'
|
||||||
import { checkInstanceFeature, Instance } from '@utils/slices/instancesSlice'
|
import { checkInstanceFeature } from '@utils/slices/instancesSlice'
|
||||||
import * as AuthSession from 'expo-auth-session'
|
import * as AuthSession from 'expo-auth-session'
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { useQueryClient } from 'react-query'
|
import { useQueryClient } from 'react-query'
|
||||||
|
@ -12,7 +13,7 @@ export interface Props {
|
||||||
instanceDomain: string
|
instanceDomain: string
|
||||||
// Domain can be different than uri
|
// Domain can be different than uri
|
||||||
instance: Mastodon.Instance
|
instance: Mastodon.Instance
|
||||||
appData: Instance['appData']
|
appData: InstanceLatest['appData']
|
||||||
goBack?: boolean
|
goBack?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ export interface Props {
|
||||||
switchDisabled?: boolean
|
switchDisabled?: boolean
|
||||||
switchOnValueChange?: () => void
|
switchOnValueChange?: () => void
|
||||||
|
|
||||||
iconBack?: 'ChevronRight' | 'ExternalLink'
|
iconBack?: 'ChevronRight' | 'ExternalLink' | 'Check'
|
||||||
iconBackColor?: ColorDefinitions
|
iconBackColor?: ColorDefinitions
|
||||||
|
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { adaptiveScale } from '@utils/styles/scaling'
|
import { adaptiveScale } from '@utils/styles/scaling'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { StyleSheet } from 'react-native'
|
import { Platform, StyleSheet } from 'react-native'
|
||||||
import FastImage from 'react-native-fast-image'
|
import FastImage from 'react-native-fast-image'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import validUrl from 'valid-url'
|
import validUrl from 'valid-url'
|
||||||
|
@ -51,7 +51,13 @@ const ParseEmojis = React.memo(
|
||||||
image: {
|
image: {
|
||||||
width: adaptedFontsize,
|
width: adaptedFontsize,
|
||||||
height: adaptedFontsize,
|
height: adaptedFontsize,
|
||||||
transform: [{ translateY: -2 }]
|
...(Platform.OS === 'ios'
|
||||||
|
? {
|
||||||
|
transform: [{ translateY: -2 }]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
transform: [{ translateY: 1 }]
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [theme, adaptiveFontsize])
|
}, [theme, adaptiveFontsize])
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { adaptiveScale } from '@utils/styles/scaling'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Pressable, View } from 'react-native'
|
import { Platform, Pressable, View } from 'react-native'
|
||||||
import HTMLView from 'react-native-htmlview'
|
import HTMLView from 'react-native-htmlview'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
|
@ -139,7 +139,13 @@ const renderNode = ({
|
||||||
name='ExternalLink'
|
name='ExternalLink'
|
||||||
size={adaptedFontsize}
|
size={adaptedFontsize}
|
||||||
style={{
|
style={{
|
||||||
transform: [{ translateY: -2 }]
|
...(Platform.OS === 'ios'
|
||||||
|
? {
|
||||||
|
transform: [{ translateY: -2 }]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
transform: [{ translateY: 1 }]
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import ComponentSeparator from '@components/Separator'
|
import ComponentSeparator from '@components/Separator'
|
||||||
import { useScrollToTop } from '@react-navigation/native'
|
import { useScrollToTop } from '@react-navigation/native'
|
||||||
import {
|
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||||
QueryKeyTimeline,
|
|
||||||
TimelineData,
|
|
||||||
useTimelineQuery
|
|
||||||
} from '@utils/queryHooks/timeline'
|
|
||||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
@ -16,10 +12,20 @@ import {
|
||||||
RefreshControl,
|
RefreshControl,
|
||||||
StyleSheet
|
StyleSheet
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import { InfiniteData, useQueryClient } from 'react-query'
|
import Animated, {
|
||||||
|
useAnimatedScrollHandler,
|
||||||
|
useSharedValue
|
||||||
|
} from 'react-native-reanimated'
|
||||||
|
import { useQueryClient } from 'react-query'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import TimelineEmpty from './Timeline/Empty'
|
import TimelineEmpty from './Timeline/Empty'
|
||||||
import TimelineFooter from './Timeline/Footer'
|
import TimelineFooter from './Timeline/Footer'
|
||||||
|
import TimelineRefresh, {
|
||||||
|
SEPARATION_Y_1,
|
||||||
|
SEPARATION_Y_2
|
||||||
|
} from './Timeline/Refresh'
|
||||||
|
|
||||||
|
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList)
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
flRef?: RefObject<FlatList<any>>
|
flRef?: RefObject<FlatList<any>>
|
||||||
|
@ -40,15 +46,12 @@ const Timeline: React.FC<Props> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
refetch,
|
refetch,
|
||||||
isFetching,
|
isFetching,
|
||||||
isLoading,
|
isLoading,
|
||||||
fetchPreviousPage,
|
|
||||||
fetchNextPage,
|
fetchNextPage,
|
||||||
isFetchingPreviousPage,
|
|
||||||
isFetchingNextPage
|
isFetchingNextPage
|
||||||
} = useTimelineQuery({
|
} = useTimelineQuery({
|
||||||
...queryKey[1],
|
...queryKey[1],
|
||||||
|
@ -57,12 +60,6 @@ const Timeline: React.FC<Props> = ({
|
||||||
ios: ['dataUpdatedAt', 'isFetching'],
|
ios: ['dataUpdatedAt', 'isFetching'],
|
||||||
android: ['dataUpdatedAt', 'isFetching', 'isLoading']
|
android: ['dataUpdatedAt', 'isFetching', 'isLoading']
|
||||||
}),
|
}),
|
||||||
getPreviousPageParam: firstPage =>
|
|
||||||
firstPage?.links?.prev && {
|
|
||||||
min_id: firstPage.links.prev,
|
|
||||||
// https://github.com/facebook/react-native/issues/25239
|
|
||||||
limit: '10'
|
|
||||||
},
|
|
||||||
getNextPageParam: lastPage =>
|
getNextPageParam: lastPage =>
|
||||||
lastPage?.links?.next && {
|
lastPage?.links?.next && {
|
||||||
max_id: lastPage.links.next
|
max_id: lastPage.links.next
|
||||||
|
@ -92,6 +89,27 @@ const Timeline: React.FC<Props> = ({
|
||||||
|
|
||||||
const flRef = useRef<FlatList>(null)
|
const flRef = useRef<FlatList>(null)
|
||||||
|
|
||||||
|
const scrollY = useSharedValue(0)
|
||||||
|
const fetchingType = useSharedValue<0 | 1 | 2>(0)
|
||||||
|
|
||||||
|
const onScroll = useAnimatedScrollHandler(
|
||||||
|
{
|
||||||
|
onScroll: ({ contentOffset: { y } }) => {
|
||||||
|
scrollY.value = y
|
||||||
|
},
|
||||||
|
onEndDrag: ({ contentOffset: { y } }) => {
|
||||||
|
if (!disableRefresh && !isFetching) {
|
||||||
|
if (y <= SEPARATION_Y_2) {
|
||||||
|
fetchingType.value = 2
|
||||||
|
} else if (y <= SEPARATION_Y_1) {
|
||||||
|
fetchingType.value = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isFetching]
|
||||||
|
)
|
||||||
|
|
||||||
const androidRefreshControl = Platform.select({
|
const androidRefreshControl = Platform.select({
|
||||||
android: {
|
android: {
|
||||||
refreshControl: (
|
refreshControl: (
|
||||||
|
@ -115,46 +133,40 @@ const Timeline: React.FC<Props> = ({
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<>
|
||||||
ref={customFLRef || flRef}
|
<TimelineRefresh
|
||||||
scrollEventThrottle={16}
|
flRef={flRef}
|
||||||
windowSize={7}
|
queryKey={queryKey}
|
||||||
data={flattenData}
|
scrollY={scrollY}
|
||||||
initialNumToRender={6}
|
fetchingType={fetchingType}
|
||||||
maxToRenderPerBatch={3}
|
disableRefresh={disableRefresh}
|
||||||
style={styles.flatList}
|
/>
|
||||||
onEndReached={onEndReached}
|
<AnimatedFlatList
|
||||||
onEndReachedThreshold={0.75}
|
ref={customFLRef || flRef}
|
||||||
ListFooterComponent={
|
scrollEventThrottle={16}
|
||||||
<TimelineFooter queryKey={queryKey} disableInfinity={disableInfinity} />
|
onScroll={onScroll}
|
||||||
}
|
windowSize={7}
|
||||||
ListEmptyComponent={<TimelineEmpty queryKey={queryKey} />}
|
data={flattenData}
|
||||||
ItemSeparatorComponent={ItemSeparatorComponent}
|
initialNumToRender={6}
|
||||||
{...(isFetchingPreviousPage && {
|
maxToRenderPerBatch={3}
|
||||||
maintainVisibleContentPosition: { minIndexForVisible: 0 }
|
style={styles.flatList}
|
||||||
})}
|
onEndReached={onEndReached}
|
||||||
refreshing={isFetchingPreviousPage}
|
onEndReachedThreshold={0.75}
|
||||||
onRefresh={() => {
|
ListFooterComponent={
|
||||||
if (!disableRefresh && !isFetchingPreviousPage) {
|
<TimelineFooter
|
||||||
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(
|
queryKey={queryKey}
|
||||||
queryKey,
|
disableInfinity={disableInfinity}
|
||||||
data => {
|
/>
|
||||||
if (data?.pages[0] && data.pages[0].body.length === 0) {
|
|
||||||
return {
|
|
||||||
pages: data.pages.slice(1),
|
|
||||||
pageParams: data.pageParams.slice(1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
fetchPreviousPage()
|
|
||||||
}
|
}
|
||||||
}}
|
ListEmptyComponent={<TimelineEmpty queryKey={queryKey} />}
|
||||||
{...androidRefreshControl}
|
ItemSeparatorComponent={ItemSeparatorComponent}
|
||||||
{...customProps}
|
maintainVisibleContentPosition={{
|
||||||
/>
|
minIndexForVisible: 0
|
||||||
|
}}
|
||||||
|
{...androidRefreshControl}
|
||||||
|
{...customProps}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { isEqual, uniqBy } from 'lodash'
|
import { uniqBy } from 'lodash'
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { Pressable, View } from 'react-native'
|
import { Pressable, View } from 'react-native'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
@ -34,145 +34,136 @@ export interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
// When the poll is long
|
// When the poll is long
|
||||||
const TimelineDefault = React.memo(
|
const TimelineDefault: React.FC<Props> = ({
|
||||||
({
|
item,
|
||||||
item,
|
queryKey,
|
||||||
queryKey,
|
rootQueryKey,
|
||||||
rootQueryKey,
|
origin,
|
||||||
origin,
|
highlighted = false,
|
||||||
highlighted = false,
|
disableDetails = false,
|
||||||
disableDetails = false,
|
disableOnPress = false
|
||||||
disableOnPress = false
|
}) => {
|
||||||
}: Props) => {
|
const { colors } = useTheme()
|
||||||
const { colors } = useTheme()
|
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
||||||
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
const navigation =
|
||||||
const navigation =
|
useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||||
useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
|
||||||
|
|
||||||
const actualStatus = item.reblog ? item.reblog : item
|
const actualStatus = item.reblog ? item.reblog : item
|
||||||
|
|
||||||
const ownAccount = actualStatus.account?.id === instanceAccount?.id
|
const ownAccount = actualStatus.account?.id === instanceAccount?.id
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
!highlighted &&
|
||||||
|
queryKey &&
|
||||||
|
shouldFilter({ status: actualStatus, queryKey })
|
||||||
|
) {
|
||||||
|
return <TimelineFiltered />
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPress = useCallback(() => {
|
||||||
|
analytics('timeline_default_press', {
|
||||||
|
page: queryKey ? queryKey[1].page : origin
|
||||||
|
})
|
||||||
|
!disableOnPress &&
|
||||||
!highlighted &&
|
!highlighted &&
|
||||||
queryKey &&
|
navigation.push('Tab-Shared-Toot', {
|
||||||
shouldFilter({ status: actualStatus, queryKey })
|
toot: actualStatus,
|
||||||
) {
|
rootQueryKey: queryKey
|
||||||
return <TimelineFiltered />
|
|
||||||
}
|
|
||||||
|
|
||||||
const onPress = useCallback(() => {
|
|
||||||
analytics('timeline_default_press', {
|
|
||||||
page: queryKey ? queryKey[1].page : origin
|
|
||||||
})
|
})
|
||||||
!disableOnPress &&
|
}, [])
|
||||||
!highlighted &&
|
|
||||||
navigation.push('Tab-Shared-Toot', {
|
|
||||||
toot: actualStatus,
|
|
||||||
rootQueryKey: queryKey
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
accessible={highlighted ? false : true}
|
accessible={highlighted ? false : true}
|
||||||
|
style={{
|
||||||
|
padding: StyleConstants.Spacing.Global.PagePadding,
|
||||||
|
backgroundColor: colors.backgroundDefault,
|
||||||
|
paddingBottom:
|
||||||
|
disableDetails && disableOnPress
|
||||||
|
? StyleConstants.Spacing.Global.PagePadding
|
||||||
|
: 0
|
||||||
|
}}
|
||||||
|
onPress={onPress}
|
||||||
|
>
|
||||||
|
{item.reblog ? (
|
||||||
|
<TimelineActioned action='reblog' account={item.account} />
|
||||||
|
) : item._pinned ? (
|
||||||
|
<TimelineActioned action='pinned' account={item.account} />
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<View style={{ flex: 1, width: '100%', flexDirection: 'row' }}>
|
||||||
|
<TimelineAvatar
|
||||||
|
queryKey={disableOnPress ? undefined : queryKey}
|
||||||
|
account={actualStatus.account}
|
||||||
|
highlighted={highlighted}
|
||||||
|
/>
|
||||||
|
<TimelineHeaderDefault
|
||||||
|
queryKey={disableOnPress ? undefined : queryKey}
|
||||||
|
rootQueryKey={disableOnPress ? undefined : rootQueryKey}
|
||||||
|
status={actualStatus}
|
||||||
|
highlighted={highlighted}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View
|
||||||
style={{
|
style={{
|
||||||
padding: StyleConstants.Spacing.Global.PagePadding,
|
paddingTop: highlighted ? StyleConstants.Spacing.S : 0,
|
||||||
backgroundColor: colors.backgroundDefault,
|
paddingLeft: highlighted
|
||||||
paddingBottom:
|
? 0
|
||||||
disableDetails && disableOnPress
|
: StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
||||||
? StyleConstants.Spacing.Global.PagePadding
|
|
||||||
: 0
|
|
||||||
}}
|
}}
|
||||||
onPress={onPress}
|
|
||||||
>
|
>
|
||||||
{item.reblog ? (
|
{typeof actualStatus.content === 'string' &&
|
||||||
<TimelineActioned action='reblog' account={item.account} />
|
actualStatus.content.length > 0 ? (
|
||||||
) : item._pinned ? (
|
<TimelineContent
|
||||||
<TimelineActioned action='pinned' account={item.account} />
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<View style={{ flex: 1, width: '100%', flexDirection: 'row' }}>
|
|
||||||
<TimelineAvatar
|
|
||||||
queryKey={disableOnPress ? undefined : queryKey}
|
|
||||||
account={actualStatus.account}
|
|
||||||
highlighted={highlighted}
|
|
||||||
/>
|
|
||||||
<TimelineHeaderDefault
|
|
||||||
queryKey={disableOnPress ? undefined : queryKey}
|
|
||||||
rootQueryKey={disableOnPress ? undefined : rootQueryKey}
|
|
||||||
status={actualStatus}
|
status={actualStatus}
|
||||||
highlighted={highlighted}
|
highlighted={highlighted}
|
||||||
|
disableDetails={disableDetails}
|
||||||
/>
|
/>
|
||||||
</View>
|
) : null}
|
||||||
|
{queryKey && actualStatus.poll ? (
|
||||||
<View
|
<TimelinePoll
|
||||||
style={{
|
|
||||||
paddingTop: highlighted ? StyleConstants.Spacing.S : 0,
|
|
||||||
paddingLeft: highlighted
|
|
||||||
? 0
|
|
||||||
: StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{typeof actualStatus.content === 'string' &&
|
|
||||||
actualStatus.content.length > 0 ? (
|
|
||||||
<TimelineContent
|
|
||||||
status={actualStatus}
|
|
||||||
highlighted={highlighted}
|
|
||||||
disableDetails={disableDetails}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{queryKey && actualStatus.poll ? (
|
|
||||||
<TimelinePoll
|
|
||||||
queryKey={queryKey}
|
|
||||||
rootQueryKey={rootQueryKey}
|
|
||||||
statusId={actualStatus.id}
|
|
||||||
poll={actualStatus.poll}
|
|
||||||
reblog={item.reblog ? true : false}
|
|
||||||
sameAccount={ownAccount}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{!disableDetails &&
|
|
||||||
Array.isArray(actualStatus.media_attachments) &&
|
|
||||||
actualStatus.media_attachments.length ? (
|
|
||||||
<TimelineAttachment status={actualStatus} />
|
|
||||||
) : null}
|
|
||||||
{!disableDetails && actualStatus.card ? (
|
|
||||||
<TimelineCard card={actualStatus.card} />
|
|
||||||
) : null}
|
|
||||||
{!disableDetails ? (
|
|
||||||
<TimelineFullConversation
|
|
||||||
queryKey={queryKey}
|
|
||||||
status={actualStatus}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<TimelineTranslate status={actualStatus} highlighted={highlighted} />
|
|
||||||
<TimelineFeedback status={actualStatus} highlighted={highlighted} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{queryKey && !disableDetails ? (
|
|
||||||
<TimelineActions
|
|
||||||
queryKey={queryKey}
|
queryKey={queryKey}
|
||||||
rootQueryKey={rootQueryKey}
|
rootQueryKey={rootQueryKey}
|
||||||
highlighted={highlighted}
|
statusId={actualStatus.id}
|
||||||
status={actualStatus}
|
poll={actualStatus.poll}
|
||||||
ownAccount={ownAccount}
|
|
||||||
accts={uniqBy(
|
|
||||||
(
|
|
||||||
[actualStatus.account] as Mastodon.Account[] &
|
|
||||||
Mastodon.Mention[]
|
|
||||||
)
|
|
||||||
.concat(actualStatus.mentions)
|
|
||||||
.filter(d => d?.id !== instanceAccount?.id),
|
|
||||||
d => d?.id
|
|
||||||
).map(d => d?.acct)}
|
|
||||||
reblog={item.reblog ? true : false}
|
reblog={item.reblog ? true : false}
|
||||||
|
sameAccount={ownAccount}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</Pressable>
|
{!disableDetails &&
|
||||||
)
|
Array.isArray(actualStatus.media_attachments) &&
|
||||||
},
|
actualStatus.media_attachments.length ? (
|
||||||
(prev, next) => isEqual(prev.item, next.item)
|
<TimelineAttachment status={actualStatus} />
|
||||||
)
|
) : null}
|
||||||
|
{!disableDetails && actualStatus.card ? (
|
||||||
|
<TimelineCard card={actualStatus.card} />
|
||||||
|
) : null}
|
||||||
|
{!disableDetails ? (
|
||||||
|
<TimelineFullConversation queryKey={queryKey} status={actualStatus} />
|
||||||
|
) : null}
|
||||||
|
<TimelineTranslate status={actualStatus} highlighted={highlighted} />
|
||||||
|
<TimelineFeedback status={actualStatus} highlighted={highlighted} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{queryKey && !disableDetails ? (
|
||||||
|
<TimelineActions
|
||||||
|
queryKey={queryKey}
|
||||||
|
rootQueryKey={rootQueryKey}
|
||||||
|
highlighted={highlighted}
|
||||||
|
status={actualStatus}
|
||||||
|
ownAccount={ownAccount}
|
||||||
|
accts={uniqBy(
|
||||||
|
([actualStatus.account] as Mastodon.Account[] & Mastodon.Mention[])
|
||||||
|
.concat(actualStatus.mentions)
|
||||||
|
.filter(d => d?.id !== instanceAccount?.id),
|
||||||
|
d => d?.id
|
||||||
|
).map(d => d?.acct)}
|
||||||
|
reblog={item.reblog ? true : false}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</Pressable>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default TimelineDefault
|
export default TimelineDefault
|
||||||
|
|
|
@ -0,0 +1,323 @@
|
||||||
|
import haptics from '@components/haptics'
|
||||||
|
import Icon from '@components/Icon'
|
||||||
|
import {
|
||||||
|
QueryKeyTimeline,
|
||||||
|
TimelineData,
|
||||||
|
useTimelineQuery
|
||||||
|
} from '@utils/queryHooks/timeline'
|
||||||
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
import React, { RefObject, useCallback, useRef, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { FlatList, Platform, StyleSheet, Text, View } from 'react-native'
|
||||||
|
import { Circle } from 'react-native-animated-spinkit'
|
||||||
|
import Animated, {
|
||||||
|
Extrapolate,
|
||||||
|
interpolate,
|
||||||
|
runOnJS,
|
||||||
|
useAnimatedReaction,
|
||||||
|
useAnimatedStyle,
|
||||||
|
useSharedValue,
|
||||||
|
withTiming
|
||||||
|
} from 'react-native-reanimated'
|
||||||
|
import { InfiniteData, useQueryClient } from 'react-query'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
flRef: RefObject<FlatList<any>>
|
||||||
|
queryKey: QueryKeyTimeline
|
||||||
|
scrollY: Animated.SharedValue<number>
|
||||||
|
fetchingType: Animated.SharedValue<0 | 1 | 2>
|
||||||
|
disableRefresh?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONTAINER_HEIGHT = StyleConstants.Spacing.M * 2.5
|
||||||
|
export const SEPARATION_Y_1 = -(
|
||||||
|
CONTAINER_HEIGHT / 2 +
|
||||||
|
StyleConstants.Font.Size.S / 2
|
||||||
|
)
|
||||||
|
export const SEPARATION_Y_2 = -(
|
||||||
|
CONTAINER_HEIGHT * 1.5 +
|
||||||
|
StyleConstants.Font.Size.S / 2
|
||||||
|
)
|
||||||
|
|
||||||
|
const TimelineRefresh: React.FC<Props> = ({
|
||||||
|
flRef,
|
||||||
|
queryKey,
|
||||||
|
scrollY,
|
||||||
|
fetchingType,
|
||||||
|
disableRefresh = false
|
||||||
|
}) => {
|
||||||
|
if (Platform.OS !== 'ios') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (disableRefresh) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchingLatestIndex = useRef(0)
|
||||||
|
const refetchActive = useRef(false)
|
||||||
|
|
||||||
|
const {
|
||||||
|
refetch,
|
||||||
|
isFetching,
|
||||||
|
isLoading,
|
||||||
|
fetchPreviousPage,
|
||||||
|
hasPreviousPage,
|
||||||
|
isFetchingNextPage
|
||||||
|
} = useTimelineQuery({
|
||||||
|
...queryKey[1],
|
||||||
|
options: {
|
||||||
|
getPreviousPageParam: firstPage =>
|
||||||
|
firstPage?.links?.prev && {
|
||||||
|
min_id: firstPage.links.prev,
|
||||||
|
// https://github.com/facebook/react-native/issues/25239#issuecomment-731100372
|
||||||
|
limit: '3'
|
||||||
|
},
|
||||||
|
select: data => {
|
||||||
|
if (refetchActive.current) {
|
||||||
|
data.pageParams = [data.pageParams[0]]
|
||||||
|
data.pages = [data.pages[0]]
|
||||||
|
refetchActive.current = false
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
if (fetchingLatestIndex.current > 0) {
|
||||||
|
if (fetchingLatestIndex.current > 5) {
|
||||||
|
clearFirstPage()
|
||||||
|
fetchingLatestIndex.current = 0
|
||||||
|
} else {
|
||||||
|
if (hasPreviousPage) {
|
||||||
|
fetchPreviousPage()
|
||||||
|
fetchingLatestIndex.current++
|
||||||
|
} else {
|
||||||
|
clearFirstPage()
|
||||||
|
fetchingLatestIndex.current = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { t } = useTranslation('componentTimeline')
|
||||||
|
const { colors } = useTheme()
|
||||||
|
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
const clearFirstPage = () => {
|
||||||
|
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(
|
||||||
|
queryKey,
|
||||||
|
data => {
|
||||||
|
if (data?.pages[0] && data.pages[0].body.length === 0) {
|
||||||
|
return {
|
||||||
|
pages: data.pages.slice(1),
|
||||||
|
pageParams: data.pageParams.slice(1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const prepareRefetch = () => {
|
||||||
|
refetchActive.current = true
|
||||||
|
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(
|
||||||
|
queryKey,
|
||||||
|
data => {
|
||||||
|
if (data) {
|
||||||
|
data.pageParams = [undefined]
|
||||||
|
const newFirstPage: TimelineData = { body: [] }
|
||||||
|
for (let page of data.pages) {
|
||||||
|
// @ts-ignore
|
||||||
|
newFirstPage.body.push(...page.body)
|
||||||
|
if (newFirstPage.body.length > 10) break
|
||||||
|
}
|
||||||
|
data.pages = [newFirstPage]
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const callRefetch = async () => {
|
||||||
|
await refetch()
|
||||||
|
setTimeout(() => flRef.current?.scrollToOffset({ offset: 1 }), 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
const [textRight, setTextRight] = useState(0)
|
||||||
|
const arrowY = useAnimatedStyle(() => ({
|
||||||
|
transform: [
|
||||||
|
{
|
||||||
|
translateY: interpolate(
|
||||||
|
scrollY.value,
|
||||||
|
[0, SEPARATION_Y_1],
|
||||||
|
[
|
||||||
|
-CONTAINER_HEIGHT / 2 - StyleConstants.Font.Size.M / 2,
|
||||||
|
CONTAINER_HEIGHT / 2 - StyleConstants.Font.Size.S / 2
|
||||||
|
],
|
||||||
|
Extrapolate.CLAMP
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}))
|
||||||
|
const arrowTop = useAnimatedStyle(() => ({
|
||||||
|
marginTop:
|
||||||
|
scrollY.value < SEPARATION_Y_2
|
||||||
|
? withTiming(CONTAINER_HEIGHT)
|
||||||
|
: withTiming(0)
|
||||||
|
}))
|
||||||
|
|
||||||
|
const arrowStage = useSharedValue(0)
|
||||||
|
const onLayout = useCallback(
|
||||||
|
({ nativeEvent }) => {
|
||||||
|
if (nativeEvent.layout.x + nativeEvent.layout.width > textRight) {
|
||||||
|
setTextRight(nativeEvent.layout.x + nativeEvent.layout.width)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[textRight]
|
||||||
|
)
|
||||||
|
useAnimatedReaction(
|
||||||
|
() => {
|
||||||
|
if (isFetching) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch (arrowStage.value) {
|
||||||
|
case 0:
|
||||||
|
if (scrollY.value < SEPARATION_Y_1) {
|
||||||
|
arrowStage.value = 1
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case 1:
|
||||||
|
if (scrollY.value < SEPARATION_Y_2) {
|
||||||
|
arrowStage.value = 2
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (scrollY.value > SEPARATION_Y_1) {
|
||||||
|
arrowStage.value = 0
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case 2:
|
||||||
|
if (scrollY.value > SEPARATION_Y_2) {
|
||||||
|
arrowStage.value = 1
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data => {
|
||||||
|
if (data) {
|
||||||
|
runOnJS(haptics)('Light')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isFetching]
|
||||||
|
)
|
||||||
|
const wrapperStartLatest = () => {
|
||||||
|
fetchingLatestIndex.current = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
useAnimatedReaction(
|
||||||
|
() => {
|
||||||
|
return fetchingType.value
|
||||||
|
},
|
||||||
|
data => {
|
||||||
|
fetchingType.value = 0
|
||||||
|
switch (data) {
|
||||||
|
case 1:
|
||||||
|
runOnJS(wrapperStartLatest)()
|
||||||
|
runOnJS(clearFirstPage)()
|
||||||
|
runOnJS(fetchPreviousPage)()
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
runOnJS(prepareRefetch)()
|
||||||
|
runOnJS(callRefetch)()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const headerPadding = useAnimatedStyle(
|
||||||
|
() => ({
|
||||||
|
paddingTop:
|
||||||
|
fetchingLatestIndex.current !== 0 ||
|
||||||
|
(isFetching && !isLoading && !isFetchingNextPage)
|
||||||
|
? withTiming(StyleConstants.Spacing.M * 2.5)
|
||||||
|
: withTiming(0)
|
||||||
|
}),
|
||||||
|
[fetchingLatestIndex.current, isFetching, isFetchingNextPage, isLoading]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Animated.View style={headerPadding}>
|
||||||
|
<View style={styles.base}>
|
||||||
|
{isFetching ? (
|
||||||
|
<View style={styles.container2}>
|
||||||
|
<Circle
|
||||||
|
size={StyleConstants.Font.Size.L}
|
||||||
|
color={colors.secondary}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<View style={styles.container1}>
|
||||||
|
<Text
|
||||||
|
style={[styles.explanation, { color: colors.primaryDefault }]}
|
||||||
|
onLayout={onLayout}
|
||||||
|
children={t('refresh.fetchPreviousPage')}
|
||||||
|
/>
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
position: 'absolute',
|
||||||
|
left: textRight + StyleConstants.Spacing.S
|
||||||
|
},
|
||||||
|
arrowY,
|
||||||
|
arrowTop
|
||||||
|
]}
|
||||||
|
children={
|
||||||
|
<Icon
|
||||||
|
name='ArrowLeft'
|
||||||
|
size={StyleConstants.Font.Size.M}
|
||||||
|
color={colors.primaryDefault}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.container2}>
|
||||||
|
<Text
|
||||||
|
style={[styles.explanation, { color: colors.primaryDefault }]}
|
||||||
|
onLayout={onLayout}
|
||||||
|
children={t('refresh.refetch')}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
base: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: CONTAINER_HEIGHT * 2,
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
container1: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
height: CONTAINER_HEIGHT
|
||||||
|
},
|
||||||
|
container2: { height: CONTAINER_HEIGHT, justifyContent: 'center' },
|
||||||
|
explanation: {
|
||||||
|
fontSize: StyleConstants.Font.Size.S,
|
||||||
|
lineHeight: CONTAINER_HEIGHT
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default TimelineRefresh
|
|
@ -34,7 +34,7 @@ const TimelineActioned = React.memo(
|
||||||
navigation.push('Tab-Shared-Account', { account })
|
navigation.push('Tab-Shared-Account', { account })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const children = useMemo(() => {
|
const children = () => {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'pinned':
|
case 'pinned':
|
||||||
return (
|
return (
|
||||||
|
@ -48,7 +48,6 @@ const TimelineActioned = React.memo(
|
||||||
{content(t('shared.actioned.pinned'))}
|
{content(t('shared.actioned.pinned'))}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
break
|
|
||||||
case 'favourite':
|
case 'favourite':
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -63,7 +62,6 @@ const TimelineActioned = React.memo(
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
break
|
|
||||||
case 'follow':
|
case 'follow':
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -78,7 +76,6 @@ const TimelineActioned = React.memo(
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
break
|
|
||||||
case 'follow_request':
|
case 'follow_request':
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -93,7 +90,6 @@ const TimelineActioned = React.memo(
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
break
|
|
||||||
case 'poll':
|
case 'poll':
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -106,7 +102,6 @@ const TimelineActioned = React.memo(
|
||||||
{content(t('shared.actioned.poll'))}
|
{content(t('shared.actioned.poll'))}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
break
|
|
||||||
case 'reblog':
|
case 'reblog':
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -125,7 +120,6 @@ const TimelineActioned = React.memo(
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
break
|
|
||||||
case 'status':
|
case 'status':
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -140,9 +134,22 @@ const TimelineActioned = React.memo(
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
break
|
case 'update':
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Icon
|
||||||
|
name='BarChart2'
|
||||||
|
size={StyleConstants.Font.Size.S}
|
||||||
|
color={iconColor}
|
||||||
|
style={styles.icon}
|
||||||
|
/>
|
||||||
|
{content(t('shared.actioned.update'))}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return <></>
|
||||||
}
|
}
|
||||||
}, [])
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
@ -153,8 +160,9 @@ const TimelineActioned = React.memo(
|
||||||
paddingLeft: StyleConstants.Avatar.M - StyleConstants.Font.Size.S,
|
paddingLeft: StyleConstants.Avatar.M - StyleConstants.Font.Size.S,
|
||||||
paddingRight: StyleConstants.Spacing.Global.PagePadding
|
paddingRight: StyleConstants.Spacing.Global.PagePadding
|
||||||
}}
|
}}
|
||||||
children={children}
|
>
|
||||||
/>
|
{children()}
|
||||||
|
</View>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
() => true
|
() => true
|
||||||
|
|
|
@ -8,11 +8,13 @@ import AttachmentVideo from '@components/Timeline/Shared/Attachment/Video'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import { StackNavigationProp } from '@react-navigation/stack'
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||||
|
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||||
import React, { useRef, useState } from 'react'
|
import React, { useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Pressable, View } from 'react-native'
|
import { Pressable, View } from 'react-native'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
status: Pick<Mastodon.Status, 'media_attachments' | 'sensitive'>
|
status: Pick<Mastodon.Status, 'media_attachments' | 'sensitive'>
|
||||||
|
@ -22,7 +24,23 @@ const TimelineAttachment = React.memo(
|
||||||
({ status }: Props) => {
|
({ status }: Props) => {
|
||||||
const { t } = useTranslation('componentTimeline')
|
const { t } = useTranslation('componentTimeline')
|
||||||
|
|
||||||
const [sensitiveShown, setSensitiveShown] = useState(status.sensitive)
|
const account = useSelector(
|
||||||
|
getInstanceAccount,
|
||||||
|
(prev, next) =>
|
||||||
|
prev.preferences['reading:expand:media'] ===
|
||||||
|
next.preferences['reading:expand:media']
|
||||||
|
)
|
||||||
|
const defaultSensitive = () => {
|
||||||
|
switch (account.preferences['reading:expand:media']) {
|
||||||
|
case 'show_all':
|
||||||
|
return false
|
||||||
|
case 'hide_all':
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return status.sensitive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const [sensitiveShown, setSensitiveShown] = useState(defaultSensitive())
|
||||||
|
|
||||||
const imageUrls = useRef<
|
const imageUrls = useRef<
|
||||||
RootStackParamList['Screen-ImagesViewer']['imageUrls']
|
RootStackParamList['Screen-ImagesViewer']['imageUrls']
|
||||||
|
@ -151,7 +169,7 @@ const TimelineAttachment = React.memo(
|
||||||
})}
|
})}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{status.sensitive &&
|
{defaultSensitive() &&
|
||||||
(sensitiveShown ? (
|
(sensitiveShown ? (
|
||||||
<Pressable
|
<Pressable
|
||||||
style={{
|
style={{
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import Button from '@components/Button'
|
||||||
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
|
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||||
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
sensitiveShown: boolean
|
||||||
|
text?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const AttachmentAltText: React.FC<Props> = ({ sensitiveShown, text }) => {
|
||||||
|
if (!text) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>()
|
||||||
|
|
||||||
|
return !sensitiveShown ? (
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
right: StyleConstants.Spacing.S,
|
||||||
|
bottom: StyleConstants.Spacing.S
|
||||||
|
}}
|
||||||
|
overlay
|
||||||
|
size='S'
|
||||||
|
type='text'
|
||||||
|
content='ALT'
|
||||||
|
fontBold
|
||||||
|
onPress={() => {
|
||||||
|
navigation.navigate('Screen-Actions', { type: 'alt_text', text })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AttachmentAltText
|
|
@ -8,6 +8,7 @@ import { Audio } from 'expo-av'
|
||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import { StyleSheet, View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
import { Blurhash } from 'react-native-blurhash'
|
import { Blurhash } from 'react-native-blurhash'
|
||||||
|
import AttachmentAltText from './AltText'
|
||||||
import attachmentAspectRatio from './aspectRatio'
|
import attachmentAspectRatio from './aspectRatio'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
@ -127,6 +128,10 @@ const AttachmentAudio: React.FC<Props> = ({
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
|
<AttachmentAltText
|
||||||
|
sensitiveShown={sensitiveShown}
|
||||||
|
text={audio.description}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import GracefullyImage from '@components/GracefullyImage'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { View } from 'react-native'
|
import { View } from 'react-native'
|
||||||
|
import AttachmentAltText from './AltText'
|
||||||
import attachmentAspectRatio from './aspectRatio'
|
import attachmentAspectRatio from './aspectRatio'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
@ -34,7 +35,9 @@ const AttachmentImage = ({
|
||||||
uri={{ original: image.preview_url, remote: image.remote_url }}
|
uri={{ original: image.preview_url, remote: image.remote_url }}
|
||||||
blurhash={image.blurhash}
|
blurhash={image.blurhash}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
analytics('timeline_shared_attachment_image_press', { id: image.id })
|
analytics('timeline_shared_attachment_image_press', {
|
||||||
|
id: image.id
|
||||||
|
})
|
||||||
navigateToImagesViewer(image.id)
|
navigateToImagesViewer(image.id)
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
|
@ -48,6 +51,10 @@ const AttachmentImage = ({
|
||||||
: image.meta.original.width / image.meta.original.height
|
: image.meta.original.width / image.meta.original.height
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<AttachmentAltText
|
||||||
|
sensitiveShown={sensitiveShown}
|
||||||
|
text={image.description}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { View } from 'react-native'
|
import { View } from 'react-native'
|
||||||
import { Blurhash } from 'react-native-blurhash'
|
import { Blurhash } from 'react-native-blurhash'
|
||||||
|
import AttachmentAltText from './AltText'
|
||||||
import attachmentAspectRatio from './aspectRatio'
|
import attachmentAspectRatio from './aspectRatio'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
@ -75,6 +76,10 @@ const AttachmentUnsupported: React.FC<Props> = ({
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
<AttachmentAltText
|
||||||
|
sensitiveShown={sensitiveShown}
|
||||||
|
text={attachment.description}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,11 @@ import Button from '@components/Button'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { ResizeMode, Video, VideoFullscreenUpdate } from 'expo-av'
|
import { ResizeMode, Video, VideoFullscreenUpdate } from 'expo-av'
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import {
|
import { AppState, AppStateStatus, Pressable, View } from 'react-native'
|
||||||
AppState,
|
|
||||||
AppStateStatus,
|
|
||||||
Pressable,
|
|
||||||
StyleSheet,
|
|
||||||
View
|
|
||||||
} from 'react-native'
|
|
||||||
import { Blurhash } from 'react-native-blurhash'
|
import { Blurhash } from 'react-native-blurhash'
|
||||||
import attachmentAspectRatio from './aspectRatio'
|
import attachmentAspectRatio from './aspectRatio'
|
||||||
import analytics from '@components/analytics'
|
import analytics from '@components/analytics'
|
||||||
|
import AttachmentAltText from './AltText'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
total: number
|
total: number
|
||||||
|
@ -88,10 +83,12 @@ const AttachmentVideo: React.FC<Props> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={{
|
||||||
styles.base,
|
flex: 1,
|
||||||
{ aspectRatio: attachmentAspectRatio({ total, index }) }
|
flexBasis: '50%',
|
||||||
]}
|
padding: StyleConstants.Spacing.XS / 2,
|
||||||
|
aspectRatio: attachmentAspectRatio({ total, index })
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Video
|
<Video
|
||||||
accessibilityLabel={video.description}
|
accessibilityLabel={video.description}
|
||||||
|
@ -127,7 +124,17 @@ const AttachmentVideo: React.FC<Props> = ({
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Pressable style={styles.overlay} onPress={gifv ? playOnPress : null}>
|
<Pressable
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
onPress={gifv ? playOnPress : null}
|
||||||
|
>
|
||||||
{sensitiveShown ? (
|
{sensitiveShown ? (
|
||||||
video.blurhash ? (
|
video.blurhash ? (
|
||||||
<Blurhash
|
<Blurhash
|
||||||
|
@ -149,25 +156,13 @@ const AttachmentVideo: React.FC<Props> = ({
|
||||||
loading={videoLoading}
|
loading={videoLoading}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
<AttachmentAltText
|
||||||
|
sensitiveShown={sensitiveShown}
|
||||||
|
text={video.description}
|
||||||
|
/>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
base: {
|
|
||||||
flex: 1,
|
|
||||||
flexBasis: '50%',
|
|
||||||
padding: StyleConstants.Spacing.XS / 2
|
|
||||||
},
|
|
||||||
overlay: {
|
|
||||||
position: 'absolute',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default AttachmentVideo
|
export default AttachmentVideo
|
||||||
|
|
|
@ -55,7 +55,7 @@ export const shouldFilter = ({
|
||||||
) // $& means the whole matched string
|
) // $& means the whole matched string
|
||||||
switch (filter.whole_word) {
|
switch (filter.whole_word) {
|
||||||
case true:
|
case true:
|
||||||
if (new RegExp('\\B' + escapedPhrase + '\\B').test(text)) {
|
if (new RegExp('\\b' + escapedPhrase + '\\b').test(text)) {
|
||||||
shouldFilter = true
|
shouldFilter = true
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
@ -100,6 +100,7 @@ export const shouldFilter = ({
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
status.spoiler_text && parser.write(status.spoiler_text)
|
||||||
parser.write(status.content)
|
parser.write(status.content)
|
||||||
parser.end()
|
parser.end()
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,9 @@ const TimelineHeaderNotification = ({ queryKey, notification }: Props) => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<HeaderSharedCreated
|
<HeaderSharedCreated
|
||||||
created_at={notification.created_at}
|
created_at={
|
||||||
|
notification.status?.created_at || notification.created_at
|
||||||
|
}
|
||||||
edited_at={notification.status?.edited_at}
|
edited_at={notification.status?.edited_at}
|
||||||
/>
|
/>
|
||||||
{notification.status?.visibility ? (
|
{notification.status?.visibility ? (
|
||||||
|
|
|
@ -145,36 +145,6 @@ const TimelinePoll: React.FC<Props> = ({
|
||||||
mutation.isLoading
|
mutation.isLoading
|
||||||
])
|
])
|
||||||
|
|
||||||
const pollExpiration = useMemo(() => {
|
|
||||||
if (poll.expired) {
|
|
||||||
return (
|
|
||||||
<CustomText fontStyle='S' style={{ color: colors.secondary }}>
|
|
||||||
{t('shared.poll.meta.expiration.expired')}
|
|
||||||
</CustomText>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
if (poll.expires_at) {
|
|
||||||
return (
|
|
||||||
<CustomText fontStyle='S' style={{ color: colors.secondary }}>
|
|
||||||
<Trans
|
|
||||||
i18nKey='componentTimeline:shared.poll.meta.expiration.until'
|
|
||||||
components={[
|
|
||||||
<FormattedRelativeTime
|
|
||||||
value={
|
|
||||||
(new Date(poll.expires_at).getTime() -
|
|
||||||
new Date().getTime()) /
|
|
||||||
1000
|
|
||||||
}
|
|
||||||
updateIntervalInSeconds={1}
|
|
||||||
/>
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</CustomText>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [theme, i18n.language, poll.expired, poll.expires_at])
|
|
||||||
|
|
||||||
const isSelected = useCallback(
|
const isSelected = useCallback(
|
||||||
(index: number): string =>
|
(index: number): string =>
|
||||||
allOptions[index]
|
allOptions[index]
|
||||||
|
@ -302,21 +272,38 @@ const TimelinePoll: React.FC<Props> = ({
|
||||||
const pollVoteCounts = useMemo(() => {
|
const pollVoteCounts = useMemo(() => {
|
||||||
if (poll.voters_count !== null) {
|
if (poll.voters_count !== null) {
|
||||||
return (
|
return (
|
||||||
<CustomText fontStyle='S' style={{ color: colors.secondary }}>
|
t('shared.poll.meta.count.voters', { count: poll.voters_count }) + ' • '
|
||||||
{t('shared.poll.meta.count.voters', { count: poll.voters_count })}
|
|
||||||
{' • '}
|
|
||||||
</CustomText>
|
|
||||||
)
|
)
|
||||||
} else if (poll.votes_count !== null) {
|
} else if (poll.votes_count !== null) {
|
||||||
return (
|
return (
|
||||||
<CustomText fontStyle='S' style={{ color: colors.secondary }}>
|
t('shared.poll.meta.count.votes', { count: poll.votes_count }) + ' • '
|
||||||
{t('shared.poll.meta.count.votes', { count: poll.votes_count })}
|
|
||||||
{' • '}
|
|
||||||
</CustomText>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, [poll.voters_count, poll.votes_count])
|
}, [poll.voters_count, poll.votes_count])
|
||||||
|
|
||||||
|
const pollExpiration = useMemo(() => {
|
||||||
|
if (poll.expired) {
|
||||||
|
return t('shared.poll.meta.expiration.expired')
|
||||||
|
} else {
|
||||||
|
if (poll.expires_at) {
|
||||||
|
return (
|
||||||
|
<Trans
|
||||||
|
i18nKey='componentTimeline:shared.poll.meta.expiration.until'
|
||||||
|
components={[
|
||||||
|
<FormattedRelativeTime
|
||||||
|
value={
|
||||||
|
(new Date(poll.expires_at).getTime() - new Date().getTime()) /
|
||||||
|
1000
|
||||||
|
}
|
||||||
|
updateIntervalInSeconds={1}
|
||||||
|
/>
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [theme, i18n.language, poll.expired, poll.expires_at])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ marginTop: StyleConstants.Spacing.M }}>
|
<View style={{ marginTop: StyleConstants.Spacing.M }}>
|
||||||
{poll.expired || poll.voted ? pollBodyDisallow : pollBodyAllow}
|
{poll.expired || poll.voted ? pollBodyDisallow : pollBodyAllow}
|
||||||
|
@ -329,8 +316,13 @@ const TimelinePoll: React.FC<Props> = ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{pollButton}
|
{pollButton}
|
||||||
{pollVoteCounts}
|
<CustomText
|
||||||
{pollExpiration}
|
fontStyle='S'
|
||||||
|
style={{ flexShrink: 1, color: colors.secondary }}
|
||||||
|
>
|
||||||
|
{pollVoteCounts}
|
||||||
|
{pollExpiration}
|
||||||
|
</CustomText>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,5 +8,20 @@
|
||||||
"feature": "deprecate_auth_follow",
|
"feature": "deprecate_auth_follow",
|
||||||
"version": 3.5,
|
"version": 3.5,
|
||||||
"reference": "https://github.com/mastodon/mastodon/releases/tag/v3.5.0"
|
"reference": "https://github.com/mastodon/mastodon/releases/tag/v3.5.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"feature": "notification_type_status",
|
||||||
|
"version": 3.3,
|
||||||
|
"reference": "https://docs.joinmastodon.org/entities/notification/#required-attributes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"feature": "notification_type_update",
|
||||||
|
"version": 3.5,
|
||||||
|
"reference": "https://github.com/mastodon/mastodon/releases/tag/v3.5.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"feature": "notification_types_positive_filter",
|
||||||
|
"version": 3.5,
|
||||||
|
"reference": "https://github.com/mastodon/mastodon/releases/tag/v3.5.0"
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"OK": "",
|
"OK": "OK",
|
||||||
"apply": "Übernehmen",
|
"apply": "Übernehmen",
|
||||||
"cancel": "Abbrechen"
|
"cancel": "Abbrechen"
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"options": {
|
"options": {
|
||||||
"library": "Hochladen",
|
"library": "Hochladen",
|
||||||
"photo": "Bild aufnehmen",
|
"photo": "Bild aufnehmen",
|
||||||
"cancel": ""
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
"alert": {
|
"alert": {
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
"message": "Für den Upload ist eine Zugriffsgenehmigung erforderlich",
|
"message": "Für den Upload ist eine Zugriffsgenehmigung erforderlich",
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"settings": "Einstellungen bestätigen",
|
"settings": "Einstellungen bestätigen",
|
||||||
"cancel": ""
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
"message": "Zugriff auf die Kamera erforderlich",
|
"message": "Zugriff auf die Kamera erforderlich",
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"settings": "Einstellungen übernehmen",
|
"settings": "Einstellungen übernehmen",
|
||||||
"cancel": ""
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,8 @@
|
||||||
"reblog": {
|
"reblog": {
|
||||||
"default": "{{name}} hat geboostet",
|
"default": "{{name}} hat geboostet",
|
||||||
"notification": "{{name}} hat deinen Tröt geboostet"
|
"notification": "{{name}} hat deinen Tröt geboostet"
|
||||||
}
|
},
|
||||||
|
"update": ""
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"reply": {
|
"reply": {
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
{
|
{
|
||||||
"content": {
|
"content": {
|
||||||
"button": {
|
"altText": {
|
||||||
"apply": "$t(common:buttons.apply)",
|
"heading": ""
|
||||||
"cancel": "$t(common:buttons.cancel)"
|
|
||||||
},
|
},
|
||||||
"notificationsFilter": {
|
"notificationsFilter": {
|
||||||
"heading": "Benachrichtigungsart anzeigen",
|
"heading": "Benachrichtigungsart anzeigen",
|
||||||
"content": {
|
"content": {
|
||||||
"follow": "$t(screenTabs:me.push.follow.heading)",
|
"follow": "$t(screenTabs:me.push.follow.heading)",
|
||||||
|
"follow_request": "Followeranfrage",
|
||||||
"favourite": "$t(screenTabs:me.push.favourite.heading)",
|
"favourite": "$t(screenTabs:me.push.favourite.heading)",
|
||||||
"reblog": "$t(screenTabs:me.push.reblog.heading)",
|
"reblog": "$t(screenTabs:me.push.reblog.heading)",
|
||||||
"mention": "$t(screenTabs:me.push.mention.heading)",
|
"mention": "$t(screenTabs:me.push.mention.heading)",
|
||||||
"poll": "$t(screenTabs:me.push.poll.heading)",
|
"poll": "$t(screenTabs:me.push.poll.heading)",
|
||||||
"follow_request": "Followeranfrage"
|
"status": "",
|
||||||
|
"update": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,6 +168,7 @@
|
||||||
"header": {
|
"header": {
|
||||||
"title": "Entwurf"
|
"title": "Entwurf"
|
||||||
},
|
},
|
||||||
|
"warning": "",
|
||||||
"content": {
|
"content": {
|
||||||
"accessibilityHint": "Gespeicherter Entwurf, tippe, um diesen zu bearbeiten",
|
"accessibilityHint": "Gespeicherter Entwurf, tippe, um diesen zu bearbeiten",
|
||||||
"textEmpty": "Kein Inhalt"
|
"textEmpty": "Kein Inhalt"
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
"options": {
|
"options": {
|
||||||
"save": "Bild speichern",
|
"save": "Bild speichern",
|
||||||
"share": "Bild teilen",
|
"share": "Bild teilen",
|
||||||
"cancel": ""
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
},
|
},
|
||||||
"save": {
|
"save": {
|
||||||
"succeed": "Bild gespeichert",
|
"succeed": "Bild gespeichert",
|
||||||
|
|
|
@ -43,6 +43,9 @@
|
||||||
"fontSize": {
|
"fontSize": {
|
||||||
"name": "Schriftgröße"
|
"name": "Schriftgröße"
|
||||||
},
|
},
|
||||||
|
"language": {
|
||||||
|
"name": "Sprache"
|
||||||
|
},
|
||||||
"lists": {
|
"lists": {
|
||||||
"name": "Listen"
|
"name": "Listen"
|
||||||
},
|
},
|
||||||
|
@ -141,7 +144,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"group": "",
|
"group": "Gruppe {{index}}",
|
||||||
"label": "Kennzeichnung",
|
"label": "Kennzeichnung",
|
||||||
"content": "Inhalt"
|
"content": "Inhalt"
|
||||||
}
|
}
|
||||||
|
@ -166,6 +169,9 @@
|
||||||
"follow": {
|
"follow": {
|
||||||
"heading": "Neue Follower"
|
"heading": "Neue Follower"
|
||||||
},
|
},
|
||||||
|
"follow_request": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
"favourite": {
|
"favourite": {
|
||||||
"heading": "Favoriten"
|
"heading": "Favoriten"
|
||||||
},
|
},
|
||||||
|
@ -178,6 +184,9 @@
|
||||||
"poll": {
|
"poll": {
|
||||||
"heading": "Umfrageupdate"
|
"heading": "Umfrageupdate"
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
"howitworks": "Erfahre, wie das Routing funktioniert"
|
"howitworks": "Erfahre, wie das Routing funktioniert"
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
|
@ -221,7 +230,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"heading": "Sprache",
|
"heading": "$t(me.stacks.language.name)",
|
||||||
"options": {
|
"options": {
|
||||||
"cancel": "$t(common:buttons.cancel)"
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,8 @@
|
||||||
"reblog": {
|
"reblog": {
|
||||||
"default": "{{name}} boosted",
|
"default": "{{name}} boosted",
|
||||||
"notification": "{{name}} boosted your toot"
|
"notification": "{{name}} boosted your toot"
|
||||||
}
|
},
|
||||||
|
"update": "Reblog has been edited"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"reply": {
|
"reply": {
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
{
|
{
|
||||||
"content": {
|
"content": {
|
||||||
"button": {
|
"altText": {
|
||||||
"apply": "$t(common:buttons.apply)",
|
"heading": "Alternative Text"
|
||||||
"cancel": "$t(common:buttons.cancel)"
|
|
||||||
},
|
},
|
||||||
"notificationsFilter": {
|
"notificationsFilter": {
|
||||||
"heading": "Show notification types",
|
"heading": "Show notification types",
|
||||||
"content": {
|
"content": {
|
||||||
"follow": "$t(screenTabs:me.push.follow.heading)",
|
"follow": "$t(screenTabs:me.push.follow.heading)",
|
||||||
|
"follow_request": "Follow request",
|
||||||
"favourite": "$t(screenTabs:me.push.favourite.heading)",
|
"favourite": "$t(screenTabs:me.push.favourite.heading)",
|
||||||
"reblog": "$t(screenTabs:me.push.reblog.heading)",
|
"reblog": "$t(screenTabs:me.push.reblog.heading)",
|
||||||
"mention": "$t(screenTabs:me.push.mention.heading)",
|
"mention": "$t(screenTabs:me.push.mention.heading)",
|
||||||
"poll": "$t(screenTabs:me.push.poll.heading)",
|
"poll": "$t(screenTabs:me.push.poll.heading)",
|
||||||
"follow_request": "Follow request"
|
"status": "Toot from subscribed users",
|
||||||
|
"update": "Reblog has been edited"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,6 +168,7 @@
|
||||||
"header": {
|
"header": {
|
||||||
"title": "Draft"
|
"title": "Draft"
|
||||||
},
|
},
|
||||||
|
"warning": "Drafts are only stored locally, and can be lost in unfortunate events. Advise not using drafts for long term storage.",
|
||||||
"content": {
|
"content": {
|
||||||
"accessibilityHint": "Saved draft, tap to edit this draft",
|
"accessibilityHint": "Saved draft, tap to edit this draft",
|
||||||
"textEmpty": "Content empty"
|
"textEmpty": "Content empty"
|
||||||
|
|
|
@ -43,6 +43,9 @@
|
||||||
"fontSize": {
|
"fontSize": {
|
||||||
"name": "Toot Font Size"
|
"name": "Toot Font Size"
|
||||||
},
|
},
|
||||||
|
"language": {
|
||||||
|
"name": "Language"
|
||||||
|
},
|
||||||
"lists": {
|
"lists": {
|
||||||
"name": "Lists"
|
"name": "Lists"
|
||||||
},
|
},
|
||||||
|
@ -166,6 +169,9 @@
|
||||||
"follow": {
|
"follow": {
|
||||||
"heading": "New follower"
|
"heading": "New follower"
|
||||||
},
|
},
|
||||||
|
"follow_request": {
|
||||||
|
"heading": "Follow request"
|
||||||
|
},
|
||||||
"favourite": {
|
"favourite": {
|
||||||
"heading": "Favourited"
|
"heading": "Favourited"
|
||||||
},
|
},
|
||||||
|
@ -178,6 +184,9 @@
|
||||||
"poll": {
|
"poll": {
|
||||||
"heading": "Poll updates"
|
"heading": "Poll updates"
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
"heading": "Toot from subscribed users"
|
||||||
|
},
|
||||||
"howitworks": "Learn how routing works"
|
"howitworks": "Learn how routing works"
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
|
@ -221,7 +230,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"heading": "Language",
|
"heading": "$t(me.stacks.language.name)",
|
||||||
"options": {
|
"options": {
|
||||||
"cancel": "$t(common:buttons.cancel)"
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,50 @@ import de from '@root/i18n/de/_all'
|
||||||
import en from '@root/i18n/en/_all'
|
import en from '@root/i18n/en/_all'
|
||||||
import it from '@root/i18n/it/_all'
|
import it from '@root/i18n/it/_all'
|
||||||
import ko from '@root/i18n/ko/_all'
|
import ko from '@root/i18n/ko/_all'
|
||||||
|
import pt_BR from '@root/i18n/pt_BR/_all'
|
||||||
import vi from '@root/i18n/vi/_all'
|
import vi from '@root/i18n/vi/_all'
|
||||||
import zh_Hans from '@root/i18n/zh-Hans/_all'
|
import zh_Hans from '@root/i18n/zh-Hans/_all'
|
||||||
|
|
||||||
|
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/it'
|
||||||
|
import '@formatjs/intl-pluralrules/locale-data/ko'
|
||||||
|
import '@formatjs/intl-pluralrules/locale-data/pt'
|
||||||
|
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/it'
|
||||||
|
import '@formatjs/intl-numberformat/locale-data/ko'
|
||||||
|
import '@formatjs/intl-numberformat/locale-data/pt'
|
||||||
|
import '@formatjs/intl-numberformat/locale-data/vi'
|
||||||
|
import '@formatjs/intl-numberformat/locale-data/zh-Hans'
|
||||||
|
|
||||||
|
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/it'
|
||||||
|
import '@formatjs/intl-datetimeformat/locale-data/ko'
|
||||||
|
import '@formatjs/intl-datetimeformat/locale-data/pt'
|
||||||
|
import '@formatjs/intl-datetimeformat/locale-data/vi'
|
||||||
|
import '@formatjs/intl-datetimeformat/locale-data/zh-Hans'
|
||||||
|
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/it'
|
||||||
|
import '@formatjs/intl-relativetimeformat/locale-data/ko'
|
||||||
|
import '@formatjs/intl-relativetimeformat/locale-data/pt'
|
||||||
|
import '@formatjs/intl-relativetimeformat/locale-data/vi'
|
||||||
|
import '@formatjs/intl-relativetimeformat/locale-data/zh-Hans'
|
||||||
|
|
||||||
i18n.use(initReactI18next).init({
|
i18n.use(initReactI18next).init({
|
||||||
lng: 'en',
|
lng: 'en',
|
||||||
fallbackLng: 'en',
|
fallbackLng: 'en',
|
||||||
|
@ -15,7 +56,7 @@ i18n.use(initReactI18next).init({
|
||||||
ns: ['common'],
|
ns: ['common'],
|
||||||
defaultNS: 'common',
|
defaultNS: 'common',
|
||||||
|
|
||||||
resources: { 'zh-Hans': zh_Hans, vi, ko, it, en, de },
|
resources: { 'zh-Hans': zh_Hans, vi, 'pt-BR': pt_BR, ko, it, en, de },
|
||||||
returnEmptyString: false,
|
returnEmptyString: false,
|
||||||
|
|
||||||
saveMissing: true,
|
saveMissing: true,
|
||||||
|
|
|
@ -29,7 +29,8 @@
|
||||||
"reblog": {
|
"reblog": {
|
||||||
"default": "{{name}} ha ricondiviso",
|
"default": "{{name}} ha ricondiviso",
|
||||||
"notification": "{{name}} ha ricondiviso il tuo toot"
|
"notification": "{{name}} ha ricondiviso il tuo toot"
|
||||||
}
|
},
|
||||||
|
"update": ""
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"reply": {
|
"reply": {
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
{
|
{
|
||||||
"content": {
|
"content": {
|
||||||
"button": {
|
"altText": {
|
||||||
"apply": "$t(common:buttons.apply)",
|
"heading": ""
|
||||||
"cancel": "$t(common:buttons.cancel)"
|
|
||||||
},
|
},
|
||||||
"notificationsFilter": {
|
"notificationsFilter": {
|
||||||
"heading": "Filtra notifiche per tipo",
|
"heading": "Filtra notifiche per tipo",
|
||||||
"content": {
|
"content": {
|
||||||
"follow": "$t(screenTabs:me.push.follow.heading)",
|
"follow": "$t(screenTabs:me.push.follow.heading)",
|
||||||
|
"follow_request": "Richieste di seguirti",
|
||||||
"favourite": "$t(screenTabs:me.push.favourite.heading)",
|
"favourite": "$t(screenTabs:me.push.favourite.heading)",
|
||||||
"reblog": "$t(screenTabs:me.push.reblog.heading)",
|
"reblog": "$t(screenTabs:me.push.reblog.heading)",
|
||||||
"mention": "$t(screenTabs:me.push.mention.heading)",
|
"mention": "$t(screenTabs:me.push.mention.heading)",
|
||||||
"poll": "$t(screenTabs:me.push.poll.heading)",
|
"poll": "$t(screenTabs:me.push.poll.heading)",
|
||||||
"follow_request": "Richieste di seguirti"
|
"status": "",
|
||||||
|
"update": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,6 +168,7 @@
|
||||||
"header": {
|
"header": {
|
||||||
"title": "Bozza"
|
"title": "Bozza"
|
||||||
},
|
},
|
||||||
|
"warning": "",
|
||||||
"content": {
|
"content": {
|
||||||
"accessibilityHint": "Bozza salvata, premi per modificarla",
|
"accessibilityHint": "Bozza salvata, premi per modificarla",
|
||||||
"textEmpty": "Testo vuoto"
|
"textEmpty": "Testo vuoto"
|
||||||
|
|
|
@ -43,6 +43,9 @@
|
||||||
"fontSize": {
|
"fontSize": {
|
||||||
"name": "Grandezza del testo dei toot"
|
"name": "Grandezza del testo dei toot"
|
||||||
},
|
},
|
||||||
|
"language": {
|
||||||
|
"name": "Lingua"
|
||||||
|
},
|
||||||
"lists": {
|
"lists": {
|
||||||
"name": "Liste"
|
"name": "Liste"
|
||||||
},
|
},
|
||||||
|
@ -166,6 +169,9 @@
|
||||||
"follow": {
|
"follow": {
|
||||||
"heading": "Nuovi seguaci"
|
"heading": "Nuovi seguaci"
|
||||||
},
|
},
|
||||||
|
"follow_request": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
"favourite": {
|
"favourite": {
|
||||||
"heading": "Apprezzamenti"
|
"heading": "Apprezzamenti"
|
||||||
},
|
},
|
||||||
|
@ -178,6 +184,9 @@
|
||||||
"poll": {
|
"poll": {
|
||||||
"heading": "Novità sui sondaggi"
|
"heading": "Novità sui sondaggi"
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
"howitworks": "Scopri come funziona il traversamento dei messaggi"
|
"howitworks": "Scopri come funziona il traversamento dei messaggi"
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
|
@ -221,7 +230,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"heading": "Lingua",
|
"heading": "$t(me.stacks.language.name)",
|
||||||
"options": {
|
"options": {
|
||||||
"cancel": "$t(common:buttons.cancel)"
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"OK": "",
|
"OK": "확인",
|
||||||
"apply": "적용",
|
"apply": "적용",
|
||||||
"cancel": "취소"
|
"cancel": "취소"
|
||||||
},
|
},
|
||||||
|
|
|
@ -29,7 +29,8 @@
|
||||||
"reblog": {
|
"reblog": {
|
||||||
"default": "{{name}}님이 부스트했어요",
|
"default": "{{name}}님이 부스트했어요",
|
||||||
"notification": "{{name}}이 내 툿을 부스트했어요"
|
"notification": "{{name}}이 내 툿을 부스트했어요"
|
||||||
}
|
},
|
||||||
|
"update": ""
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"reply": {
|
"reply": {
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
{
|
{
|
||||||
"content": {
|
"content": {
|
||||||
"button": {
|
"altText": {
|
||||||
"apply": "$t(common:buttons.apply)",
|
"heading": ""
|
||||||
"cancel": "$t(common:buttons.cancel)"
|
|
||||||
},
|
},
|
||||||
"notificationsFilter": {
|
"notificationsFilter": {
|
||||||
"heading": "알림 종류 표시",
|
"heading": "알림 종류 표시",
|
||||||
"content": {
|
"content": {
|
||||||
"follow": "$t(screenTabs:me.push.follow.heading)",
|
"follow": "$t(screenTabs:me.push.follow.heading)",
|
||||||
|
"follow_request": "팔로우 요청",
|
||||||
"favourite": "$t(screenTabs:me.push.favourite.heading)",
|
"favourite": "$t(screenTabs:me.push.favourite.heading)",
|
||||||
"reblog": "$t(screenTabs:me.push.reblog.heading)",
|
"reblog": "$t(screenTabs:me.push.reblog.heading)",
|
||||||
"mention": "$t(screenTabs:me.push.mention.heading)",
|
"mention": "$t(screenTabs:me.push.mention.heading)",
|
||||||
"poll": "$t(screenTabs:me.push.poll.heading)",
|
"poll": "$t(screenTabs:me.push.poll.heading)",
|
||||||
"follow_request": "팔로우 요청"
|
"status": "",
|
||||||
|
"update": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,6 +168,7 @@
|
||||||
"header": {
|
"header": {
|
||||||
"title": "초안"
|
"title": "초안"
|
||||||
},
|
},
|
||||||
|
"warning": "",
|
||||||
"content": {
|
"content": {
|
||||||
"accessibilityHint": "저장된 초안, 수정하려면 탭하세요",
|
"accessibilityHint": "저장된 초안, 수정하려면 탭하세요",
|
||||||
"textEmpty": "콘텐츠 빔"
|
"textEmpty": "콘텐츠 빔"
|
||||||
|
|
|
@ -43,6 +43,9 @@
|
||||||
"fontSize": {
|
"fontSize": {
|
||||||
"name": "툿 폰트 크기"
|
"name": "툿 폰트 크기"
|
||||||
},
|
},
|
||||||
|
"language": {
|
||||||
|
"name": "언어"
|
||||||
|
},
|
||||||
"lists": {
|
"lists": {
|
||||||
"name": "목록"
|
"name": "목록"
|
||||||
},
|
},
|
||||||
|
@ -166,6 +169,9 @@
|
||||||
"follow": {
|
"follow": {
|
||||||
"heading": "새 팔로워"
|
"heading": "새 팔로워"
|
||||||
},
|
},
|
||||||
|
"follow_request": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
"favourite": {
|
"favourite": {
|
||||||
"heading": "즐겨찾기됨"
|
"heading": "즐겨찾기됨"
|
||||||
},
|
},
|
||||||
|
@ -178,6 +184,9 @@
|
||||||
"poll": {
|
"poll": {
|
||||||
"heading": "투표 업데이트"
|
"heading": "투표 업데이트"
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
"howitworks": "라우팅 방법 알아보기"
|
"howitworks": "라우팅 방법 알아보기"
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
|
@ -221,7 +230,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"heading": "언어",
|
"heading": "$t(me.stacks.language.name)",
|
||||||
"options": {
|
"options": {
|
||||||
"cancel": "$t(common:buttons.cancel)"
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ const LOCALES = {
|
||||||
en: 'English',
|
en: 'English',
|
||||||
it: 'Italiano',
|
it: 'Italiano',
|
||||||
ko: '한국어',
|
ko: '한국어',
|
||||||
|
'pt-BR': 'Português (Brasil)',
|
||||||
vi: 'Tiếng Việt',
|
vi: 'Tiếng Việt',
|
||||||
'zh-Hans': '简体中文'
|
'zh-Hans': '简体中文'
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
export default {
|
||||||
|
common: require('./common'),
|
||||||
|
|
||||||
|
screens: require('./screens'),
|
||||||
|
screenActions: require('./screens/actions'),
|
||||||
|
screenAnnouncements: require('./screens/announcements'),
|
||||||
|
screenCompose: require('./screens/compose'),
|
||||||
|
screenImageViewer: require('./screens/imageViewer'),
|
||||||
|
screenTabs: require('./screens/tabs'),
|
||||||
|
|
||||||
|
componentEmojis: require('./components/emojis'),
|
||||||
|
componentInstance: require('./components/instance'),
|
||||||
|
componentMediaSelector: require('./components/mediaSelector'),
|
||||||
|
componentParse: require('./components/parse'),
|
||||||
|
componentRelationship: require('./components/relationship'),
|
||||||
|
componentTimeline: require('./components/timeline')
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"buttons": {
|
||||||
|
"OK": "OK",
|
||||||
|
"apply": "Aplicar",
|
||||||
|
"cancel": "Cancelar"
|
||||||
|
},
|
||||||
|
"customEmoji": {
|
||||||
|
"accessibilityLabel": "Emoji personalizado {{emoji}}"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"success": {
|
||||||
|
"message": "{{function}} com sucesso"
|
||||||
|
},
|
||||||
|
"warning": {
|
||||||
|
"message": ""
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"message": "{{function}} falhou, por favor, tente novamente"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"separator": ", "
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"frequentUsed": "Usados frequentemente"
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"server": {
|
||||||
|
"textInput": {
|
||||||
|
"placeholder": "Domínio da instância"
|
||||||
|
},
|
||||||
|
"button": "Entrar",
|
||||||
|
"information": {
|
||||||
|
"name": "Nome",
|
||||||
|
"accounts": "Usuários",
|
||||||
|
"statuses": "Toots",
|
||||||
|
"domains": "Universo"
|
||||||
|
},
|
||||||
|
"disclaimer": {
|
||||||
|
"base": "O processo de login usa o navegador do sistema e as informações da sua conta não estarão visíveis para o aplicativo Tooot. Consulte Mais informação ",
|
||||||
|
"privacy": "política de privacidade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"alert": {
|
||||||
|
"title": "Conectado a esta instância",
|
||||||
|
"message": "Você pode fazer login em outra conta mantendo a conta existente logada",
|
||||||
|
"buttons": {
|
||||||
|
"cancel": "$t(common:buttons.cancel)",
|
||||||
|
"continue": "Continuar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"title": "Selecionar fonte de mídia",
|
||||||
|
"options": {
|
||||||
|
"library": "Carregar da biblioteca",
|
||||||
|
"photo": "Tirar foto",
|
||||||
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
|
},
|
||||||
|
"library": {
|
||||||
|
"alert": {
|
||||||
|
"title": "Sem permissão",
|
||||||
|
"message": "Exigir permissão de leitura da biblioteca de fotos para fazer upload",
|
||||||
|
"buttons": {
|
||||||
|
"settings": "Atualizar configurações",
|
||||||
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"photo": {
|
||||||
|
"alert": {
|
||||||
|
"title": "Sem permissão",
|
||||||
|
"message": "Requer permissão de uso da câmera para fazer upload",
|
||||||
|
"buttons": {
|
||||||
|
"settings": "Atualizar configurações",
|
||||||
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"HTML": {
|
||||||
|
"expanded": {
|
||||||
|
"true": "Fechar {{hint}}",
|
||||||
|
"false": "Expandir {{hint}}"
|
||||||
|
},
|
||||||
|
"defaultHint": "artigo"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"follow": {
|
||||||
|
"function": "Seguir usuário"
|
||||||
|
},
|
||||||
|
"block": {
|
||||||
|
"function": "Bloquear usuário"
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"error": "Erro ao carregar",
|
||||||
|
"blocked_by": "Bloqueado pelo usuário",
|
||||||
|
"blocking": "Desbloquear",
|
||||||
|
"following": "Deixar de seguir",
|
||||||
|
"requested": "Cancelar a solicitação",
|
||||||
|
"default": "Seguir"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,235 @@
|
||||||
|
{
|
||||||
|
"empty": {
|
||||||
|
"error": {
|
||||||
|
"message": "Erro ao carregar",
|
||||||
|
"button": "Tentar novamente"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"message": "Linha do tempo vazia"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"message": "Que tal um copo de <0 />"
|
||||||
|
},
|
||||||
|
"lookback": {
|
||||||
|
"message": "Última leitura em"
|
||||||
|
},
|
||||||
|
"refresh": {
|
||||||
|
"fetchPreviousPage": "Mais novo aqui",
|
||||||
|
"refetch": "Mais recente"
|
||||||
|
},
|
||||||
|
"shared": {
|
||||||
|
"actioned": {
|
||||||
|
"pinned": "Fixado",
|
||||||
|
"favourite": "{{name}} favoritou seu toot",
|
||||||
|
"status": "{{name}} acabou de postar",
|
||||||
|
"follow": "{{name}} seguiu você",
|
||||||
|
"follow_request": "{{name}} pediu para te seguir",
|
||||||
|
"poll": "Uma enquete em que você votou terminou",
|
||||||
|
"reblog": {
|
||||||
|
"default": "{{name}} boostou",
|
||||||
|
"notification": "{{name}} deu boost no teu toot"
|
||||||
|
},
|
||||||
|
"update": ""
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"reply": {
|
||||||
|
"accessibilityLabel": "Responder a este toot"
|
||||||
|
},
|
||||||
|
"reblogged": {
|
||||||
|
"accessibilityLabel": "Boost este toot",
|
||||||
|
"function": "Boost toot"
|
||||||
|
},
|
||||||
|
"favourited": {
|
||||||
|
"accessibilityLabel": "Adicionar este toot aos favoritos",
|
||||||
|
"function": "Toot favorito"
|
||||||
|
},
|
||||||
|
"bookmarked": {
|
||||||
|
"accessibilityLabel": "Adicionar este toot aos favoritos",
|
||||||
|
"function": "Favoritos"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actionsUsers": {
|
||||||
|
"reblogged_by": {
|
||||||
|
"accessibilityLabel": "{{count}} usuários compartilharam este toot",
|
||||||
|
"accessibilityHint": "Toque para conhecer os usuários",
|
||||||
|
"text": "$t(screenTabs:shared.users.statuses.reblogged_by)"
|
||||||
|
},
|
||||||
|
"favourited_by": {
|
||||||
|
"accessibilityLabel": "{{count}} usuários favoritaram este toot",
|
||||||
|
"accessibilityHint": "Toque para conhecer os usuários",
|
||||||
|
"text": "$t(screenTabs:shared.users.statuses.favourited_by)"
|
||||||
|
},
|
||||||
|
"history": {
|
||||||
|
"accessibilityLabel": "Este toot foi editado {{count}} vezes",
|
||||||
|
"accessibilityHint": "Toque para ver o histórico completo de edição",
|
||||||
|
"text_one": "{{count}} edição",
|
||||||
|
"text_other": "{{count}} edições"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"attachment": {
|
||||||
|
"sensitive": {
|
||||||
|
"button": "Mostrar mídia sensível"
|
||||||
|
},
|
||||||
|
"unsupported": {
|
||||||
|
"text": "Erro ao carregar",
|
||||||
|
"button": "Experimente o link remoto"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"avatar": {
|
||||||
|
"accessibilityLabel": "Avatar de {{name}}",
|
||||||
|
"accessibilityHint": "Toque para ir à página de {{name}}"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"expandHint": "conteúdo oculto"
|
||||||
|
},
|
||||||
|
"filtered": "Filtrado",
|
||||||
|
"fullConversation": "Ler conversas",
|
||||||
|
"translate": {
|
||||||
|
"default": "Traduzir",
|
||||||
|
"succeed": "Traduzido por {{provider}} de {{source}}",
|
||||||
|
"failed": "Falha na tradução",
|
||||||
|
"source_not_supported": "idioma do toot não é suportado",
|
||||||
|
"target_not_supported": "O idioma de destino não é suportado"
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"shared": {
|
||||||
|
"account": {
|
||||||
|
"name": {
|
||||||
|
"accessibilityHint": "Nome de exibição"
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"accessibilityHint": "Conta do usuário"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"application": "Tooted com {{application}}",
|
||||||
|
"edited": {
|
||||||
|
"accessibilityLabel": "Toot editado"
|
||||||
|
},
|
||||||
|
"muted": {
|
||||||
|
"accessibilityLabel": "Toot silenciado"
|
||||||
|
},
|
||||||
|
"visibility": {
|
||||||
|
"direct": {
|
||||||
|
"accessibilityLabel": "Enviar uma mensagem direta"
|
||||||
|
},
|
||||||
|
"private": {
|
||||||
|
"accessibilityLabel": "Toot é visível apenas para seguidores"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"conversation": {
|
||||||
|
"withAccounts": "Com",
|
||||||
|
"delete": {
|
||||||
|
"function": "Excluir mensagem direta"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"accessibilityHint": "Ações para este toot, como o seu usuário publicado",
|
||||||
|
"account": {
|
||||||
|
"heading": "Sobre o usuário",
|
||||||
|
"mute": {
|
||||||
|
"function": "Silenciar usuário",
|
||||||
|
"button": "Silenciar @{{acct}}"
|
||||||
|
},
|
||||||
|
"block": {
|
||||||
|
"function": "Bloquear usuário",
|
||||||
|
"button": "Bloquear @{{acct}}"
|
||||||
|
},
|
||||||
|
"reports": {
|
||||||
|
"function": "Denunciar usuário",
|
||||||
|
"button": "Reportar @{{acct}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain": {
|
||||||
|
"heading": "Sobre a instância",
|
||||||
|
"block": {
|
||||||
|
"function": "Bloquear instância",
|
||||||
|
"button": "Bloquear a instância {{domain}}"
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"title": "Confirma bloquear {{domain}}?",
|
||||||
|
"message": "Na maioria das vezes, você pode silenciar ou bloquear determinado usuário.\n\nDepois de bloquear a instância, todo seu conteúdo, incluindo seguidores, será removido!",
|
||||||
|
"buttons": {
|
||||||
|
"confirm": "Confirmar bloqueio",
|
||||||
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"share": {
|
||||||
|
"status": {
|
||||||
|
"heading": "Compartilhar toot",
|
||||||
|
"button": "Compartilhar o link para este mundo"
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"heading": "Compartilhar Usuário",
|
||||||
|
"button": "Compartilhar link para este usuário"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"heading": "Sobre o toot",
|
||||||
|
"edit": {
|
||||||
|
"function": "Editar toot",
|
||||||
|
"button": "Editar este toot"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"function": "Remover toot",
|
||||||
|
"button": "Deletar este toot",
|
||||||
|
"alert": {
|
||||||
|
"title": "Confirmar exclusão?",
|
||||||
|
"message": "Tem certeza que deseja excluir este toot? Todos os boosts e favoritos serão apagados, incluindo todas as respostas.",
|
||||||
|
"buttons": {
|
||||||
|
"confirm": "Confirme a exclusão",
|
||||||
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deleteEdit": {
|
||||||
|
"function": "Remover toot",
|
||||||
|
"button": "Excluir e rascunhar",
|
||||||
|
"alert": {
|
||||||
|
"title": "Confirmar exclusão?",
|
||||||
|
"message": "Tem certeza que deseja excluir este toot? Todos os boosts e favoritos serão apagados, incluindo todas as respostas.",
|
||||||
|
"buttons": {
|
||||||
|
"confirm": "Confirme a exclusão",
|
||||||
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mute": {
|
||||||
|
"function": "Silenciar toot",
|
||||||
|
"button": {
|
||||||
|
"positive": "Silenciar este toot e respostas",
|
||||||
|
"negative": "Desbloquear este toot e respostas"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pin": {
|
||||||
|
"function": "Fixar",
|
||||||
|
"button": {
|
||||||
|
"positive": "Fixar este toot",
|
||||||
|
"negative": "Desafixar este toot"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"poll": {
|
||||||
|
"meta": {
|
||||||
|
"button": {
|
||||||
|
"vote": "Votar",
|
||||||
|
"refresh": "Atualizar"
|
||||||
|
},
|
||||||
|
"count": {
|
||||||
|
"voters_one": "{{count}} usuário votou",
|
||||||
|
"voters_other": "{{count}} usuários votaram",
|
||||||
|
"votes_one": "{{count}} voto",
|
||||||
|
"votes_other": "{{count}} votos"
|
||||||
|
},
|
||||||
|
"expiration": {
|
||||||
|
"expired": "Voto expirado",
|
||||||
|
"until": "Expira em <0 />"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"screenshot": {
|
||||||
|
"title": "Proteção de privacidade",
|
||||||
|
"message": "Por favor, não divulgue a identidade de outros usuários, como nome de usuário, avatar, etc. Obrigado!",
|
||||||
|
"button": "Confirmar"
|
||||||
|
},
|
||||||
|
"localCorrupt": {
|
||||||
|
"message": "Sua sessão expirou. Por favor, faça o login novamente"
|
||||||
|
},
|
||||||
|
"pushError": {
|
||||||
|
"message": "Erro no Serviço Push",
|
||||||
|
"description": "Por favor, re-ative as notificações push nas configurações"
|
||||||
|
},
|
||||||
|
"shareError": {
|
||||||
|
"imageNotSupported": "Tipo de imagem {{type}} não suportado",
|
||||||
|
"videoNotSupported": "Tipo de vídeo {{type}} não suportado"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"altText": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
|
"notificationsFilter": {
|
||||||
|
"heading": "Exibir notificações",
|
||||||
|
"content": {
|
||||||
|
"follow": "$t(screenTabs:me.push.follow.heading)",
|
||||||
|
"follow_request": "Seguidores pendentes",
|
||||||
|
"favourite": "$t(screenTabs:me.push.favourite.heading)",
|
||||||
|
"reblog": "$t(screenTabs:me.push.reblog.heading)",
|
||||||
|
"mention": "$t(screenTabs:me.push.mention.heading)",
|
||||||
|
"poll": "$t(screenTabs:me.push.poll.heading)",
|
||||||
|
"status": "",
|
||||||
|
"update": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"heading": "Comunicados",
|
||||||
|
"content": {
|
||||||
|
"published": "Publicado <0 />",
|
||||||
|
"button": {
|
||||||
|
"read": "Ler",
|
||||||
|
"unread": "Marcar como lido"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
{
|
||||||
|
"heading": {
|
||||||
|
"left": {
|
||||||
|
"button": "Cancelar",
|
||||||
|
"alert": {
|
||||||
|
"title": "Cancelar edições?",
|
||||||
|
"buttons": {
|
||||||
|
"save": "Salvar rascunho",
|
||||||
|
"delete": "Apagar rascunho",
|
||||||
|
"cancel": "Cancelar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"right": {
|
||||||
|
"button": {
|
||||||
|
"default": "Toot",
|
||||||
|
"conversation": "Toot DM",
|
||||||
|
"reply": "Resposta de toot",
|
||||||
|
"deleteEdit": "Toot",
|
||||||
|
"edit": "Toot",
|
||||||
|
"share": "Toot"
|
||||||
|
},
|
||||||
|
"alert": {
|
||||||
|
"default": {
|
||||||
|
"title": "Falha no tooting",
|
||||||
|
"button": "Tente Novamente"
|
||||||
|
},
|
||||||
|
"removeReply": {
|
||||||
|
"title": "Não foi possível encontrar um toot respondido",
|
||||||
|
"description": "Um toot respondido pode ter sido excluído. Você quer removê-lo da sua referência?",
|
||||||
|
"cancel": "$t(common:buttons.cancel)",
|
||||||
|
"confirm": "Remover referência"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"root": {
|
||||||
|
"header": {
|
||||||
|
"postingAs": "Tooting cono @{{acct}}@{{domain}}",
|
||||||
|
"spoilerInput": {
|
||||||
|
"placeholder": "Mensagem de aviso para Spoiler"
|
||||||
|
},
|
||||||
|
"textInput": {
|
||||||
|
"placeholder": "No que você está pensando",
|
||||||
|
"keyboardImage": {
|
||||||
|
"exceedMaximum": {
|
||||||
|
"title": "Quantidade máxima de anexos atingida",
|
||||||
|
"OK": "$t(common:buttons.OK)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"attachments": {
|
||||||
|
"sensitive": "Marcar anexos como sensível",
|
||||||
|
"remove": {
|
||||||
|
"accessibilityLabel": "Remover anexo enviado, número {{attachment}}"
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"accessibilityLabel": "Editar o anexo enviado, número {{attachment}}"
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"accessibilityLabel": "Carregar mais anexos"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"emojis": {
|
||||||
|
"accessibilityHint": "Toque para adicionar emojis ao toot"
|
||||||
|
},
|
||||||
|
"poll": {
|
||||||
|
"option": {
|
||||||
|
"placeholder": {
|
||||||
|
"accessibilityLabel": "Opção de enquete {{index}}",
|
||||||
|
"single": "Única escolha",
|
||||||
|
"multiple": "Múltiplas opções"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quantity": {
|
||||||
|
"reduce": {
|
||||||
|
"accessibilityLabel": "Reduzir opções da enquete para {{amount}}",
|
||||||
|
"accessibilityHint": "Quantidade mínima de opções da enquete atingida, tem atualmente {{amount}}"
|
||||||
|
},
|
||||||
|
"increase": {
|
||||||
|
"accessibilityLabel": "Aumentar opções de enquete para {{amount}}",
|
||||||
|
"accessibilityHint": "Quantidade máxima de opções da enquete atingida, tem atualmente {{amount}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"multiple": {
|
||||||
|
"heading": "Tipo de opção",
|
||||||
|
"options": {
|
||||||
|
"single": "Uma opção",
|
||||||
|
"multiple": "Múltipla escolha",
|
||||||
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"expiration": {
|
||||||
|
"heading": "Validade",
|
||||||
|
"options": {
|
||||||
|
"300": "5 minutos",
|
||||||
|
"1800": "30 minutos",
|
||||||
|
"3600": "1 hora",
|
||||||
|
"21600": "6 horas",
|
||||||
|
"86400": "1 dia",
|
||||||
|
"259200": "3 dias",
|
||||||
|
"604800": "7 dias",
|
||||||
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"attachment": {
|
||||||
|
"accessibilityLabel": "Fazer upload de um anexo",
|
||||||
|
"accessibilityHint": "A função de enquete será desativada quando houver qualquer anexo",
|
||||||
|
"failed": {
|
||||||
|
"alert": {
|
||||||
|
"title": "Falha no envio",
|
||||||
|
"button": "Tente Novamente"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"poll": {
|
||||||
|
"accessibilityLabel": "Adicionar enquete",
|
||||||
|
"accessibilityHint": "A função Anexo será desabilitada quando a enquete estiver ativa"
|
||||||
|
},
|
||||||
|
"visibility": {
|
||||||
|
"accessibilityLabel": "Visibilidade do toot é {{visibility}}",
|
||||||
|
"title": "Visibilidade do Toot",
|
||||||
|
"options": {
|
||||||
|
"public": "Público",
|
||||||
|
"unlisted": "Não listado",
|
||||||
|
"private": "Apenas seguidores",
|
||||||
|
"direct": "Mensagem Direta",
|
||||||
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spoiler": {
|
||||||
|
"accessibilityLabel": "Spoiler"
|
||||||
|
},
|
||||||
|
"emoji": {
|
||||||
|
"accessibilityLabel": "Adicionar emoji",
|
||||||
|
"accessibilityHint": "Abra o painel de seleção de emojis, deslize horizontalmente para alterar a página"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"drafts_one": "Rascunho ({{count}})",
|
||||||
|
"drafts_other": "Rascunhos ({{count}})"
|
||||||
|
},
|
||||||
|
"editAttachment": {
|
||||||
|
"header": {
|
||||||
|
"title": "Editar anexos",
|
||||||
|
"right": {
|
||||||
|
"accessibilityLabel": "Salvar anexo editado",
|
||||||
|
"failed": {
|
||||||
|
"title": "Falha ao editar",
|
||||||
|
"button": "Tente Novamente"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"altText": {
|
||||||
|
"heading": "Descrever mídia para deficientes visuais",
|
||||||
|
"placeholder": "Você pode adicionar uma descrição, às vezes chamada texto alternativo aos seus meios de comunicação social, assim eles estão acessíveis a ainda mais pessoas, incluindo as que são cegos ou deficientes visuais.\n\nAs boas descrições são concisas, mas apresentam o que está em sua mídia com precisão o suficiente para entender seu contexto."
|
||||||
|
},
|
||||||
|
"imageFocus": "Arraste o círculo de foco para atualizar o ponto de foco"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"draftsList": {
|
||||||
|
"header": {
|
||||||
|
"title": "Rascunho"
|
||||||
|
},
|
||||||
|
"warning": "",
|
||||||
|
"content": {
|
||||||
|
"accessibilityHint": "Toque para editar este rascunho",
|
||||||
|
"textEmpty": "O conteúdo está vazio"
|
||||||
|
},
|
||||||
|
"checkAttachment": "Verificando anexos no servidor..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"actions": {
|
||||||
|
"accessibilityLabel": "Mais ações desta imagem",
|
||||||
|
"accessibilityHint": "Você pode salvar ou compartilhar esta imagem"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"save": "Salvar imagem",
|
||||||
|
"share": "Compartilhar Imagem",
|
||||||
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
|
},
|
||||||
|
"save": {
|
||||||
|
"succeed": "Imagem salva",
|
||||||
|
"failed": "Falha ao salvar imagem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,354 @@
|
||||||
|
{
|
||||||
|
"tabs": {
|
||||||
|
"local": {
|
||||||
|
"name": "Seguindo"
|
||||||
|
},
|
||||||
|
"public": {
|
||||||
|
"name": "",
|
||||||
|
"segments": {
|
||||||
|
"left": "Global",
|
||||||
|
"right": "Local"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"name": "Notificações"
|
||||||
|
},
|
||||||
|
"me": {
|
||||||
|
"name": "Sobre mim"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"search": {
|
||||||
|
"accessibilityLabel": "Pesquisa",
|
||||||
|
"accessibilityHint": "Pesquisar por hashtags, usuários ou toots"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"filter": {
|
||||||
|
"accessibilityLabel": "Filtro",
|
||||||
|
"accessibilityHint": "Filtrar notificações por tipos"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"me": {
|
||||||
|
"stacks": {
|
||||||
|
"bookmarks": {
|
||||||
|
"name": "Favoritos"
|
||||||
|
},
|
||||||
|
"conversations": {
|
||||||
|
"name": "Mensagens diretas"
|
||||||
|
},
|
||||||
|
"favourites": {
|
||||||
|
"name": "Favoritos"
|
||||||
|
},
|
||||||
|
"fontSize": {
|
||||||
|
"name": "Tamanho da fonte do Toot"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"name": "Idioma"
|
||||||
|
},
|
||||||
|
"lists": {
|
||||||
|
"name": "Listas"
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"name": "Lista: {{list}}"
|
||||||
|
},
|
||||||
|
"push": {
|
||||||
|
"name": "Notificação"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"name": "Editar Perfil"
|
||||||
|
},
|
||||||
|
"profileName": {
|
||||||
|
"name": "Editar nome de exibição"
|
||||||
|
},
|
||||||
|
"profileNote": {
|
||||||
|
"name": "Editar descrição"
|
||||||
|
},
|
||||||
|
"profileFields": {
|
||||||
|
"name": "Editar Metadados"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"name": "Configurações do Aplicativo"
|
||||||
|
},
|
||||||
|
"webSettings": {
|
||||||
|
"name": "Mais configurações"
|
||||||
|
},
|
||||||
|
"switch": {
|
||||||
|
"name": "Alterar Conta"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fontSize": {
|
||||||
|
"showcase": "Exemplo de toot",
|
||||||
|
"demo": "<p>Esta é uma demonstração também😊. Você pode escolher entre várias opções abaixo.<br /><br />Esta configuração afeta apenas o conteúdo principal dos toots, mas não os tamanhos de outra fonte.</p>",
|
||||||
|
"availableSizes": "Tamanhos disponíveis",
|
||||||
|
"sizes": {
|
||||||
|
"S": "S",
|
||||||
|
"M": "M - Padrão",
|
||||||
|
"L": "Grande",
|
||||||
|
"XL": "Extra grande",
|
||||||
|
"XXL": "Extra grande"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"cancellation": {
|
||||||
|
"title": "Alterações não salvas",
|
||||||
|
"message": "Sua alteração não foi salva. Descartar as alterações?",
|
||||||
|
"buttons": {
|
||||||
|
"cancel": "$t(common:buttons.cancel)",
|
||||||
|
"discard": "Descartar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"succeed": "{{type}} atualizado",
|
||||||
|
"failed": "{{type}} falhou na atualização. Por favor, tente novamente"
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"name": {
|
||||||
|
"title": "Nome de exibição"
|
||||||
|
},
|
||||||
|
"avatar": {
|
||||||
|
"title": "Imagem de perfil",
|
||||||
|
"description": "Será reduzido a 400x400px"
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"title": "Banner",
|
||||||
|
"description": "Será reduzido para 1500x500px"
|
||||||
|
},
|
||||||
|
"note": {
|
||||||
|
"title": "Descrição"
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"title": "Metadados",
|
||||||
|
"total_one": "{{count}} campo",
|
||||||
|
"total_other": "{{count}} campos"
|
||||||
|
},
|
||||||
|
"visibility": {
|
||||||
|
"title": "Visibilidade da Postagem",
|
||||||
|
"options": {
|
||||||
|
"public": "Público",
|
||||||
|
"unlisted": "Não listado",
|
||||||
|
"private": "Apenas seguidores",
|
||||||
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sensitive": {
|
||||||
|
"title": "Publicação de Mídia Sensiva"
|
||||||
|
},
|
||||||
|
"lock": {
|
||||||
|
"title": "Trancar conta",
|
||||||
|
"description": "Requer que você aprove manualmente seguidores"
|
||||||
|
},
|
||||||
|
"bot": {
|
||||||
|
"title": "Conta bot",
|
||||||
|
"description": "Essa conta executa principalmente ações automatizadas e pode não ser monitorada"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"group": "Grupo {{index}}",
|
||||||
|
"label": "Rótulo",
|
||||||
|
"content": "Conteúdo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"push": {
|
||||||
|
"notAvailable": "Seu telefone não suporta notificação de envio de tooot",
|
||||||
|
"enable": {
|
||||||
|
"direct": "Habilitar notificações via push",
|
||||||
|
"settings": "Ativar em configurações"
|
||||||
|
},
|
||||||
|
"global": {
|
||||||
|
"heading": "Habilitar para {{acct}}",
|
||||||
|
"description": "Mensagens são encaminhadas pelo servidor do tooot"
|
||||||
|
},
|
||||||
|
"decode": {
|
||||||
|
"heading": "Ver detalhes da mensagem",
|
||||||
|
"description": "As mensagens enviadas através do servidor do tooot são criptografadas, mas você pode optar por decodificar a mensagem no servidor. Nosso código fonte de servidor é open source, e nenhuma política de registro."
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"heading": "Padrão"
|
||||||
|
},
|
||||||
|
"follow": {
|
||||||
|
"heading": "Novo seguidor"
|
||||||
|
},
|
||||||
|
"follow_request": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
|
"favourite": {
|
||||||
|
"heading": "Favoritos"
|
||||||
|
},
|
||||||
|
"reblog": {
|
||||||
|
"heading": "Boosted"
|
||||||
|
},
|
||||||
|
"mention": {
|
||||||
|
"heading": "Mencionou você"
|
||||||
|
},
|
||||||
|
"poll": {
|
||||||
|
"heading": "Pesquisa atualizada"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
|
"howitworks": "Saiba como funciona o roteamento"
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"announcements": {
|
||||||
|
"content": {
|
||||||
|
"unread": "{{amount}} não lidas",
|
||||||
|
"read": "Não há mensagens não lidas",
|
||||||
|
"empty": "Nenhum"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"push": {
|
||||||
|
"content": {
|
||||||
|
"enabled": "Habilitado",
|
||||||
|
"disabled": "Desabilitado"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"title": "Atualize para a versão mais recente"
|
||||||
|
},
|
||||||
|
"logout": {
|
||||||
|
"button": "Sair",
|
||||||
|
"alert": {
|
||||||
|
"title": "Desconectar?",
|
||||||
|
"message": "Após sair, você precisa entrar novamente",
|
||||||
|
"buttons": {
|
||||||
|
"logout": "Sair",
|
||||||
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"fontsize": {
|
||||||
|
"heading": "$t(me.stacks.fontSize.name)",
|
||||||
|
"content": {
|
||||||
|
"S": "$t(me.fontSize.sizes.S)",
|
||||||
|
"M": "$t(me.fontSize.sizes.M)",
|
||||||
|
"L": "$t(me.fontSize.sizes.L)",
|
||||||
|
"XL": "$t(me.fontSize.sizes.XL)",
|
||||||
|
"XXL": "$t(me.fontSize.sizes.XXL)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"heading": "$t(me.stacks.language.name)",
|
||||||
|
"options": {
|
||||||
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"heading": "Aparência",
|
||||||
|
"options": {
|
||||||
|
"auto": "Como o sistema",
|
||||||
|
"light": "Modo claro",
|
||||||
|
"dark": "Modo escuro",
|
||||||
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"darkTheme": {
|
||||||
|
"heading": "Tema escuro",
|
||||||
|
"options": {
|
||||||
|
"lighter": "Claro",
|
||||||
|
"darker": "Escuro",
|
||||||
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"browser": {
|
||||||
|
"heading": "Abrir links",
|
||||||
|
"options": {
|
||||||
|
"internal": "Dentro do aplicativo",
|
||||||
|
"external": "Usar navegador do sistema",
|
||||||
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"staticEmoji": {
|
||||||
|
"heading": "Usar emojis estáticos",
|
||||||
|
"description": "Se você encontrar falhas frequentes de apps ao visualizar a lista de emojis, você pode tentar usar emojis estáticos."
|
||||||
|
},
|
||||||
|
"feedback": {
|
||||||
|
"heading": "Pedidos de Funcionalidades"
|
||||||
|
},
|
||||||
|
"support": {
|
||||||
|
"heading": "Suporte tooot"
|
||||||
|
},
|
||||||
|
"review": {
|
||||||
|
"heading": "Revisar tooot"
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"heading": "Contatar tooot"
|
||||||
|
},
|
||||||
|
"analytics": {
|
||||||
|
"heading": "Ajude-nos a melhorar",
|
||||||
|
"description": "Coletando somente relativo ao uso, não ao usuário"
|
||||||
|
},
|
||||||
|
"version": "Versão v{{version}}",
|
||||||
|
"instanceVersion": "Versão Mastodon v{{version}}"
|
||||||
|
},
|
||||||
|
"switch": {
|
||||||
|
"existing": "Escolha a partir do login",
|
||||||
|
"new": "Fazer login para instância"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shared": {
|
||||||
|
"account": {
|
||||||
|
"actions": {
|
||||||
|
"accessibilityLabel": "Ações para o usuário {{user}}",
|
||||||
|
"accessibilityHint": "Você pode silenciar, bloquear, relatar ou compartilhar este usuário"
|
||||||
|
},
|
||||||
|
"followed_by": " está seguindo você",
|
||||||
|
"moved": "Usuário movido",
|
||||||
|
"created_at": "Registrado em: {{date}}",
|
||||||
|
"summary": {
|
||||||
|
"statuses_count": "{{count}} toots",
|
||||||
|
"following_count": "$t(shared.users.accounts.following)",
|
||||||
|
"followers_count": "$t(shared.users.accounts.followers)"
|
||||||
|
},
|
||||||
|
"toots": {
|
||||||
|
"default": "Toots",
|
||||||
|
"all": "Toots e respostas"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"attachments": {
|
||||||
|
"name": "<0 /><1>\"s mídia</1>"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"header": {
|
||||||
|
"prefix": "Procurando",
|
||||||
|
"placeholder": "para..."
|
||||||
|
},
|
||||||
|
"empty": {
|
||||||
|
"general": "Digite a palavra-chave para pesquisar por <bold>$t(screenTabs:shared.search.sections.accounts)</bold>£<bold>$t(screenTabs:shared.search.sections.hashtags)</bold> ou <bold>$t(screenTabs:shared.search.sections.statuses)</bold>",
|
||||||
|
"advanced": {
|
||||||
|
"header": "Pesquisa avançada",
|
||||||
|
"example": {
|
||||||
|
"account": "$t(shared.search.header.prefix) $t(shared.search.sections.accounts)",
|
||||||
|
"hashtag": "$t(shared.search.header.prefix) $t(shared.search.sections.hashtags)",
|
||||||
|
"statusLink": "$t(shared.search.header.prefix) $t(shared.search.sections.statuses)",
|
||||||
|
"accountLink": "$t(shared.search.header.prefix) $t(shared.search.sections.accounts)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sections": {
|
||||||
|
"accounts": "Usuário",
|
||||||
|
"hashtags": "Palavras-chave",
|
||||||
|
"statuses": "Toot"
|
||||||
|
},
|
||||||
|
"notFound": "Não foi possível encontrar <bold>{{searchTerm}}</bold> {{type}} relacionado"
|
||||||
|
},
|
||||||
|
"toot": {
|
||||||
|
"name": "Discussões"
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"accounts": {
|
||||||
|
"following": "Seguindo {{count}}",
|
||||||
|
"followers": "{{count}} seguidores"
|
||||||
|
},
|
||||||
|
"statuses": {
|
||||||
|
"reblogged_by": "{{count}} boostou",
|
||||||
|
"favourited_by": "{{count}} favoritados"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"history": {
|
||||||
|
"name": "Histórico de Edição"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,7 +29,8 @@
|
||||||
"reblog": {
|
"reblog": {
|
||||||
"default": "{{name}} đăng lại",
|
"default": "{{name}} đăng lại",
|
||||||
"notification": "{{name}} đăng lại tút của bạn"
|
"notification": "{{name}} đăng lại tút của bạn"
|
||||||
}
|
},
|
||||||
|
"update": "Đăng lại đã được sửa"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"reply": {
|
"reply": {
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
{
|
{
|
||||||
"content": {
|
"content": {
|
||||||
"button": {
|
"altText": {
|
||||||
"apply": "$t(common:buttons.apply)",
|
"heading": ""
|
||||||
"cancel": "$t(common:buttons.cancel)"
|
|
||||||
},
|
},
|
||||||
"notificationsFilter": {
|
"notificationsFilter": {
|
||||||
"heading": "Những kiểu thông báo cho phép",
|
"heading": "Những kiểu thông báo cho phép",
|
||||||
"content": {
|
"content": {
|
||||||
"follow": "$t(screenTabs:me.push.follow.heading)",
|
"follow": "$t(screenTabs:me.push.follow.heading)",
|
||||||
|
"follow_request": "Yêu cầu theo dõi",
|
||||||
"favourite": "$t(screenTabs:me.push.favourite.heading)",
|
"favourite": "$t(screenTabs:me.push.favourite.heading)",
|
||||||
"reblog": "$t(screenTabs:me.push.reblog.heading)",
|
"reblog": "$t(screenTabs:me.push.reblog.heading)",
|
||||||
"mention": "$t(screenTabs:me.push.mention.heading)",
|
"mention": "$t(screenTabs:me.push.mention.heading)",
|
||||||
"poll": "$t(screenTabs:me.push.poll.heading)",
|
"poll": "$t(screenTabs:me.push.poll.heading)",
|
||||||
"follow_request": "Yêu cầu theo dõi"
|
"status": "Tút từ người đã theo dõi",
|
||||||
|
"update": "Đăng lại đã được sửa"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,6 +168,7 @@
|
||||||
"header": {
|
"header": {
|
||||||
"title": "Nháp"
|
"title": "Nháp"
|
||||||
},
|
},
|
||||||
|
"warning": "Tút nháp chỉ được lưu trữ trên điện thoại và có thể bị mất nếu có sự cố. Hãy cẩn thận.",
|
||||||
"content": {
|
"content": {
|
||||||
"accessibilityHint": "Đã lưu nháp, nhấn để tiếp tục viết",
|
"accessibilityHint": "Đã lưu nháp, nhấn để tiếp tục viết",
|
||||||
"textEmpty": "Chưa có nội dung"
|
"textEmpty": "Chưa có nội dung"
|
||||||
|
|
|
@ -43,6 +43,9 @@
|
||||||
"fontSize": {
|
"fontSize": {
|
||||||
"name": "Cỡ chữ"
|
"name": "Cỡ chữ"
|
||||||
},
|
},
|
||||||
|
"language": {
|
||||||
|
"name": "Ngôn ngữ"
|
||||||
|
},
|
||||||
"lists": {
|
"lists": {
|
||||||
"name": "Danh sách"
|
"name": "Danh sách"
|
||||||
},
|
},
|
||||||
|
@ -166,6 +169,9 @@
|
||||||
"follow": {
|
"follow": {
|
||||||
"heading": "Người theo dõi mới"
|
"heading": "Người theo dõi mới"
|
||||||
},
|
},
|
||||||
|
"follow_request": {
|
||||||
|
"heading": "Yêu cầu theo dõi"
|
||||||
|
},
|
||||||
"favourite": {
|
"favourite": {
|
||||||
"heading": "Lượt thích"
|
"heading": "Lượt thích"
|
||||||
},
|
},
|
||||||
|
@ -178,6 +184,9 @@
|
||||||
"poll": {
|
"poll": {
|
||||||
"heading": "Kết quả bình chọn"
|
"heading": "Kết quả bình chọn"
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
"heading": "Tút từ người đã theo dõi"
|
||||||
|
},
|
||||||
"howitworks": "Tìm hiểu cách truyền"
|
"howitworks": "Tìm hiểu cách truyền"
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
|
@ -221,7 +230,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"heading": "Ngôn ngữ",
|
"heading": "$t(me.stacks.language.name)",
|
||||||
"options": {
|
"options": {
|
||||||
"cancel": "$t(common:buttons.cancel)"
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,8 @@
|
||||||
"reblog": {
|
"reblog": {
|
||||||
"default": "{{name}} 转嘟了",
|
"default": "{{name}} 转嘟了",
|
||||||
"notification": "{{name}} 转嘟了你的嘟文"
|
"notification": "{{name}} 转嘟了你的嘟文"
|
||||||
}
|
},
|
||||||
|
"update": "转嘟已被编辑"
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"reply": {
|
"reply": {
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
{
|
{
|
||||||
"content": {
|
"content": {
|
||||||
"button": {
|
"altText": {
|
||||||
"apply": "$t(common:buttons.apply)",
|
"heading": "替代文本"
|
||||||
"cancel": "$t(common:buttons.cancel)"
|
|
||||||
},
|
},
|
||||||
"notificationsFilter": {
|
"notificationsFilter": {
|
||||||
"heading": "显示通知",
|
"heading": "显示通知",
|
||||||
"content": {
|
"content": {
|
||||||
"follow": "$t(screenTabs:me.push.follow.heading)",
|
"follow": "$t(screenTabs:me.push.follow.heading)",
|
||||||
|
"follow_request": "关注请求",
|
||||||
"favourite": "$t(screenTabs:me.push.favourite.heading)",
|
"favourite": "$t(screenTabs:me.push.favourite.heading)",
|
||||||
"reblog": "$t(screenTabs:me.push.reblog.heading)",
|
"reblog": "$t(screenTabs:me.push.reblog.heading)",
|
||||||
"mention": "$t(screenTabs:me.push.mention.heading)",
|
"mention": "$t(screenTabs:me.push.mention.heading)",
|
||||||
"poll": "$t(screenTabs:me.push.poll.heading)",
|
"poll": "$t(screenTabs:me.push.poll.heading)",
|
||||||
"follow_request": "关注请求"
|
"status": "订阅用户的嘟文",
|
||||||
|
"update": "转嘟被编辑"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,6 +168,7 @@
|
||||||
"header": {
|
"header": {
|
||||||
"title": "草稿"
|
"title": "草稿"
|
||||||
},
|
},
|
||||||
|
"warning": "草稿只存储在本地,在部分情况下可能丢失。建议不要长期存储草稿。",
|
||||||
"content": {
|
"content": {
|
||||||
"accessibilityHint": "已保存的草稿,点击编辑此草稿",
|
"accessibilityHint": "已保存的草稿,点击编辑此草稿",
|
||||||
"textEmpty": "无正文内容"
|
"textEmpty": "无正文内容"
|
||||||
|
|
|
@ -43,6 +43,9 @@
|
||||||
"fontSize": {
|
"fontSize": {
|
||||||
"name": "嘟文字号"
|
"name": "嘟文字号"
|
||||||
},
|
},
|
||||||
|
"language": {
|
||||||
|
"name": "应用语言"
|
||||||
|
},
|
||||||
"lists": {
|
"lists": {
|
||||||
"name": "列表"
|
"name": "列表"
|
||||||
},
|
},
|
||||||
|
@ -166,6 +169,9 @@
|
||||||
"follow": {
|
"follow": {
|
||||||
"heading": "新关注者"
|
"heading": "新关注者"
|
||||||
},
|
},
|
||||||
|
"follow_request": {
|
||||||
|
"heading": "关注请求"
|
||||||
|
},
|
||||||
"favourite": {
|
"favourite": {
|
||||||
"heading": "嘟文被喜欢"
|
"heading": "嘟文被喜欢"
|
||||||
},
|
},
|
||||||
|
@ -178,6 +184,9 @@
|
||||||
"poll": {
|
"poll": {
|
||||||
"heading": "投票更新"
|
"heading": "投票更新"
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
"heading": "订阅用户的嘟文"
|
||||||
|
},
|
||||||
"howitworks": "了解通知消息转发如何工作"
|
"howitworks": "了解通知消息转发如何工作"
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
|
@ -221,7 +230,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"heading": "切换语言",
|
"heading": "$t(me.stacks.language.name)",
|
||||||
"options": {
|
"options": {
|
||||||
"cancel": "$t(common:buttons.cancel)"
|
"cancel": "$t(common:buttons.cancel)"
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,8 @@
|
||||||
"reblog": {
|
"reblog": {
|
||||||
"default": "{{name}} 轉嘟了",
|
"default": "{{name}} 轉嘟了",
|
||||||
"notification": "{{name}} 轉嘟了您的嘟文"
|
"notification": "{{name}} 轉嘟了您的嘟文"
|
||||||
}
|
},
|
||||||
|
"update": ""
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"reply": {
|
"reply": {
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
{
|
{
|
||||||
"content": {
|
"content": {
|
||||||
"button": {
|
"altText": {
|
||||||
"apply": "",
|
"heading": ""
|
||||||
"cancel": ""
|
|
||||||
},
|
},
|
||||||
"notificationsFilter": {
|
"notificationsFilter": {
|
||||||
"heading": "",
|
"heading": "",
|
||||||
"content": {
|
"content": {
|
||||||
"follow": "",
|
"follow": "",
|
||||||
|
"follow_request": "",
|
||||||
"favourite": "",
|
"favourite": "",
|
||||||
"reblog": "",
|
"reblog": "",
|
||||||
"mention": "",
|
"mention": "",
|
||||||
"poll": "",
|
"poll": "",
|
||||||
"follow_request": ""
|
"status": "",
|
||||||
|
"update": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,6 +168,7 @@
|
||||||
"header": {
|
"header": {
|
||||||
"title": ""
|
"title": ""
|
||||||
},
|
},
|
||||||
|
"warning": "",
|
||||||
"content": {
|
"content": {
|
||||||
"accessibilityHint": "",
|
"accessibilityHint": "",
|
||||||
"textEmpty": ""
|
"textEmpty": ""
|
||||||
|
|
|
@ -43,6 +43,9 @@
|
||||||
"fontSize": {
|
"fontSize": {
|
||||||
"name": "嘟文字體大小"
|
"name": "嘟文字體大小"
|
||||||
},
|
},
|
||||||
|
"language": {
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
"lists": {
|
"lists": {
|
||||||
"name": "清單"
|
"name": "清單"
|
||||||
},
|
},
|
||||||
|
@ -166,6 +169,9 @@
|
||||||
"follow": {
|
"follow": {
|
||||||
"heading": ""
|
"heading": ""
|
||||||
},
|
},
|
||||||
|
"follow_request": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
"favourite": {
|
"favourite": {
|
||||||
"heading": ""
|
"heading": ""
|
||||||
},
|
},
|
||||||
|
@ -178,6 +184,9 @@
|
||||||
"poll": {
|
"poll": {
|
||||||
"heading": ""
|
"heading": ""
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
"heading": ""
|
||||||
|
},
|
||||||
"howitworks": ""
|
"howitworks": ""
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
} from '@utils/slices/instancesSlice'
|
} from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useEffect, useMemo } from 'react'
|
import React, { useCallback, useEffect } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Dimensions, StyleSheet, View } from 'react-native'
|
import { Dimensions, StyleSheet, View } from 'react-native'
|
||||||
import {
|
import {
|
||||||
|
@ -30,6 +30,7 @@ import {
|
||||||
} from 'react-native-safe-area-context'
|
} from 'react-native-safe-area-context'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import ActionsAccount from './Actions/Account'
|
import ActionsAccount from './Actions/Account'
|
||||||
|
import ActionsAltText from './Actions/AltText'
|
||||||
import ActionsDomain from './Actions/Domain'
|
import ActionsDomain from './Actions/Domain'
|
||||||
import ActionsNotificationsFilter from './Actions/NotificationsFilter'
|
import ActionsNotificationsFilter from './Actions/NotificationsFilter'
|
||||||
import ActionsShare from './Actions/Share'
|
import ActionsShare from './Actions/Share'
|
||||||
|
@ -173,6 +174,8 @@ const ScreenActions = ({
|
||||||
)
|
)
|
||||||
case 'notifications_filter':
|
case 'notifications_filter':
|
||||||
return <ActionsNotificationsFilter />
|
return <ActionsNotificationsFilter />
|
||||||
|
case 'alt_text':
|
||||||
|
return <ActionsAltText text={params.text} />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import Button from '@components/Button'
|
||||||
|
import MenuContainer from '@components/Menu/Container'
|
||||||
|
import MenuHeader from '@components/Menu/Header'
|
||||||
|
import CustomText from '@components/Text'
|
||||||
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Dimensions } from 'react-native'
|
||||||
|
import { ScrollView } from 'react-native-gesture-handler'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ActionsAltText: React.FC<Props> = ({ text }) => {
|
||||||
|
const navigation = useNavigation()
|
||||||
|
const { t } = useTranslation('screenActions')
|
||||||
|
const { colors } = useTheme()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuContainer>
|
||||||
|
<MenuHeader heading={t(`content.altText.heading`)} />
|
||||||
|
<ScrollView style={{ maxHeight: Dimensions.get('window').height / 2 }}>
|
||||||
|
<CustomText style={{ color: colors.primaryDefault }}>
|
||||||
|
{text}
|
||||||
|
</CustomText>
|
||||||
|
</ScrollView>
|
||||||
|
</MenuContainer>
|
||||||
|
<Button
|
||||||
|
type='text'
|
||||||
|
content={t('common:buttons.OK')}
|
||||||
|
onPress={() => navigation.goBack()}
|
||||||
|
style={{
|
||||||
|
marginHorizontal: StyleConstants.Spacing.Global.PagePadding * 2
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ActionsAltText
|
|
@ -4,12 +4,12 @@ import MenuHeader from '@components/Menu/Header'
|
||||||
import MenuRow from '@components/Menu/Row'
|
import MenuRow from '@components/Menu/Row'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import {
|
import {
|
||||||
|
checkInstanceFeature,
|
||||||
getInstanceNotificationsFilter,
|
getInstanceNotificationsFilter,
|
||||||
updateInstanceNotificationsFilter
|
updateInstanceNotificationsFilter
|
||||||
} from '@utils/slices/instancesSlice'
|
} from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { StyleSheet } from 'react-native'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { useQueryClient } from 'react-query'
|
import { useQueryClient } from 'react-query'
|
||||||
|
@ -33,42 +33,63 @@ const ActionsNotificationsFilter: React.FC = () => {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasTypeStatus = useSelector(
|
||||||
|
checkInstanceFeature('notification_type_status')
|
||||||
|
)
|
||||||
|
const hasTypeUpdate = useSelector(
|
||||||
|
checkInstanceFeature('notification_type_update')
|
||||||
|
)
|
||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
instanceNotificationsFilter &&
|
instanceNotificationsFilter &&
|
||||||
(
|
(
|
||||||
[
|
[
|
||||||
'follow',
|
'follow',
|
||||||
|
'follow_request',
|
||||||
'favourite',
|
'favourite',
|
||||||
'reblog',
|
'reblog',
|
||||||
'mention',
|
'mention',
|
||||||
'poll',
|
'poll',
|
||||||
'follow_request'
|
'status',
|
||||||
|
'update'
|
||||||
] as [
|
] as [
|
||||||
'follow',
|
'follow',
|
||||||
|
'follow_request',
|
||||||
'favourite',
|
'favourite',
|
||||||
'reblog',
|
'reblog',
|
||||||
'mention',
|
'mention',
|
||||||
'poll',
|
'poll',
|
||||||
'follow_request'
|
'status',
|
||||||
|
'update'
|
||||||
]
|
]
|
||||||
).map(type => (
|
)
|
||||||
<MenuRow
|
.filter(type => {
|
||||||
key={type}
|
switch (type) {
|
||||||
title={t(`content.notificationsFilter.content.${type}`)}
|
case 'status':
|
||||||
switchValue={instanceNotificationsFilter[type]}
|
return hasTypeStatus
|
||||||
switchOnValueChange={() =>
|
case 'update':
|
||||||
dispatch(
|
return hasTypeUpdate
|
||||||
updateInstanceNotificationsFilter({
|
default:
|
||||||
...instanceNotificationsFilter,
|
return true
|
||||||
[type]: !instanceNotificationsFilter[type]
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
})
|
||||||
))
|
.map(type => (
|
||||||
|
<MenuRow
|
||||||
|
key={type}
|
||||||
|
title={t(`content.notificationsFilter.content.${type}`)}
|
||||||
|
switchValue={instanceNotificationsFilter[type]}
|
||||||
|
switchOnValueChange={() =>
|
||||||
|
dispatch(
|
||||||
|
updateInstanceNotificationsFilter({
|
||||||
|
...instanceNotificationsFilter,
|
||||||
|
[type]: !instanceNotificationsFilter[type]
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))
|
||||||
)
|
)
|
||||||
}, [instanceNotificationsFilter])
|
}, [instanceNotificationsFilter, hasTypeStatus, hasTypeUpdate])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -78,20 +99,16 @@ const ActionsNotificationsFilter: React.FC = () => {
|
||||||
</MenuContainer>
|
</MenuContainer>
|
||||||
<Button
|
<Button
|
||||||
type='text'
|
type='text'
|
||||||
content={t('content.button.apply')}
|
content={t('common:buttons.apply')}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
queryClient.resetQueries(queryKey)
|
queryClient.resetQueries(queryKey)
|
||||||
}}
|
}}
|
||||||
style={styles.button}
|
style={{
|
||||||
|
marginHorizontal: StyleConstants.Spacing.Global.PagePadding * 2
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
button: {
|
|
||||||
marginHorizontal: StyleConstants.Spacing.Global.PagePadding * 2
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default ActionsNotificationsFilter
|
export default ActionsNotificationsFilter
|
||||||
|
|
|
@ -53,7 +53,6 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
|
||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
({ item }: { item: ComposeStateDraft }) => {
|
({ item }: { item: ComposeStateDraft }) => {
|
||||||
console.log('timestamp', item.timestamp)
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
accessibilityHint={t('content.draftsList.content.accessibilityHint')}
|
accessibilityHint={t('content.draftsList.content.accessibilityHint')}
|
||||||
|
@ -193,6 +192,30 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||||
|
padding: StyleConstants.Spacing.S,
|
||||||
|
borderColor: colors.border,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: StyleConstants.Spacing.S
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name='AlertTriangle'
|
||||||
|
color={colors.secondary}
|
||||||
|
size={StyleConstants.Font.Size.M}
|
||||||
|
style={{ marginRight: StyleConstants.Spacing.S }}
|
||||||
|
/>
|
||||||
|
<CustomText
|
||||||
|
fontStyle='S'
|
||||||
|
style={{ flexShrink: 1, color: colors.secondary }}
|
||||||
|
>
|
||||||
|
{t('content.draftsList.warning')}
|
||||||
|
</CustomText>
|
||||||
|
</View>
|
||||||
<PanGestureHandler enabled={Platform.OS === 'ios'}>
|
<PanGestureHandler enabled={Platform.OS === 'ios'}>
|
||||||
<SwipeListView
|
<SwipeListView
|
||||||
data={instanceDrafts}
|
data={instanceDrafts}
|
||||||
|
|
|
@ -100,8 +100,8 @@ const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
|
||||||
)}
|
)}
|
||||||
source={{ uri }}
|
source={{ uri }}
|
||||||
style={{
|
style={{
|
||||||
width: 32,
|
width: 36,
|
||||||
height: 32,
|
height: 36,
|
||||||
padding: StyleConstants.Spacing.S,
|
padding: StyleConstants.Spacing.S,
|
||||||
margin: StyleConstants.Spacing.S
|
margin: StyleConstants.Spacing.S
|
||||||
}}
|
}}
|
||||||
|
@ -119,8 +119,8 @@ const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
|
||||||
)}
|
)}
|
||||||
source={{ uri }}
|
source={{ uri }}
|
||||||
style={{
|
style={{
|
||||||
width: 32,
|
width: 36,
|
||||||
height: 32,
|
height: 36,
|
||||||
padding: StyleConstants.Spacing.S,
|
padding: StyleConstants.Spacing.S,
|
||||||
margin: StyleConstants.Spacing.S
|
margin: StyleConstants.Spacing.S
|
||||||
}}
|
}}
|
||||||
|
@ -145,7 +145,7 @@ const ComposeEmojis: React.FC<Props> = ({ accessibleRefEmojis }) => {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
justifyContent: 'space-around',
|
justifyContent: 'space-around',
|
||||||
height: 260
|
height: 280
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SectionList
|
<SectionList
|
||||||
|
|
|
@ -19,94 +19,6 @@ import {
|
||||||
import ImageViewer from './ImageViewer/Root'
|
import ImageViewer from './ImageViewer/Root'
|
||||||
import saveImage from './ImageViewer/save'
|
import saveImage from './ImageViewer/save'
|
||||||
|
|
||||||
const HeaderComponent = React.memo(
|
|
||||||
({
|
|
||||||
messageRef,
|
|
||||||
navigation,
|
|
||||||
currentIndex,
|
|
||||||
imageUrls
|
|
||||||
}: {
|
|
||||||
messageRef: RefObject<FlashMessage>
|
|
||||||
navigation: NativeStackNavigationProp<
|
|
||||||
RootStackParamList,
|
|
||||||
'Screen-ImagesViewer'
|
|
||||||
>
|
|
||||||
currentIndex: number
|
|
||||||
imageUrls: RootStackParamList['Screen-ImagesViewer']['imageUrls']
|
|
||||||
}) => {
|
|
||||||
const insets = useSafeAreaInsets()
|
|
||||||
const { mode, theme } = useTheme()
|
|
||||||
const { t } = useTranslation('screenImageViewer')
|
|
||||||
const { showActionSheetWithOptions } = useActionSheet()
|
|
||||||
|
|
||||||
const onPress = useCallback(() => {
|
|
||||||
analytics('imageviewer_more_press')
|
|
||||||
showActionSheetWithOptions(
|
|
||||||
{
|
|
||||||
options: [
|
|
||||||
t('content.options.save'),
|
|
||||||
t('content.options.share'),
|
|
||||||
t('content.options.cancel')
|
|
||||||
],
|
|
||||||
cancelButtonIndex: 2,
|
|
||||||
userInterfaceStyle: mode
|
|
||||||
},
|
|
||||||
async buttonIndex => {
|
|
||||||
switch (buttonIndex) {
|
|
||||||
case 0:
|
|
||||||
analytics('imageviewer_more_save_press')
|
|
||||||
saveImage({ messageRef, theme, image: imageUrls[currentIndex] })
|
|
||||||
break
|
|
||||||
case 1:
|
|
||||||
analytics('imageviewer_more_share_press')
|
|
||||||
switch (Platform.OS) {
|
|
||||||
case 'ios':
|
|
||||||
await Share.share({ url: imageUrls[currentIndex].url })
|
|
||||||
break
|
|
||||||
case 'android':
|
|
||||||
await Share.share({ message: imageUrls[currentIndex].url })
|
|
||||||
break
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}, [currentIndex])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: insets.top
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<HeaderLeft
|
|
||||||
content='X'
|
|
||||||
native={false}
|
|
||||||
background
|
|
||||||
onPress={() => navigation.goBack()}
|
|
||||||
/>
|
|
||||||
<HeaderCenter
|
|
||||||
inverted
|
|
||||||
content={`${currentIndex + 1} / ${imageUrls.length}`}
|
|
||||||
/>
|
|
||||||
<HeaderRight
|
|
||||||
accessibilityLabel={t('content.actions.accessibilityLabel')}
|
|
||||||
accessibilityHint={t('content.actions.accessibilityHint')}
|
|
||||||
content='MoreHorizontal'
|
|
||||||
native={false}
|
|
||||||
background
|
|
||||||
onPress={onPress}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
(prev, next) => prev.currentIndex === next.currentIndex
|
|
||||||
)
|
|
||||||
|
|
||||||
const ScreenImagesViewer = ({
|
const ScreenImagesViewer = ({
|
||||||
route: {
|
route: {
|
||||||
params: { imageUrls, id }
|
params: { imageUrls, id }
|
||||||
|
@ -118,13 +30,51 @@ const ScreenImagesViewer = ({
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const { theme } = useTheme()
|
const insets = useSafeAreaInsets()
|
||||||
|
|
||||||
|
const { mode, theme } = useTheme()
|
||||||
|
const { t } = useTranslation('screenImageViewer')
|
||||||
|
|
||||||
const initialIndex = imageUrls.findIndex(image => image.id === id)
|
const initialIndex = imageUrls.findIndex(image => image.id === id)
|
||||||
const [currentIndex, setCurrentIndex] = useState(initialIndex)
|
const [currentIndex, setCurrentIndex] = useState(initialIndex)
|
||||||
|
|
||||||
const messageRef = useRef<FlashMessage>(null)
|
const messageRef = useRef<FlashMessage>(null)
|
||||||
|
|
||||||
|
const { showActionSheetWithOptions } = useActionSheet()
|
||||||
|
const onPress = useCallback(() => {
|
||||||
|
analytics('imageviewer_more_press')
|
||||||
|
showActionSheetWithOptions(
|
||||||
|
{
|
||||||
|
options: [
|
||||||
|
t('content.options.save'),
|
||||||
|
t('content.options.share'),
|
||||||
|
t('content.options.cancel')
|
||||||
|
],
|
||||||
|
cancelButtonIndex: 2,
|
||||||
|
userInterfaceStyle: mode
|
||||||
|
},
|
||||||
|
async buttonIndex => {
|
||||||
|
switch (buttonIndex) {
|
||||||
|
case 0:
|
||||||
|
analytics('imageviewer_more_save_press')
|
||||||
|
saveImage({ messageRef, theme, image: imageUrls[currentIndex] })
|
||||||
|
break
|
||||||
|
case 1:
|
||||||
|
analytics('imageviewer_more_share_press')
|
||||||
|
switch (Platform.OS) {
|
||||||
|
case 'ios':
|
||||||
|
await Share.share({ url: imageUrls[currentIndex].url })
|
||||||
|
break
|
||||||
|
case 'android':
|
||||||
|
await Share.share({ message: imageUrls[currentIndex].url })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}, [currentIndex])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaProvider>
|
<SafeAreaProvider>
|
||||||
<StatusBar hidden />
|
<StatusBar hidden />
|
||||||
|
@ -133,14 +83,74 @@ const ScreenImagesViewer = ({
|
||||||
imageIndex={initialIndex}
|
imageIndex={initialIndex}
|
||||||
onImageIndexChange={index => setCurrentIndex(index)}
|
onImageIndexChange={index => setCurrentIndex(index)}
|
||||||
onRequestClose={() => navigation.goBack()}
|
onRequestClose={() => navigation.goBack()}
|
||||||
onLongPress={image => saveImage({ messageRef, theme, image })}
|
onLongPress={() => {
|
||||||
|
analytics('imageviewer_more_press')
|
||||||
|
showActionSheetWithOptions(
|
||||||
|
{
|
||||||
|
options: [
|
||||||
|
t('content.options.save'),
|
||||||
|
t('content.options.share'),
|
||||||
|
t('content.options.cancel')
|
||||||
|
],
|
||||||
|
cancelButtonIndex: 2,
|
||||||
|
userInterfaceStyle: mode
|
||||||
|
},
|
||||||
|
async buttonIndex => {
|
||||||
|
switch (buttonIndex) {
|
||||||
|
case 0:
|
||||||
|
analytics('imageviewer_more_save_press')
|
||||||
|
saveImage({
|
||||||
|
messageRef,
|
||||||
|
theme,
|
||||||
|
image: imageUrls[currentIndex]
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 1:
|
||||||
|
analytics('imageviewer_more_share_press')
|
||||||
|
switch (Platform.OS) {
|
||||||
|
case 'ios':
|
||||||
|
await Share.share({ url: imageUrls[currentIndex].url })
|
||||||
|
break
|
||||||
|
case 'android':
|
||||||
|
await Share.share({
|
||||||
|
message: imageUrls[currentIndex].url
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}}
|
||||||
HeaderComponent={() => (
|
HeaderComponent={() => (
|
||||||
<HeaderComponent
|
<View
|
||||||
messageRef={messageRef}
|
style={{
|
||||||
navigation={navigation}
|
flex: 1,
|
||||||
currentIndex={currentIndex}
|
flexDirection: 'row',
|
||||||
imageUrls={imageUrls}
|
justifyContent: 'space-between',
|
||||||
/>
|
alignItems: 'center',
|
||||||
|
marginTop: insets.top
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HeaderLeft
|
||||||
|
content='X'
|
||||||
|
native={false}
|
||||||
|
background
|
||||||
|
onPress={() => navigation.goBack()}
|
||||||
|
/>
|
||||||
|
<HeaderCenter
|
||||||
|
inverted
|
||||||
|
content={`${currentIndex + 1} / ${imageUrls.length}`}
|
||||||
|
/>
|
||||||
|
<HeaderRight
|
||||||
|
accessibilityLabel={t('content.actions.accessibilityLabel')}
|
||||||
|
accessibilityHint={t('content.actions.accessibilityHint')}
|
||||||
|
content='MoreHorizontal'
|
||||||
|
native={false}
|
||||||
|
background
|
||||||
|
onPress={onPress}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Message ref={messageRef} />
|
<Message ref={messageRef} />
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {
|
||||||
import {
|
import {
|
||||||
getVersionUpdate,
|
getVersionUpdate,
|
||||||
retriveVersionLatest
|
retriveVersionLatest
|
||||||
} from '@utils/slices/versionSlice'
|
} from '@utils/slices/appSlice'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useEffect, useMemo } from 'react'
|
import React, { useCallback, useEffect, useMemo } from 'react'
|
||||||
import { Platform } from 'react-native'
|
import { Platform } from 'react-native'
|
||||||
|
|
|
@ -14,6 +14,7 @@ import TabMePush from './Me/Push'
|
||||||
import TabMeRoot from './Me/Root'
|
import TabMeRoot from './Me/Root'
|
||||||
import TabMeSettings from './Me/Settings'
|
import TabMeSettings from './Me/Settings'
|
||||||
import TabMeSettingsFontsize from './Me/SettingsFontsize'
|
import TabMeSettingsFontsize from './Me/SettingsFontsize'
|
||||||
|
import TabMeSettingsLanguage from './Me/SettingsLanguage'
|
||||||
import TabMeSwitch from './Me/Switch'
|
import TabMeSwitch from './Me/Switch'
|
||||||
import TabSharedRoot from './Shared/Root'
|
import TabSharedRoot from './Shared/Root'
|
||||||
|
|
||||||
|
@ -152,6 +153,19 @@ const TabMe = React.memo(
|
||||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='Tab-Me-Settings-Language'
|
||||||
|
component={TabMeSettingsLanguage}
|
||||||
|
options={({ navigation }: any) => ({
|
||||||
|
title: t('me.stacks.language.name'),
|
||||||
|
...(Platform.OS === 'android' && {
|
||||||
|
headerCenter: () => (
|
||||||
|
<HeaderCenter content={t('me.stacks.language.name')} />
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||||
|
})}
|
||||||
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name='Tab-Me-Switch'
|
name='Tab-Me-Switch'
|
||||||
component={TabMeSwitch}
|
component={TabMeSwitch}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { MenuContainer, MenuRow } from '@components/Menu'
|
||||||
import CustomText from '@components/Text'
|
import CustomText from '@components/Text'
|
||||||
import { useAppDispatch } from '@root/store'
|
import { useAppDispatch } from '@root/store'
|
||||||
import { isDevelopment } from '@utils/checkEnvironment'
|
import { isDevelopment } from '@utils/checkEnvironment'
|
||||||
|
import { getExpoToken } from '@utils/slices/appSlice'
|
||||||
import { updateInstancePush } from '@utils/slices/instances/updatePush'
|
import { updateInstancePush } from '@utils/slices/instances/updatePush'
|
||||||
import { updateInstancePushAlert } from '@utils/slices/instances/updatePushAlert'
|
import { updateInstancePushAlert } from '@utils/slices/instances/updatePushAlert'
|
||||||
import { updateInstancePushDecode } from '@utils/slices/instances/updatePushDecode'
|
import { updateInstancePushDecode } from '@utils/slices/instances/updatePushDecode'
|
||||||
|
@ -45,16 +46,12 @@ const TabMePush: React.FC = () => {
|
||||||
setPushEnabled(settings.granted)
|
setPushEnabled(settings.granted)
|
||||||
setPushCanAskAgain(settings.canAskAgain)
|
setPushCanAskAgain(settings.canAskAgain)
|
||||||
}
|
}
|
||||||
|
const expoToken = useSelector(getExpoToken)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDevelopment) {
|
if (isDevelopment) {
|
||||||
setPushAvailable(true)
|
setPushAvailable(true)
|
||||||
} else {
|
} else {
|
||||||
Notifications.getExpoPushTokenAsync({
|
setPushAvailable(!!expoToken)
|
||||||
experienceId: '@xmflsct/tooot',
|
|
||||||
applicationId: 'com.xmflsct.app.tooot'
|
|
||||||
})
|
|
||||||
.then(data => setPushAvailable(!!data))
|
|
||||||
.catch(() => setPushAvailable(false))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checkPush()
|
checkPush()
|
||||||
|
@ -73,12 +70,22 @@ const TabMePush: React.FC = () => {
|
||||||
const alerts = useMemo(() => {
|
const alerts = useMemo(() => {
|
||||||
return instancePush?.alerts
|
return instancePush?.alerts
|
||||||
? (
|
? (
|
||||||
['follow', 'favourite', 'reblog', 'mention', 'poll'] as [
|
[
|
||||||
'follow',
|
'follow',
|
||||||
|
'follow_request',
|
||||||
'favourite',
|
'favourite',
|
||||||
'reblog',
|
'reblog',
|
||||||
'mention',
|
'mention',
|
||||||
'poll'
|
'poll',
|
||||||
|
'status'
|
||||||
|
] as [
|
||||||
|
'follow',
|
||||||
|
'follow_request',
|
||||||
|
'favourite',
|
||||||
|
'reblog',
|
||||||
|
'mention',
|
||||||
|
'poll',
|
||||||
|
'status'
|
||||||
]
|
]
|
||||||
).map(alert => (
|
).map(alert => (
|
||||||
<MenuRow
|
<MenuRow
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||||
import { getVersionUpdate } from '@utils/slices/versionSlice'
|
import { getVersionUpdate } from '@utils/slices/appSlice'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Linking, Platform } from 'react-native'
|
import { Linking, Platform } from 'react-native'
|
||||||
|
|
|
@ -5,11 +5,8 @@ import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import { LOCALES } from '@root/i18n/locales'
|
import { LOCALES } from '@root/i18n/locales'
|
||||||
import { useAppDispatch } from '@root/store'
|
import { useAppDispatch } from '@root/store'
|
||||||
import androidDefaults from '@utils/slices/instances/push/androidDefaults'
|
|
||||||
import { getInstances } from '@utils/slices/instancesSlice'
|
|
||||||
import {
|
import {
|
||||||
changeBrowser,
|
changeBrowser,
|
||||||
changeLanguage,
|
|
||||||
changeTheme,
|
changeTheme,
|
||||||
getSettingsTheme,
|
getSettingsTheme,
|
||||||
getSettingsBrowser,
|
getSettingsBrowser,
|
||||||
|
@ -19,11 +16,8 @@ import {
|
||||||
getSettingsStaticEmoji,
|
getSettingsStaticEmoji,
|
||||||
changeStaticEmoji
|
changeStaticEmoji
|
||||||
} from '@utils/slices/settingsSlice'
|
} from '@utils/slices/settingsSlice'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
|
||||||
import * as Notifications from 'expo-notifications'
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Platform } from 'react-native'
|
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { mapFontsizeToName } from '../SettingsFontsize'
|
import { mapFontsizeToName } from '../SettingsFontsize'
|
||||||
|
|
||||||
|
@ -31,10 +25,8 @@ const SettingsApp: React.FC = () => {
|
||||||
const navigation = useNavigation<any>()
|
const navigation = useNavigation<any>()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const { showActionSheetWithOptions } = useActionSheet()
|
const { showActionSheetWithOptions } = useActionSheet()
|
||||||
const { mode } = useTheme()
|
|
||||||
const { t, i18n } = useTranslation('screenTabs')
|
const { t, i18n } = useTranslation('screenTabs')
|
||||||
|
|
||||||
const instances = useSelector(getInstances, () => true)
|
|
||||||
const settingsFontsize = useSelector(getSettingsFontsize)
|
const settingsFontsize = useSelector(getSettingsFontsize)
|
||||||
const settingsTheme = useSelector(getSettingsTheme)
|
const settingsTheme = useSelector(getSettingsTheme)
|
||||||
const settingsDarkTheme = useSelector(getSettingsDarkTheme)
|
const settingsDarkTheme = useSelector(getSettingsDarkTheme)
|
||||||
|
@ -49,102 +41,14 @@ const SettingsApp: React.FC = () => {
|
||||||
`me.settings.fontsize.content.${mapFontsizeToName(settingsFontsize)}`
|
`me.settings.fontsize.content.${mapFontsizeToName(settingsFontsize)}`
|
||||||
)}
|
)}
|
||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
onPress={() => {
|
onPress={() => navigation.navigate('Tab-Me-Settings-Fontsize')}
|
||||||
navigation.navigate('Tab-Me-Settings-Fontsize')
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
title={t('me.settings.language.heading')}
|
title={t('me.settings.language.heading')}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
content={LOCALES[i18n.language]}
|
content={LOCALES[i18n.language]}
|
||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
onPress={() => {
|
onPress={() => navigation.navigate('Tab-Me-Settings-Language')}
|
||||||
const options = Object.keys(LOCALES)
|
|
||||||
// @ts-ignore
|
|
||||||
.map(locale => LOCALES[locale])
|
|
||||||
.concat(t('me.settings.language.options.cancel'))
|
|
||||||
|
|
||||||
showActionSheetWithOptions(
|
|
||||||
{
|
|
||||||
title: t('me.settings.language.heading'),
|
|
||||||
options,
|
|
||||||
cancelButtonIndex: options.length - 1,
|
|
||||||
userInterfaceStyle: mode
|
|
||||||
},
|
|
||||||
buttonIndex => {
|
|
||||||
if (buttonIndex === undefined) return
|
|
||||||
if (buttonIndex < options.length - 1) {
|
|
||||||
analytics('settings_language_press', {
|
|
||||||
current: i18n.language,
|
|
||||||
new: options[buttonIndex]
|
|
||||||
})
|
|
||||||
haptics('Success')
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
dispatch(changeLanguage(Object.keys(LOCALES)[buttonIndex]))
|
|
||||||
i18n.changeLanguage(Object.keys(LOCALES)[buttonIndex])
|
|
||||||
|
|
||||||
// Update Android notification channel language
|
|
||||||
if (Platform.OS === 'android') {
|
|
||||||
instances.forEach(instance => {
|
|
||||||
const accountFull = `@${instance.account.acct}@${instance.uri}`
|
|
||||||
if (instance.push.decode.value === false) {
|
|
||||||
Notifications.setNotificationChannelAsync(
|
|
||||||
`${accountFull}_default`,
|
|
||||||
{
|
|
||||||
groupId: accountFull,
|
|
||||||
name: t('me.push.default.heading'),
|
|
||||||
...androidDefaults
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Notifications.setNotificationChannelAsync(
|
|
||||||
`${accountFull}_follow`,
|
|
||||||
{
|
|
||||||
groupId: accountFull,
|
|
||||||
name: t('me.push.follow.heading'),
|
|
||||||
...androidDefaults
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Notifications.setNotificationChannelAsync(
|
|
||||||
`${accountFull}_favourite`,
|
|
||||||
{
|
|
||||||
groupId: accountFull,
|
|
||||||
name: t('me.push.favourite.heading'),
|
|
||||||
...androidDefaults
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Notifications.setNotificationChannelAsync(
|
|
||||||
`${accountFull}_reblog`,
|
|
||||||
{
|
|
||||||
groupId: accountFull,
|
|
||||||
name: t('me.push.reblog.heading'),
|
|
||||||
...androidDefaults
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Notifications.setNotificationChannelAsync(
|
|
||||||
`${accountFull}_mention`,
|
|
||||||
{
|
|
||||||
groupId: accountFull,
|
|
||||||
name: t('me.push.mention.heading'),
|
|
||||||
...androidDefaults
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Notifications.setNotificationChannelAsync(
|
|
||||||
`${accountFull}_poll`,
|
|
||||||
{
|
|
||||||
groupId: accountFull,
|
|
||||||
name: t('me.push.poll.heading'),
|
|
||||||
...androidDefaults
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
title={t('me.settings.theme.heading')}
|
title={t('me.settings.theme.heading')}
|
||||||
|
|
|
@ -45,7 +45,7 @@ const TabMeSettingsFontsize: React.FC<
|
||||||
const item = {
|
const item = {
|
||||||
id: 'demo',
|
id: 'demo',
|
||||||
uri: 'https://tooot.app',
|
uri: 'https://tooot.app',
|
||||||
created_at: new Date(),
|
created_at: new Date(2021, 4, 16),
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
visibility: 'public',
|
visibility: 'public',
|
||||||
replies_count: 0,
|
replies_count: 0,
|
||||||
|
@ -67,6 +67,7 @@ const TabMeSettingsFontsize: React.FC<
|
||||||
username: 'tooot📱',
|
username: 'tooot📱',
|
||||||
acct: 'tooot@xmflsct.com',
|
acct: 'tooot@xmflsct.com',
|
||||||
display_name: 'tooot📱',
|
display_name: 'tooot📱',
|
||||||
|
avatar: 'https://avatars.githubusercontent.com/u/77554750?s=100',
|
||||||
avatar_static: 'https://avatars.githubusercontent.com/u/77554750?s=100'
|
avatar_static: 'https://avatars.githubusercontent.com/u/77554750?s=100'
|
||||||
},
|
},
|
||||||
media_attachments: [],
|
media_attachments: [],
|
||||||
|
@ -100,7 +101,7 @@ const TabMeSettingsFontsize: React.FC<
|
||||||
}, [theme, initialSize])
|
}, [theme, initialSize])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView scrollEnabled={false}>
|
<ScrollView>
|
||||||
<CustomText
|
<CustomText
|
||||||
fontStyle='M'
|
fontStyle='M'
|
||||||
style={{
|
style={{
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
|
import haptics from '@components/haptics'
|
||||||
|
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||||
|
import { LOCALES } from '@root/i18n/locales'
|
||||||
|
import { TabMeStackScreenProps } from '@utils/navigation/navigators'
|
||||||
|
import androidDefaults from '@utils/slices/instances/push/androidDefaults'
|
||||||
|
import { getInstances } from '@utils/slices/instancesSlice'
|
||||||
|
import { changeLanguage } from '@utils/slices/settingsSlice'
|
||||||
|
import * as Notifications from 'expo-notifications'
|
||||||
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { FlatList, Platform } from 'react-native'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
|
||||||
|
const TabMeSettingsLanguage: React.FC<
|
||||||
|
TabMeStackScreenProps<'Tab-Me-Settings-Language'>
|
||||||
|
> = ({ navigation }) => {
|
||||||
|
const { i18n, t } = useTranslation('screenTabs')
|
||||||
|
const languages = Object.entries(LOCALES)
|
||||||
|
const instances = useSelector(getInstances)
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
const change = (lang: string) => {
|
||||||
|
analytics('settings_language_press', {
|
||||||
|
current: i18n.language,
|
||||||
|
new: lang
|
||||||
|
})
|
||||||
|
haptics('Success')
|
||||||
|
|
||||||
|
dispatch(changeLanguage(lang))
|
||||||
|
i18n.changeLanguage(lang)
|
||||||
|
|
||||||
|
// Update Android notification channel language
|
||||||
|
if (Platform.OS === 'android') {
|
||||||
|
instances.forEach(instance => {
|
||||||
|
const accountFull = `@${instance.account.acct}@${instance.uri}`
|
||||||
|
if (instance.push.decode.value === false) {
|
||||||
|
Notifications.setNotificationChannelAsync(`${accountFull}_default`, {
|
||||||
|
groupId: accountFull,
|
||||||
|
name: t('me.push.default.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Notifications.setNotificationChannelAsync(`${accountFull}_follow`, {
|
||||||
|
groupId: accountFull,
|
||||||
|
name: t('me.push.follow.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
})
|
||||||
|
Notifications.setNotificationChannelAsync(
|
||||||
|
`${accountFull}_favourite`,
|
||||||
|
{
|
||||||
|
groupId: accountFull,
|
||||||
|
name: t('me.push.favourite.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Notifications.setNotificationChannelAsync(`${accountFull}_reblog`, {
|
||||||
|
groupId: accountFull,
|
||||||
|
name: t('me.push.reblog.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
})
|
||||||
|
Notifications.setNotificationChannelAsync(`${accountFull}_mention`, {
|
||||||
|
groupId: accountFull,
|
||||||
|
name: t('me.push.mention.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
})
|
||||||
|
Notifications.setNotificationChannelAsync(`${accountFull}_poll`, {
|
||||||
|
groupId: accountFull,
|
||||||
|
name: t('me.push.poll.heading'),
|
||||||
|
...androidDefaults
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
navigation.pop(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuContainer>
|
||||||
|
<FlatList
|
||||||
|
data={languages}
|
||||||
|
renderItem={({ item }) => {
|
||||||
|
return (
|
||||||
|
<MenuRow
|
||||||
|
key={item[0]}
|
||||||
|
title={item[1]}
|
||||||
|
iconBack={item[0] === i18n.language ? 'Check' : undefined}
|
||||||
|
iconBackColor={'blue'}
|
||||||
|
onPress={() => item[0] !== i18n.language && change(item[0])}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</MenuContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TabMeSettingsLanguage
|
|
@ -5,11 +5,8 @@ import ComponentInstance from '@components/Instance'
|
||||||
import CustomText from '@components/Text'
|
import CustomText from '@components/Text'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import initQuery from '@utils/initQuery'
|
import initQuery from '@utils/initQuery'
|
||||||
import {
|
import { InstanceLatest } from '@utils/migrations/instances/migration'
|
||||||
getInstanceActive,
|
import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice'
|
||||||
getInstances,
|
|
||||||
Instance
|
|
||||||
} from '@utils/slices/instancesSlice'
|
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useEffect, useRef } from 'react'
|
import React, { useEffect, useRef } from 'react'
|
||||||
|
@ -19,7 +16,7 @@ import { ScrollView } from 'react-native-gesture-handler'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
instance: Instance
|
instance: InstanceLatest
|
||||||
selected?: boolean
|
selected?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
} from '@utils/slices/instancesSlice'
|
} from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useMemo } from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { View } from 'react-native'
|
import { View } from 'react-native'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
@ -35,23 +35,6 @@ const AccountInformationAccount: React.FC<Props> = ({
|
||||||
options: { enabled: account !== undefined }
|
options: { enabled: account !== undefined }
|
||||||
})
|
})
|
||||||
|
|
||||||
const movedContent = useMemo(() => {
|
|
||||||
if (account?.moved) {
|
|
||||||
return (
|
|
||||||
<CustomText
|
|
||||||
fontStyle='M'
|
|
||||||
style={{
|
|
||||||
marginLeft: StyleConstants.Spacing.S,
|
|
||||||
color: colors.secondary
|
|
||||||
}}
|
|
||||||
selectable
|
|
||||||
>
|
|
||||||
@{account.moved.acct}
|
|
||||||
</CustomText>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}, [account?.moved])
|
|
||||||
|
|
||||||
if (account || (localInstance && instanceAccount)) {
|
if (account || (localInstance && instanceAccount)) {
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
@ -62,23 +45,24 @@ const AccountInformationAccount: React.FC<Props> = ({
|
||||||
marginBottom: StyleConstants.Spacing.L
|
marginBottom: StyleConstants.Spacing.L
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CustomText
|
<CustomText fontStyle='M' style={{ color: colors.secondary }}>
|
||||||
fontStyle='M'
|
{account?.moved ? (
|
||||||
style={{
|
<>
|
||||||
textDecorationLine: account?.moved ? 'line-through' : undefined,
|
{' '}
|
||||||
color: colors.secondary
|
<CustomText selectable>@{account.moved.acct}</CustomText>
|
||||||
}}
|
</>
|
||||||
selectable
|
) : null}
|
||||||
>
|
<CustomText
|
||||||
@{localInstance ? instanceAccount?.acct : account?.acct}
|
style={{
|
||||||
{localInstance ? `@${instanceUri}` : null}
|
textDecorationLine: account?.moved ? 'line-through' : undefined
|
||||||
</CustomText>
|
}}
|
||||||
{relationship?.followed_by ? (
|
selectable
|
||||||
<CustomText fontStyle='M' style={{ color: colors.secondary }}>
|
>
|
||||||
{t('shared.account.followed_by')}
|
@{localInstance ? instanceAccount?.acct : account?.acct}
|
||||||
|
{localInstance ? `@${instanceUri}` : null}
|
||||||
</CustomText>
|
</CustomText>
|
||||||
) : null}
|
{relationship?.followed_by ? t('shared.account.followed_by') : null}
|
||||||
{movedContent}
|
</CustomText>
|
||||||
{account?.locked ? (
|
{account?.locked ? (
|
||||||
<Icon
|
<Icon
|
||||||
name='Lock'
|
name='Lock'
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
/// <reference types="@welldone-software/why-did-you-render" />
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import log from './log'
|
import log from './log'
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { isRelease } from '@utils/checkEnvironment'
|
|
||||||
import * as Sentry from 'sentry-expo'
|
import * as Sentry from 'sentry-expo'
|
||||||
import log from './log'
|
import log from './log'
|
||||||
|
|
||||||
|
@ -7,7 +6,7 @@ const sentry = () => {
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: 'https://53348b60ff844d52886e90251b3a5f41@o917354.ingest.sentry.io/6410576',
|
dsn: 'https://53348b60ff844d52886e90251b3a5f41@o917354.ingest.sentry.io/6410576',
|
||||||
enableInExpoDevelopment: false,
|
enableInExpoDevelopment: false,
|
||||||
debug: !isRelease,
|
// debug: !isRelease,
|
||||||
autoSessionTracking: true
|
autoSessionTracking: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
11
src/store.ts
11
src/store.ts
|
@ -4,10 +4,10 @@ import { AnyAction, configureStore, Reducer } from '@reduxjs/toolkit'
|
||||||
import contextsMigration from '@utils/migrations/contexts/migration'
|
import contextsMigration from '@utils/migrations/contexts/migration'
|
||||||
import instancesMigration from '@utils/migrations/instances/migration'
|
import instancesMigration from '@utils/migrations/instances/migration'
|
||||||
import settingsMigration from '@utils/migrations/settings/migration'
|
import settingsMigration from '@utils/migrations/settings/migration'
|
||||||
|
import appSlice from '@utils/slices/appSlice'
|
||||||
import contextsSlice, { ContextsState } from '@utils/slices/contextsSlice'
|
import contextsSlice, { ContextsState } from '@utils/slices/contextsSlice'
|
||||||
import instancesSlice, { InstancesState } from '@utils/slices/instancesSlice'
|
import instancesSlice, { InstancesState } from '@utils/slices/instancesSlice'
|
||||||
import settingsSlice, { SettingsState } from '@utils/slices/settingsSlice'
|
import settingsSlice, { SettingsState } from '@utils/slices/settingsSlice'
|
||||||
import versionSlice from '@utils/slices/versionSlice'
|
|
||||||
import { Platform } from 'react-native'
|
import { Platform } from 'react-native'
|
||||||
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
|
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
|
||||||
import {
|
import {
|
||||||
|
@ -39,7 +39,7 @@ const instancesPersistConfig = {
|
||||||
key: 'instances',
|
key: 'instances',
|
||||||
prefix,
|
prefix,
|
||||||
storage: Platform.OS === 'ios' ? secureStorage : AsyncStorage,
|
storage: Platform.OS === 'ios' ? secureStorage : AsyncStorage,
|
||||||
version: 9,
|
version: 10,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
migrate: createMigrate(instancesMigration)
|
migrate: createMigrate(instancesMigration)
|
||||||
}
|
}
|
||||||
|
@ -67,18 +67,13 @@ const store = configureStore({
|
||||||
SettingsState,
|
SettingsState,
|
||||||
AnyAction
|
AnyAction
|
||||||
>,
|
>,
|
||||||
version: versionSlice
|
app: appSlice
|
||||||
},
|
},
|
||||||
middleware: getDefaultMiddleware =>
|
middleware: getDefaultMiddleware =>
|
||||||
getDefaultMiddleware({
|
getDefaultMiddleware({
|
||||||
serializableCheck: {
|
serializableCheck: {
|
||||||
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER]
|
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER]
|
||||||
}
|
}
|
||||||
}).concat(store => next => action => {
|
|
||||||
console.log('dispatching', action)
|
|
||||||
let result = next(action)
|
|
||||||
console.log('next state', store.getState())
|
|
||||||
return result
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import queryClient from '@helpers/queryClient'
|
import queryClient from '@helpers/queryClient'
|
||||||
import { store } from '@root/store'
|
import { store } from '@root/store'
|
||||||
|
import { InstanceLatest } from './migrations/instances/migration'
|
||||||
// import { prefetchTimelineQuery } from './queryHooks/timeline'
|
// import { prefetchTimelineQuery } from './queryHooks/timeline'
|
||||||
import { Instance, updateInstanceActive } from './slices/instancesSlice'
|
import { updateInstanceActive } from './slices/instancesSlice'
|
||||||
|
|
||||||
const initQuery = async ({
|
const initQuery = async ({
|
||||||
instance,
|
instance,
|
||||||
prefetch
|
prefetch
|
||||||
}: {
|
}: {
|
||||||
instance: Instance
|
instance: InstanceLatest
|
||||||
prefetch?: { enabled: boolean; page?: 'Following' | 'LocalPublic' }
|
prefetch?: { enabled: boolean; page?: 'Following' | 'LocalPublic' }
|
||||||
}) => {
|
}) => {
|
||||||
store.dispatch(updateInstanceActive(instance))
|
store.dispatch(updateInstanceActive(instance))
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { InstanceV6 } from './v6'
|
||||||
import { InstanceV7 } from './v7'
|
import { InstanceV7 } from './v7'
|
||||||
import { InstanceV8 } from './v8'
|
import { InstanceV8 } from './v8'
|
||||||
import { InstanceV9 } from './v9'
|
import { InstanceV9 } from './v9'
|
||||||
|
import { InstanceV10 } from './v10'
|
||||||
|
|
||||||
const instancesMigration = {
|
const instancesMigration = {
|
||||||
4: (state: InstanceV3): InstanceV4 => {
|
4: (state: InstanceV3): InstanceV4 => {
|
||||||
|
@ -99,7 +100,37 @@ const instancesMigration = {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
10: (state: { instances: InstanceV9[] }): { instances: InstanceV10[] } => {
|
||||||
|
return {
|
||||||
|
instances: state.instances.map(instance => {
|
||||||
|
return {
|
||||||
|
...instance,
|
||||||
|
notifications_filter: {
|
||||||
|
...instance.notifications_filter,
|
||||||
|
status: true,
|
||||||
|
update: true
|
||||||
|
},
|
||||||
|
push: {
|
||||||
|
...instance.push,
|
||||||
|
alerts: {
|
||||||
|
...instance.push.alerts,
|
||||||
|
follow_request: {
|
||||||
|
loading: false,
|
||||||
|
value: true
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
loading: false,
|
||||||
|
value: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { InstanceV10 as InstanceLatest }
|
||||||
|
|
||||||
export default instancesMigration
|
export default instancesMigration
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
import { ComposeStateDraft } from '@screens/Compose/utils/types'
|
||||||
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
|
|
||||||
|
export type InstanceV10 = {
|
||||||
|
active: boolean
|
||||||
|
appData: {
|
||||||
|
clientId: string
|
||||||
|
clientSecret: string
|
||||||
|
}
|
||||||
|
url: string
|
||||||
|
token: string
|
||||||
|
uri: Mastodon.Instance['uri']
|
||||||
|
urls: Mastodon.Instance['urls']
|
||||||
|
account: {
|
||||||
|
id: Mastodon.Account['id']
|
||||||
|
acct: Mastodon.Account['acct']
|
||||||
|
avatarStatic: Mastodon.Account['avatar_static']
|
||||||
|
preferences: Mastodon.Preferences
|
||||||
|
}
|
||||||
|
version: string
|
||||||
|
configuration?: Mastodon.Instance['configuration']
|
||||||
|
filters: Mastodon.Filter[]
|
||||||
|
notifications_filter: {
|
||||||
|
follow: boolean
|
||||||
|
follow_request: boolean
|
||||||
|
favourite: boolean
|
||||||
|
reblog: boolean
|
||||||
|
mention: boolean
|
||||||
|
poll: boolean
|
||||||
|
status: boolean
|
||||||
|
update: boolean
|
||||||
|
}
|
||||||
|
push: {
|
||||||
|
global: { loading: boolean; value: boolean }
|
||||||
|
decode: { loading: boolean; value: boolean }
|
||||||
|
alerts: {
|
||||||
|
follow: {
|
||||||
|
loading: boolean
|
||||||
|
value: Mastodon.PushSubscription['alerts']['follow']
|
||||||
|
}
|
||||||
|
follow_request: {
|
||||||
|
loading: boolean
|
||||||
|
value: Mastodon.PushSubscription['alerts']['follow_request']
|
||||||
|
}
|
||||||
|
favourite: {
|
||||||
|
loading: boolean
|
||||||
|
value: Mastodon.PushSubscription['alerts']['favourite']
|
||||||
|
}
|
||||||
|
reblog: {
|
||||||
|
loading: boolean
|
||||||
|
value: Mastodon.PushSubscription['alerts']['reblog']
|
||||||
|
}
|
||||||
|
mention: {
|
||||||
|
loading: boolean
|
||||||
|
value: Mastodon.PushSubscription['alerts']['mention']
|
||||||
|
}
|
||||||
|
poll: {
|
||||||
|
loading: boolean
|
||||||
|
value: Mastodon.PushSubscription['alerts']['poll']
|
||||||
|
}
|
||||||
|
status: {
|
||||||
|
loading: boolean
|
||||||
|
value: Mastodon.PushSubscription['alerts']['status']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keys: {
|
||||||
|
auth?: string
|
||||||
|
public?: string // legacy
|
||||||
|
private?: string // legacy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timelinesLookback?: {
|
||||||
|
[key: string]: {
|
||||||
|
queryKey: QueryKeyTimeline
|
||||||
|
ids: Mastodon.Status['id'][]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mePage: {
|
||||||
|
lists: { shown: boolean }
|
||||||
|
announcements: { shown: boolean; unread: number }
|
||||||
|
}
|
||||||
|
drafts: ComposeStateDraft[]
|
||||||
|
frequentEmojis: {
|
||||||
|
emoji: Pick<Mastodon.Emoji, 'shortcode' | 'url' | 'static_url'>
|
||||||
|
score: number
|
||||||
|
count: number
|
||||||
|
lastUsed: number
|
||||||
|
}[]
|
||||||
|
}
|
|
@ -20,6 +20,10 @@ export type RootStackParamList = {
|
||||||
| {
|
| {
|
||||||
type: 'notifications_filter'
|
type: 'notifications_filter'
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: 'alt_text'
|
||||||
|
text: string
|
||||||
|
}
|
||||||
'Screen-Announcements': { showAll: boolean }
|
'Screen-Announcements': { showAll: boolean }
|
||||||
'Screen-Compose':
|
'Screen-Compose':
|
||||||
| {
|
| {
|
||||||
|
@ -146,6 +150,7 @@ export type TabMeStackParamList = {
|
||||||
'Tab-Me-Push': undefined
|
'Tab-Me-Push': undefined
|
||||||
'Tab-Me-Settings': undefined
|
'Tab-Me-Settings': undefined
|
||||||
'Tab-Me-Settings-Fontsize': undefined
|
'Tab-Me-Settings-Fontsize': undefined
|
||||||
|
'Tab-Me-Settings-Language': undefined
|
||||||
'Tab-Me-Switch': undefined
|
'Tab-Me-Switch': undefined
|
||||||
} & TabSharedStackParamList
|
} & TabSharedStackParamList
|
||||||
export type TabMeStackScreenProps<T extends keyof TabMeStackParamList> =
|
export type TabMeStackScreenProps<T extends keyof TabMeStackParamList> =
|
||||||
|
|
|
@ -3,84 +3,102 @@ import apiTooot from '@api/tooot'
|
||||||
import { displayMessage } from '@components/Message'
|
import { displayMessage } from '@components/Message'
|
||||||
import navigationRef from '@helpers/navigationRef'
|
import navigationRef from '@helpers/navigationRef'
|
||||||
import { useAppDispatch } from '@root/store'
|
import { useAppDispatch } from '@root/store'
|
||||||
import { isDevelopment } from '@utils/checkEnvironment'
|
import { InstanceLatest } from '@utils/migrations/instances/migration'
|
||||||
import { disableAllPushes, Instance } from '@utils/slices/instancesSlice'
|
import { getExpoToken, retriveExpoToken } from '@utils/slices/appSlice'
|
||||||
|
import { disableAllPushes } from '@utils/slices/instancesSlice'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import * as Notifications from 'expo-notifications'
|
import * as Notifications from 'expo-notifications'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { TFunction } from 'react-i18next'
|
import { TFunction } from 'react-i18next'
|
||||||
|
import { AppState } from 'react-native'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
export interface Params {
|
export interface Params {
|
||||||
t: TFunction<'screens'>
|
t: TFunction<'screens'>
|
||||||
instances: Instance[]
|
instances: InstanceLatest[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const pushUseConnect = ({ t, instances }: Params) => {
|
const pushUseConnect = ({ t, instances }: Params) => {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(retriveExpoToken())
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const expoToken = useSelector(getExpoToken)
|
||||||
|
|
||||||
|
const connect = () => {
|
||||||
|
apiTooot({
|
||||||
|
method: 'get',
|
||||||
|
url: `push/connect/${expoToken}`,
|
||||||
|
sentry: true
|
||||||
|
}).catch(error => {
|
||||||
|
if (error?.status == 404) {
|
||||||
|
displayMessage({
|
||||||
|
theme,
|
||||||
|
type: 'error',
|
||||||
|
duration: 'long',
|
||||||
|
message: t('pushError.message'),
|
||||||
|
description: t('pushError.description'),
|
||||||
|
onPress: () => {
|
||||||
|
navigationRef.navigate('Screen-Tabs', {
|
||||||
|
screen: 'Tab-Me',
|
||||||
|
params: {
|
||||||
|
screen: 'Tab-Me-Root'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
navigationRef.navigate('Screen-Tabs', {
|
||||||
|
screen: 'Tab-Me',
|
||||||
|
params: {
|
||||||
|
screen: 'Tab-Me-Settings'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
dispatch(disableAllPushes())
|
||||||
|
|
||||||
|
instances.forEach(instance => {
|
||||||
|
if (instance.push.global.value) {
|
||||||
|
apiGeneral<{}>({
|
||||||
|
method: 'delete',
|
||||||
|
domain: instance.url,
|
||||||
|
url: 'api/v1/push/subscription',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${instance.token}`
|
||||||
|
}
|
||||||
|
}).catch(() => console.log('error!!!'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const pushEnabled = instances.filter(instance => instance.push.global.value)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const appStateListener = AppState.addEventListener('change', state => {
|
||||||
|
console.log('changing state to', state)
|
||||||
|
if (expoToken && pushEnabled.length && state === 'active') {
|
||||||
|
Notifications.getBadgeCountAsync().then(count => {
|
||||||
|
if (count > 0) {
|
||||||
|
Notifications.setBadgeCountAsync(0)
|
||||||
|
connect()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
appStateListener.remove()
|
||||||
|
}
|
||||||
|
}, [expoToken, pushEnabled.length])
|
||||||
|
|
||||||
return useEffect(() => {
|
return useEffect(() => {
|
||||||
const connect = async () => {
|
if (expoToken && pushEnabled.length) {
|
||||||
const expoToken = isDevelopment
|
|
||||||
? 'DEVELOPMENT_TOKEN_1'
|
|
||||||
: (
|
|
||||||
await Notifications.getExpoPushTokenAsync({
|
|
||||||
experienceId: '@xmflsct/tooot',
|
|
||||||
applicationId: 'com.xmflsct.app.tooot'
|
|
||||||
})
|
|
||||||
).data
|
|
||||||
|
|
||||||
apiTooot({
|
|
||||||
method: 'get',
|
|
||||||
url: `push/connect/${expoToken}`,
|
|
||||||
sentry: true
|
|
||||||
}).catch(error => {
|
|
||||||
if (error?.status == 404) {
|
|
||||||
displayMessage({
|
|
||||||
theme,
|
|
||||||
type: 'error',
|
|
||||||
duration: 'long',
|
|
||||||
message: t('pushError.message'),
|
|
||||||
description: t('pushError.description'),
|
|
||||||
onPress: () => {
|
|
||||||
navigationRef.navigate('Screen-Tabs', {
|
|
||||||
screen: 'Tab-Me',
|
|
||||||
params: {
|
|
||||||
screen: 'Tab-Me-Root'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
navigationRef.navigate('Screen-Tabs', {
|
|
||||||
screen: 'Tab-Me',
|
|
||||||
params: {
|
|
||||||
screen: 'Tab-Me-Settings'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
dispatch(disableAllPushes())
|
|
||||||
|
|
||||||
instances.forEach(instance => {
|
|
||||||
if (instance.push.global.value) {
|
|
||||||
apiGeneral<{}>({
|
|
||||||
method: 'delete',
|
|
||||||
domain: instance.url,
|
|
||||||
url: 'api/v1/push/subscription',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${instance.token}`
|
|
||||||
}
|
|
||||||
}).catch(() => console.log('error!!!'))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const pushEnabled = instances.filter(instance => instance.push.global.value)
|
|
||||||
if (pushEnabled.length) {
|
|
||||||
connect()
|
connect()
|
||||||
}
|
}
|
||||||
}, [instances])
|
}, [expoToken, pushEnabled.length])
|
||||||
}
|
}
|
||||||
|
|
||||||
export default pushUseConnect
|
export default pushUseConnect
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { displayMessage } from '@components/Message'
|
import { displayMessage } from '@components/Message'
|
||||||
import queryClient from '@helpers/queryClient'
|
import queryClient from '@helpers/queryClient'
|
||||||
import initQuery from '@utils/initQuery'
|
import initQuery from '@utils/initQuery'
|
||||||
|
import { InstanceLatest } from '@utils/migrations/instances/migration'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import { Instance } from '@utils/slices/instancesSlice'
|
|
||||||
import * as Notifications from 'expo-notifications'
|
import * as Notifications from 'expo-notifications'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import pushUseNavigate from './useNavigate'
|
import pushUseNavigate from './useNavigate'
|
||||||
|
|
||||||
export interface Params {
|
export interface Params {
|
||||||
instances: Instance[]
|
instances: InstanceLatest[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const pushUseReceive = ({ instances }: Params) => {
|
const pushUseReceive = ({ instances }: Params) => {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import queryClient from '@helpers/queryClient'
|
import queryClient from '@helpers/queryClient'
|
||||||
import initQuery from '@utils/initQuery'
|
import initQuery from '@utils/initQuery'
|
||||||
|
import { InstanceLatest } from '@utils/migrations/instances/migration'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import { Instance } from '@utils/slices/instancesSlice'
|
|
||||||
import * as Notifications from 'expo-notifications'
|
import * as Notifications from 'expo-notifications'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import pushUseNavigate from './useNavigate'
|
import pushUseNavigate from './useNavigate'
|
||||||
|
|
||||||
export interface Params {
|
export interface Params {
|
||||||
instances: Instance[]
|
instances: InstanceLatest[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const pushUseRespond = ({ instances }: Params) => {
|
const pushUseRespond = ({ instances }: Params) => {
|
||||||
|
|
|
@ -2,7 +2,10 @@ import apiInstance, { InstanceResponse } from '@api/instance'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import queryClient from '@helpers/queryClient'
|
import queryClient from '@helpers/queryClient'
|
||||||
import { store } from '@root/store'
|
import { store } from '@root/store'
|
||||||
import { getInstanceNotificationsFilter } from '@utils/slices/instancesSlice'
|
import {
|
||||||
|
checkInstanceFeature,
|
||||||
|
getInstanceNotificationsFilter
|
||||||
|
} from '@utils/slices/instancesSlice'
|
||||||
import { AxiosError } from 'axios'
|
import { AxiosError } from 'axios'
|
||||||
import { uniqBy } from 'lodash'
|
import { uniqBy } from 'lodash'
|
||||||
import {
|
import {
|
||||||
|
@ -62,16 +65,26 @@ const queryFunction = async ({
|
||||||
case 'Notifications':
|
case 'Notifications':
|
||||||
const rootStore = store.getState()
|
const rootStore = store.getState()
|
||||||
const notificationsFilter = getInstanceNotificationsFilter(rootStore)
|
const notificationsFilter = getInstanceNotificationsFilter(rootStore)
|
||||||
|
const usePositiveFilter = checkInstanceFeature(
|
||||||
|
'notification_types_positive_filter'
|
||||||
|
)(rootStore)
|
||||||
return apiInstance<Mastodon.Notification[]>({
|
return apiInstance<Mastodon.Notification[]>({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: 'notifications',
|
url: 'notifications',
|
||||||
params: {
|
params: {
|
||||||
...params,
|
...params,
|
||||||
...(notificationsFilter && {
|
...(notificationsFilter &&
|
||||||
exclude_types: Object.keys(notificationsFilter)
|
(usePositiveFilter
|
||||||
// @ts-ignore
|
? {
|
||||||
.filter(filter => notificationsFilter[filter] === false)
|
types: Object.keys(notificationsFilter)
|
||||||
})
|
// @ts-ignore
|
||||||
|
.filter(filter => notificationsFilter[filter] === true)
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
exclude_types: Object.keys(notificationsFilter)
|
||||||
|
// @ts-ignore
|
||||||
|
.filter(filter => notificationsFilter[filter] === false)
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -427,8 +440,8 @@ const useTimelineMutation = ({
|
||||||
...(onMutate && {
|
...(onMutate && {
|
||||||
onMutate: params => {
|
onMutate: params => {
|
||||||
queryClient.cancelQueries(params.queryKey)
|
queryClient.cancelQueries(params.queryKey)
|
||||||
let oldData
|
const oldData =
|
||||||
params.queryKey && (oldData = queryClient.getQueryData(params.queryKey))
|
params.queryKey && queryClient.getQueryData(params.queryKey)
|
||||||
|
|
||||||
haptics('Light')
|
haptics('Light')
|
||||||
switch (params.type) {
|
switch (params.type) {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue