mirror of
https://github.com/tooot-app/app
synced 2025-03-06 12:37:44 +01:00
619 restructure local storage (#628)
* To MMKV migration working * POC migrated font size settings * Moved settings to mmkv * Fix typos * Migrated contexts slice * Migrated app slice * POC instance emoji update * Migrated drafts * Migrated simple instance properties * All migrated! * Re-structure files * Tolerant of undefined settings * Can properly logging in and out including empty state
This commit is contained in:
parent
71ccb4a93c
commit
1ea6aff328
@ -8,11 +8,8 @@ module.exports = function (api) {
|
||||
{
|
||||
root: ['./'],
|
||||
alias: {
|
||||
'@assets': './assets',
|
||||
'@root': './src',
|
||||
'@api': './src/api',
|
||||
'@helpers': './src/helpers',
|
||||
'@components': './src/components',
|
||||
'@i18n': './src/i18n',
|
||||
'@screens': './src/screens',
|
||||
'@utils': './src/utils'
|
||||
}
|
||||
|
2
index.js
2
index.js
@ -1,6 +1,6 @@
|
||||
import { registerRootComponent } from 'expo'
|
||||
|
||||
import App from '@root/App'
|
||||
import App from './src/App'
|
||||
|
||||
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
|
||||
// It also ensures that whether you load the app in the Expo client or in a native build,
|
||||
|
@ -65,6 +65,9 @@ PODS:
|
||||
- libwebp/mux (1.2.4):
|
||||
- libwebp/demux
|
||||
- libwebp/webp (1.2.4)
|
||||
- MMKV (1.2.14):
|
||||
- MMKVCore (~> 1.2.14)
|
||||
- MMKVCore (1.2.14)
|
||||
- RCT-Folly (2021.07.22.00):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
@ -305,6 +308,9 @@ PODS:
|
||||
- React
|
||||
- react-native-menu (0.7.2):
|
||||
- React
|
||||
- react-native-mmkv (2.5.1):
|
||||
- MMKV (>= 1.2.13)
|
||||
- React-Core
|
||||
- react-native-netinfo (9.3.7):
|
||||
- React-Core
|
||||
- react-native-pager-view (6.1.2):
|
||||
@ -494,6 +500,7 @@ DEPENDENCIES:
|
||||
- react-native-ios-context-menu (from `../node_modules/react-native-ios-context-menu`)
|
||||
- react-native-language-detection (from `../node_modules/react-native-language-detection`)
|
||||
- "react-native-menu (from `../node_modules/@react-native-menu/menu`)"
|
||||
- react-native-mmkv (from `../node_modules/react-native-mmkv`)
|
||||
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
|
||||
- react-native-pager-view (from `../node_modules/react-native-pager-view`)
|
||||
- "react-native-paste-input (from `../node_modules/@mattermost/react-native-paste-input`)"
|
||||
@ -527,6 +534,8 @@ SPEC REPOS:
|
||||
- fmt
|
||||
- libevent
|
||||
- libwebp
|
||||
- MMKV
|
||||
- MMKVCore
|
||||
- SDWebImage
|
||||
- SDWebImageWebPCoder
|
||||
- Sentry
|
||||
@ -629,6 +638,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-language-detection"
|
||||
react-native-menu:
|
||||
:path: "../node_modules/@react-native-menu/menu"
|
||||
react-native-mmkv:
|
||||
:path: "../node_modules/react-native-mmkv"
|
||||
react-native-netinfo:
|
||||
:path: "../node_modules/@react-native-community/netinfo"
|
||||
react-native-pager-view:
|
||||
@ -714,6 +725,8 @@ SPEC CHECKSUMS:
|
||||
hermes-engine: 2af7b7a59128f250adfd86f15aa1d5a2ecd39995
|
||||
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
|
||||
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
|
||||
MMKV: 9c4663aa7ca255d478ff10f2f5cb7d17c1651ccd
|
||||
MMKVCore: 89f5c8a66bba2dcd551779dea4d412eeec8ff5bb
|
||||
RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda
|
||||
RCTRequired: e1866f61af7049eb3d8e08e8b133abd38bc1ca7a
|
||||
RCTTypeSafety: 27c2ac1b00609a432ced1ae701247593f07f901e
|
||||
@ -736,6 +749,7 @@ SPEC CHECKSUMS:
|
||||
react-native-ios-context-menu: b170594b4448c0cd10c79e13432216bac99de1ac
|
||||
react-native-language-detection: f414937fa715108ab50a6269a3de0bcb95e4ceb0
|
||||
react-native-menu: 8e172cfcf0e42e92f028e7781eddf84d430cae24
|
||||
react-native-mmkv: 69b9c003f10afdd01addf7c6ee784ce42ee2eff3
|
||||
react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983
|
||||
react-native-pager-view: 54bed894cecebe28cede54c01038d9d1e122de43
|
||||
react-native-paste-input: 88709b4fd586ea8cc56ba5e2fc4cdfe90597730c
|
||||
|
@ -37,7 +37,6 @@
|
||||
"@react-navigation/native": "^6.1.1",
|
||||
"@react-navigation/native-stack": "^6.9.7",
|
||||
"@react-navigation/stack": "^6.3.10",
|
||||
"@reduxjs/toolkit": "^1.9.1",
|
||||
"@sentry/react-native": "4.12.0",
|
||||
"@sharcoux/slider": "^6.1.1",
|
||||
"@tanstack/react-query": "^4.20.4",
|
||||
@ -79,6 +78,7 @@
|
||||
"react-native-image-picker": "^4.10.3",
|
||||
"react-native-ios-context-menu": "^1.15.1",
|
||||
"react-native-language-detection": "^0.2.2",
|
||||
"react-native-mmkv": "^2.5.1",
|
||||
"react-native-pager-view": "^6.1.2",
|
||||
"react-native-reanimated": "^2.13.0",
|
||||
"react-native-reanimated-zoom": "^0.3.3",
|
||||
@ -89,7 +89,6 @@
|
||||
"react-native-swipe-list-view": "^3.2.9",
|
||||
"react-native-tab-view": "^3.3.4",
|
||||
"react-redux": "^8.0.5",
|
||||
"redux-persist": "^6.0.0",
|
||||
"rn-placeholder": "^3.0.3",
|
||||
"rtl-detect": "^1.0.4",
|
||||
"valid-url": "^1.0.9",
|
||||
|
56
src/@types/mastodon.d.ts
vendored
56
src/@types/mastodon.d.ts
vendored
@ -264,14 +264,6 @@ declare namespace Mastodon {
|
||||
}
|
||||
|
||||
type Filter<T extends 'v1' | 'v2'> = T extends 'v2' ? Filter_V2 : Filter_V1
|
||||
type Filter_V1 = {
|
||||
id: string
|
||||
phrase: string
|
||||
context: ('home' | 'notifications' | 'public' | 'thread' | 'account')[]
|
||||
expires_at?: string
|
||||
irreversible: boolean
|
||||
whole_word: boolean
|
||||
}
|
||||
type Filter_V2 = {
|
||||
id: string
|
||||
title: string
|
||||
@ -281,6 +273,14 @@ declare namespace Mastodon {
|
||||
keywords: FilterKeyword[]
|
||||
statuses: FilterStatus[]
|
||||
}
|
||||
type Filter_V1 = {
|
||||
id: string
|
||||
phrase: string
|
||||
context: ('home' | 'notifications' | 'public' | 'thread' | 'account')[]
|
||||
expires_at?: string
|
||||
irreversible: boolean
|
||||
whole_word: boolean
|
||||
}
|
||||
|
||||
type FilterKeyword = { id: string; keyword: string; whole_word: boolean }
|
||||
|
||||
@ -298,7 +298,45 @@ declare namespace Mastodon {
|
||||
replies_policy: 'none' | 'list' | 'followed'
|
||||
}
|
||||
|
||||
type Instance = {
|
||||
type Instance<T extends 'v1' | 'v2'> = T extends 'v2' ? Instance_V2 : Instance_V1
|
||||
type Instance_V2 = {
|
||||
domain: string
|
||||
title: string
|
||||
version: string
|
||||
source_url: string
|
||||
description: string
|
||||
usage: { users: { active_month: number } }
|
||||
thumbnail: { url: string; blurhash?: string; versions?: { '@1x'?: string; '@2x'?: string } }
|
||||
languages: string[]
|
||||
configuration: {
|
||||
urls: { streaming_api: string }
|
||||
accounts: { max_featured_tags: number }
|
||||
statuses: {
|
||||
max_characters: number
|
||||
max_media_attachments: number
|
||||
characters_reserved_per_url: number
|
||||
}
|
||||
media_attachments: {
|
||||
supported_mime_types: string[]
|
||||
image_size_limit: number
|
||||
image_matrix_limit: number
|
||||
video_size_limit: number
|
||||
video_frame_rate_limit: number
|
||||
video_matrix_limit: number
|
||||
}
|
||||
polls: {
|
||||
max_options: number
|
||||
max_characters_per_option: number
|
||||
min_expiration: number
|
||||
max_expiration: number
|
||||
}
|
||||
translation: { enabled: boolean }
|
||||
registrations: { enabled: boolean; approval_required: boolean; message?: string }
|
||||
contact: { email: string; account: Account }
|
||||
rules: Rule[]
|
||||
}
|
||||
}
|
||||
type Instance_V1 = {
|
||||
// Base
|
||||
uri: string
|
||||
title: string
|
||||
|
141
src/App.tsx
141
src/App.tsx
@ -1,30 +1,37 @@
|
||||
import { ActionSheetProvider } from '@expo/react-native-action-sheet'
|
||||
import getLanguage from '@helpers/getLanguage'
|
||||
import queryClient from '@helpers/queryClient'
|
||||
import i18n from '@root/i18n/i18n'
|
||||
import Screens from '@root/Screens'
|
||||
import audio from '@root/startup/audio'
|
||||
import log from '@root/startup/log'
|
||||
import netInfo from '@root/startup/netInfo'
|
||||
import push from '@root/startup/push'
|
||||
import sentry from '@root/startup/sentry'
|
||||
import timezone from '@root/startup/timezone'
|
||||
import { persistor, store } from '@root/store'
|
||||
import * as Sentry from '@sentry/react-native'
|
||||
import { QueryClientProvider } from '@tanstack/react-query'
|
||||
import AccessibilityManager from '@utils/accessibility/AccessibilityManager'
|
||||
import { changeLanguage } from '@utils/slices/settingsSlice'
|
||||
import getLanguage from '@utils/helpers/getLanguage'
|
||||
import queryClient from '@utils/queryHooks'
|
||||
import audio from '@utils/startup/audio'
|
||||
import log from '@utils/startup/log'
|
||||
import netInfo from '@utils/startup/netInfo'
|
||||
import push from '@utils/startup/push'
|
||||
import sentry from '@utils/startup/sentry'
|
||||
import timezone from '@utils/startup/timezone'
|
||||
import { storage } from '@utils/storage'
|
||||
import {
|
||||
getGlobalStorage,
|
||||
removeAccount,
|
||||
setAccount,
|
||||
setGlobalStorage
|
||||
} from '@utils/storage/actions'
|
||||
import {
|
||||
hasMigratedFromAsyncStorage,
|
||||
migrateFromAsyncStorage
|
||||
} from '@utils/storage/migrations/toMMKV'
|
||||
import ThemeManager from '@utils/styles/ThemeManager'
|
||||
import * as Localization from 'expo-localization'
|
||||
import * as SplashScreen from 'expo-splash-screen'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { IntlProvider } from 'react-intl'
|
||||
import { LogBox, Platform } from 'react-native'
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
||||
import { MMKV } from 'react-native-mmkv'
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context'
|
||||
import { enableFreeze } from 'react-native-screens'
|
||||
import { QueryClientProvider } from '@tanstack/react-query'
|
||||
import { Provider } from 'react-redux'
|
||||
import { PersistGate } from 'redux-persist/integration/react'
|
||||
import i18n from './i18n'
|
||||
import Screens from './screens'
|
||||
|
||||
Platform.select({
|
||||
android: LogBox.ignoreLogs(['Setting a timer for a long period of time'])
|
||||
@ -36,23 +43,50 @@ push()
|
||||
timezone()
|
||||
enableFreeze(true)
|
||||
|
||||
log('log', 'App', 'delay splash')
|
||||
SplashScreen.preventAutoHideAsync()
|
||||
|
||||
const App: React.FC = () => {
|
||||
log('log', 'App', 'rendering App')
|
||||
const [appIsReady, setAppIsReady] = useState(false)
|
||||
const [localCorrupt, setLocalCorrupt] = useState<string>()
|
||||
|
||||
useEffect(() => {
|
||||
const delaySplash = async () => {
|
||||
log('log', 'App', 'delay splash')
|
||||
try {
|
||||
await SplashScreen.preventAutoHideAsync()
|
||||
} catch (e) {
|
||||
console.warn(e)
|
||||
}
|
||||
}
|
||||
delaySplash()
|
||||
}, [])
|
||||
const [hasMigrated, setHasMigrated] = useState(hasMigratedFromAsyncStorage)
|
||||
|
||||
useEffect(() => {
|
||||
const prepare = async () => {
|
||||
if (!hasMigrated && !hasMigratedFromAsyncStorage) {
|
||||
try {
|
||||
await migrateFromAsyncStorage()
|
||||
setHasMigrated(true)
|
||||
} catch (e) {
|
||||
// TODO: fall back to AsyncStorage? Wipe storage clean and use MMKV? Crash app?
|
||||
}
|
||||
} else {
|
||||
log('log', 'App', 'loading from MMKV')
|
||||
const account = getGlobalStorage.string('account.active')
|
||||
if (account) {
|
||||
const storageAccount = new MMKV({ id: account })
|
||||
const token = storageAccount.getString('auth.token')
|
||||
if (token) {
|
||||
log('log', 'App', `Binding storage of ${account}`)
|
||||
storage.account = storageAccount
|
||||
} else {
|
||||
log('log', 'App', `Token not found for ${account}`)
|
||||
removeAccount(account)
|
||||
}
|
||||
} else {
|
||||
log('log', 'App', 'No active account available')
|
||||
const accounts = getGlobalStorage.object('accounts')
|
||||
if (accounts?.length) {
|
||||
log('log', 'App', `Setting active account ${accounts[accounts.length - 1]}`)
|
||||
setAccount(accounts[accounts.length - 1])
|
||||
} else {
|
||||
setGlobalStorage('account.active', undefined)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onBeforeLift = useCallback(async () => {
|
||||
let netInfoRes = undefined
|
||||
try {
|
||||
netInfoRes = await netInfo()
|
||||
@ -62,40 +96,35 @@ const App: React.FC = () => {
|
||||
setLocalCorrupt(netInfoRes.corrupted)
|
||||
}
|
||||
|
||||
log('log', 'App', 'hide splash')
|
||||
try {
|
||||
await SplashScreen.hideAsync()
|
||||
return Promise.resolve()
|
||||
} catch (e) {
|
||||
console.warn(e)
|
||||
return Promise.reject()
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<PersistGate
|
||||
persistor={persistor}
|
||||
onBeforeLift={onBeforeLift}
|
||||
children={bootstrapped => {
|
||||
log('log', 'App', 'bootstrapped')
|
||||
if (bootstrapped) {
|
||||
log('log', 'App', 'loading actual app :)')
|
||||
log('log', 'App', `Locale: ${Localization.locale}`)
|
||||
log('log', 'App', `locale: ${Localization.locale}`)
|
||||
const language = getLanguage()
|
||||
if (!language) {
|
||||
if (Platform.OS !== 'ios') {
|
||||
store.dispatch(changeLanguage('en'))
|
||||
setGlobalStorage('app.language', 'en')
|
||||
}
|
||||
i18n.changeLanguage('en')
|
||||
} else {
|
||||
i18n.changeLanguage(language)
|
||||
}
|
||||
|
||||
setAppIsReady(true)
|
||||
}
|
||||
|
||||
prepare()
|
||||
}, [])
|
||||
const onLayoutRootView = useCallback(async () => {
|
||||
if (appIsReady) {
|
||||
log('log', 'App', 'hide splash')
|
||||
await SplashScreen.hideAsync()
|
||||
}
|
||||
}, [appIsReady])
|
||||
if (!appIsReady) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<IntlProvider locale={language}>
|
||||
<GestureHandlerRootView style={{ flex: 1 }} onLayout={onLayoutRootView}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SafeAreaProvider>
|
||||
<ActionSheetProvider>
|
||||
<AccessibilityManager>
|
||||
@ -105,14 +134,6 @@ const App: React.FC = () => {
|
||||
</AccessibilityManager>
|
||||
</ActionSheetProvider>
|
||||
</SafeAreaProvider>
|
||||
</IntlProvider>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</GestureHandlerRootView>
|
||||
)
|
||||
|
@ -1,19 +1,24 @@
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import initQuery from '@utils/initQuery'
|
||||
import { InstanceLatest } from '@utils/migrations/instances/migration'
|
||||
import { generateAccountKey, getAccountDetails, setAccount } from '@utils/storage/actions'
|
||||
import { StorageGlobal } from '@utils/storage/global'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React from 'react'
|
||||
import Button from './Button'
|
||||
import haptics from './haptics'
|
||||
|
||||
interface Props {
|
||||
instance: InstanceLatest
|
||||
account: NonNullable<StorageGlobal['accounts']>[number]
|
||||
selected?: boolean
|
||||
additionalActions?: () => void
|
||||
}
|
||||
|
||||
const AccountButton: React.FC<Props> = ({ instance, selected = false, additionalActions }) => {
|
||||
const AccountButton: React.FC<Props> = ({ account, selected = false, additionalActions }) => {
|
||||
const navigation = useNavigation()
|
||||
const accountDetails = getAccountDetails(
|
||||
['auth.account.acct', 'auth.domain', 'auth.account.id'],
|
||||
account
|
||||
)
|
||||
if (!accountDetails) return null
|
||||
|
||||
return (
|
||||
<Button
|
||||
@ -23,10 +28,17 @@ const AccountButton: React.FC<Props> = ({ instance, selected = false, additional
|
||||
marginBottom: StyleConstants.Spacing.M,
|
||||
marginRight: StyleConstants.Spacing.M
|
||||
}}
|
||||
content={`@${instance.account.acct}@${instance.uri}${selected ? ' ✓' : ''}`}
|
||||
content={`@${accountDetails['auth.account.acct']}@${accountDetails['auth.domain']}${
|
||||
selected ? ' ✓' : ''
|
||||
}`}
|
||||
onPress={() => {
|
||||
haptics('Light')
|
||||
initQuery({ instance })
|
||||
setAccount(
|
||||
generateAccountKey({
|
||||
domain: accountDetails['auth.domain'],
|
||||
id: accountDetails['auth.account.id']
|
||||
})
|
||||
)
|
||||
navigation.goBack()
|
||||
if (additionalActions) {
|
||||
additionalActions()
|
||||
|
@ -4,7 +4,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext } from 'react'
|
||||
import { Keyboard, Pressable, View } from 'react-native'
|
||||
import EmojisContext from './helpers/EmojisContext'
|
||||
import EmojisContext from './Context'
|
||||
|
||||
const EmojisButton: React.FC = () => {
|
||||
const { colors } = useTheme()
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { emojis } from '@components/Emojis'
|
||||
import Icon from '@components/Icon'
|
||||
import CustomText from '@components/Text'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { countInstanceEmoji } from '@utils/slices/instancesSlice'
|
||||
import { StorageAccount } from '@utils/storage/account'
|
||||
import { getAccountStorage, setAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
@ -20,10 +20,9 @@ import {
|
||||
} from 'react-native'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import validUrl from 'valid-url'
|
||||
import EmojisContext from './helpers/EmojisContext'
|
||||
import EmojisContext from './Context'
|
||||
|
||||
const EmojisList = () => {
|
||||
const dispatch = useAppDispatch()
|
||||
const { reduceMotionEnabled } = useAccessibility()
|
||||
const { t } = useTranslation(['common', 'screenCompose'])
|
||||
|
||||
@ -75,7 +74,59 @@ const EmojisList = () => {
|
||||
key={emoji.shortcode}
|
||||
onPress={() => {
|
||||
addEmoji(`:${emoji.shortcode}:`)
|
||||
dispatch(countInstanceEmoji(emoji))
|
||||
|
||||
const HALF_LIFE = 60 * 60 * 24 * 7 // 1 week
|
||||
const calculateScore = (
|
||||
emoji: StorageAccount['emojis_frequent'][number]
|
||||
): number => {
|
||||
var seconds = (new Date().getTime() - emoji.lastUsed) / 1000
|
||||
var score = emoji.count + 1
|
||||
var order = Math.log(Math.max(score, 1)) / Math.LN10
|
||||
var sign = score > 0 ? 1 : score === 0 ? 0 : -1
|
||||
return (sign * order + seconds / HALF_LIFE) * 10
|
||||
}
|
||||
|
||||
const currentEmojis = getAccountStorage.object('emojis_frequent')
|
||||
const foundEmojiIndex = currentEmojis?.findIndex(
|
||||
e => e.emoji.shortcode === emoji.shortcode && e.emoji.url === emoji.url
|
||||
)
|
||||
|
||||
let newEmojisSort: StorageAccount['emojis_frequent']
|
||||
if (foundEmojiIndex === -1) {
|
||||
newEmojisSort = currentEmojis || []
|
||||
const temp = {
|
||||
emoji,
|
||||
score: 0,
|
||||
count: 0,
|
||||
lastUsed: new Date().getTime()
|
||||
}
|
||||
newEmojisSort.push({
|
||||
...temp,
|
||||
score: calculateScore(temp),
|
||||
count: temp.count + 1
|
||||
})
|
||||
} else {
|
||||
newEmojisSort =
|
||||
currentEmojis
|
||||
?.map((e, i) =>
|
||||
i === foundEmojiIndex
|
||||
? {
|
||||
...e,
|
||||
score: calculateScore(e),
|
||||
count: e.count + 1,
|
||||
lastUsed: new Date().getTime()
|
||||
}
|
||||
: e
|
||||
)
|
||||
.sort((a, b) => b.score - a.score) || []
|
||||
}
|
||||
|
||||
setAccountStorage([
|
||||
{
|
||||
key: 'emojis_frequent',
|
||||
value: newEmojisSort.sort((a, b) => b.score - a.score).slice(0, 20)
|
||||
}
|
||||
])
|
||||
}}
|
||||
style={{ padding: StyleConstants.Spacing.S }}
|
||||
>
|
||||
|
@ -2,14 +2,13 @@ import EmojisButton from '@components/Emojis/Button'
|
||||
import EmojisList from '@components/Emojis/List'
|
||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { useEmojisQuery } from '@utils/queryHooks/emojis'
|
||||
import { getInstanceFrequentEmojis } from '@utils/slices/instancesSlice'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { chunk, forEach, groupBy, sortBy } from 'lodash'
|
||||
import React, { createRef, PropsWithChildren, useEffect, useReducer, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Keyboard, KeyboardAvoidingView, View } from 'react-native'
|
||||
import { Edge, SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { useSelector } from 'react-redux'
|
||||
import EmojisContext, { Emojis, emojisReducer, EmojisState } from './Emojis/helpers/EmojisContext'
|
||||
import EmojisContext, { Emojis, emojisReducer, EmojisState } from './Context'
|
||||
|
||||
export type Props = {
|
||||
inputProps: EmojisState['inputProps']
|
||||
@ -36,7 +35,7 @@ const ComponentEmojis: React.FC<Props & PropsWithChildren> = ({
|
||||
|
||||
const { t } = useTranslation(['componentEmojis'])
|
||||
const { data } = useEmojisQuery({})
|
||||
const frequentEmojis = useSelector(getInstanceFrequentEmojis, () => true)
|
||||
const [frequentEmojis] = useAccountStorage.object('emojis_frequent')
|
||||
useEffect(() => {
|
||||
if (data && data.length) {
|
||||
let sortedEmojis: NonNullable<Emojis['current']> = []
|
@ -10,8 +10,8 @@ import {
|
||||
View,
|
||||
ViewStyle
|
||||
} from 'react-native'
|
||||
import FastImage, { ImageStyle } from 'react-native-fast-image'
|
||||
import { Blurhash } from 'react-native-blurhash'
|
||||
import FastImage, { ImageStyle } from 'react-native-fast-image'
|
||||
|
||||
// blurhas -> if blurhash, show before any loading succeed
|
||||
// original -> load original
|
||||
|
@ -1,5 +1,5 @@
|
||||
import HeaderLeft from '@components/Header/Left'
|
||||
import HeaderCenter from '@components/Header/Center'
|
||||
import HeaderLeft from '@components/Header/Left'
|
||||
import HeaderRight from '@components/Header/Right'
|
||||
|
||||
export { HeaderLeft, HeaderCenter, HeaderRight }
|
@ -3,7 +3,7 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { forwardRef, RefObject } from 'react'
|
||||
import { Platform, TextInput, TextInputProps, View } from 'react-native'
|
||||
import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated'
|
||||
import { EmojisState } from './Emojis/helpers/EmojisContext'
|
||||
import { EmojisState } from './Emojis/Context'
|
||||
import CustomText from './Text'
|
||||
|
||||
export type Props = {
|
||||
|
@ -1,55 +0,0 @@
|
||||
import CustomText from '@components/Text'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { View, ViewStyle } from 'react-native'
|
||||
import { PlaceholderLine } from 'rn-placeholder'
|
||||
|
||||
export interface Props {
|
||||
style?: ViewStyle
|
||||
header: string
|
||||
content?: string
|
||||
potentialWidth?: number
|
||||
}
|
||||
|
||||
const InstanceInfo: React.FC<Props> = ({ style, header, content, potentialWidth }) => {
|
||||
const { colors } = useTheme()
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
flex: 1,
|
||||
marginTop: StyleConstants.Spacing.M,
|
||||
paddingLeft: StyleConstants.Spacing.Global.PagePadding,
|
||||
paddingRight: StyleConstants.Spacing.Global.PagePadding
|
||||
},
|
||||
style
|
||||
]}
|
||||
accessible
|
||||
>
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
style={{
|
||||
marginBottom: StyleConstants.Spacing.XS,
|
||||
color: colors.primaryDefault
|
||||
}}
|
||||
fontWeight='Bold'
|
||||
children={header}
|
||||
/>
|
||||
{content ? (
|
||||
<CustomText fontStyle='M' style={{ color: colors.primaryDefault }} children={content} />
|
||||
) : (
|
||||
<PlaceholderLine
|
||||
width={potentialWidth ? potentialWidth * StyleConstants.Font.Size.M : undefined}
|
||||
height={StyleConstants.Font.LineHeight.M}
|
||||
color={colors.shimmerDefault}
|
||||
noMargin
|
||||
style={{ borderRadius: 0 }}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default InstanceInfo
|
@ -1,28 +1,35 @@
|
||||
import Button from '@components/Button'
|
||||
import Icon from '@components/Icon'
|
||||
import browserPackage from '@helpers/browserPackage'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import apiGeneral from '@utils/api/general'
|
||||
import browserPackage from '@utils/helpers/browserPackage'
|
||||
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||
import queryClient from '@utils/queryHooks'
|
||||
import { TabMeStackNavigationProp } from '@utils/navigation/navigators'
|
||||
import { redirectUri, useAppsMutation } from '@utils/queryHooks/apps'
|
||||
import { useInstanceQuery } from '@utils/queryHooks/instance'
|
||||
import { checkInstanceFeature, getInstances } from '@utils/slices/instancesSlice'
|
||||
import { StorageAccount } from '@utils/storage/account'
|
||||
import {
|
||||
generateAccountKey,
|
||||
getGlobalStorage,
|
||||
setAccountStorage,
|
||||
setGlobalStorage
|
||||
} from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as AuthSession from 'expo-auth-session'
|
||||
import * as Random from 'expo-random'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import { debounce } from 'lodash'
|
||||
import React, { RefObject, useCallback, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { Alert, Image, KeyboardAvoidingView, Platform, TextInput, View } from 'react-native'
|
||||
import base64 from 'react-native-base64'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Placeholder } from 'rn-placeholder'
|
||||
import validUrl from 'valid-url'
|
||||
import InstanceInfo from './Info'
|
||||
import CustomText from '../Text'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { TabMeStackNavigationProp } from '@utils/navigation/navigators'
|
||||
import queryClient from '@helpers/queryClient'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import addInstance from '@utils/slices/instances/add'
|
||||
import { storage } from '@utils/storage'
|
||||
import { MMKV } from 'react-native-mmkv'
|
||||
|
||||
export interface Props {
|
||||
scrollViewRef?: RefObject<ScrollView>
|
||||
@ -47,8 +54,6 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
!!validUrl.isHttpsUri(`https://${domain}`) &&
|
||||
errorCode === 401
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
const instances = useSelector(getInstances, () => true)
|
||||
const instanceQuery = useInstanceQuery({
|
||||
domain,
|
||||
options: {
|
||||
@ -62,7 +67,7 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
}
|
||||
})
|
||||
|
||||
const deprecateAuthFollow = useSelector(checkInstanceFeature('deprecate_auth_follow'))
|
||||
const deprecateAuthFollow = featureCheck('deprecate_auth_follow')
|
||||
|
||||
const appsMutation = useAppsMutation({
|
||||
retry: false,
|
||||
@ -97,14 +102,86 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
{ tokenEndpoint: `https://${variables.domain}/oauth/token` }
|
||||
)
|
||||
queryClient.clear()
|
||||
dispatch(
|
||||
addInstance({
|
||||
|
||||
const {
|
||||
body: { id, acct, avatar_static }
|
||||
} = await apiGeneral<Mastodon.Account>({
|
||||
method: 'get',
|
||||
domain,
|
||||
token: accessToken,
|
||||
instance: instanceQuery.data!,
|
||||
appData: { clientId, clientSecret }
|
||||
url: `api/v1/accounts/verify_credentials`,
|
||||
headers: { Authorization: `Bearer ${accessToken}` }
|
||||
})
|
||||
|
||||
const accounts = getGlobalStorage.object('accounts')
|
||||
const accountKey = generateAccountKey({ domain, id })
|
||||
const account = accounts?.find(account => account === accountKey)
|
||||
|
||||
const accountDetails: StorageAccount = {
|
||||
'auth.clientId': clientId,
|
||||
'auth.clientSecret': clientSecret,
|
||||
'auth.token': accessToken,
|
||||
'auth.domain': domain,
|
||||
'auth.account.id': id,
|
||||
'auth.account.acct': acct,
|
||||
'auth.account.avatar_static': avatar_static,
|
||||
version: instanceQuery.data?.version || '0',
|
||||
preferences: undefined,
|
||||
notifications: {
|
||||
follow: true,
|
||||
follow_request: true,
|
||||
favourite: true,
|
||||
reblog: true,
|
||||
mention: true,
|
||||
poll: true,
|
||||
status: true,
|
||||
update: true,
|
||||
'admin.sign_up': true,
|
||||
'admin.report': true
|
||||
},
|
||||
push: {
|
||||
global: false,
|
||||
decode: false,
|
||||
alerts: {
|
||||
follow: true,
|
||||
follow_request: true,
|
||||
favourite: true,
|
||||
reblog: true,
|
||||
mention: true,
|
||||
poll: true,
|
||||
status: true,
|
||||
update: true,
|
||||
'admin.sign_up': false,
|
||||
'admin.report': false
|
||||
},
|
||||
key: base64.encodeFromByteArray(Random.getRandomBytes(16))
|
||||
},
|
||||
page_local: {
|
||||
showBoosts: true,
|
||||
showReplies: true
|
||||
},
|
||||
page_me: {
|
||||
followedTags: { shown: false },
|
||||
lists: { shown: false },
|
||||
announcements: { shown: false, unread: 0 }
|
||||
},
|
||||
drafts: [],
|
||||
emojis_frequent: []
|
||||
}
|
||||
|
||||
setAccountStorage(
|
||||
Object.keys(accountDetails).map((key: keyof StorageAccount) => ({
|
||||
key,
|
||||
value: accountDetails[key]
|
||||
})),
|
||||
accountKey
|
||||
)
|
||||
storage.account = new MMKV({ id: accountKey })
|
||||
|
||||
if (!account) {
|
||||
setGlobalStorage('accounts', accounts?.concat([accountKey]))
|
||||
}
|
||||
setGlobalStorage('account.active', accountKey)
|
||||
|
||||
goBack && navigation.goBack()
|
||||
}
|
||||
}
|
||||
@ -112,7 +189,8 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
|
||||
const processUpdate = useCallback(() => {
|
||||
if (domain) {
|
||||
if (instances && instances.filter(instance => instance.url === domain).length) {
|
||||
const accounts = getGlobalStorage.object('accounts')
|
||||
if (accounts && accounts.filter(account => account.startsWith(`${domain}/`)).length) {
|
||||
Alert.alert(
|
||||
t('componentInstance:update.alert.title'),
|
||||
t('componentInstance:update.alert.message'),
|
||||
@ -208,7 +286,8 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
text === domain &&
|
||||
instanceQuery.isSuccess &&
|
||||
instanceQuery.data &&
|
||||
instanceQuery.data.uri
|
||||
// @ts-ignore
|
||||
(instanceQuery.data.domain || instanceQuery.data.uri)
|
||||
) {
|
||||
processUpdate()
|
||||
}
|
||||
@ -228,7 +307,8 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
type='text'
|
||||
content={t('componentInstance:server.button')}
|
||||
onPress={processUpdate}
|
||||
disabled={!instanceQuery.data?.uri && !whitelisted}
|
||||
// @ts-ignore
|
||||
disabled={!(instanceQuery.data?.domain || instanceQuery.data?.uri) && !whitelisted}
|
||||
loading={instanceQuery.isFetching || appsMutation.isLoading}
|
||||
/>
|
||||
</View>
|
||||
@ -245,35 +325,7 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
>
|
||||
{t('componentInstance:server.whitelisted')}
|
||||
</CustomText>
|
||||
) : (
|
||||
<Placeholder>
|
||||
<InstanceInfo
|
||||
header={t('componentInstance:server.information.name')}
|
||||
content={instanceQuery.data?.title || undefined}
|
||||
potentialWidth={2}
|
||||
/>
|
||||
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||
<InstanceInfo
|
||||
style={{ alignItems: 'flex-start' }}
|
||||
header={t('componentInstance:server.information.accounts')}
|
||||
content={instanceQuery.data?.stats?.user_count?.toString() || undefined}
|
||||
potentialWidth={4}
|
||||
/>
|
||||
<InstanceInfo
|
||||
style={{ alignItems: 'center' }}
|
||||
header={t('componentInstance:server.information.statuses')}
|
||||
content={instanceQuery.data?.stats?.status_count?.toString() || undefined}
|
||||
potentialWidth={4}
|
||||
/>
|
||||
<InstanceInfo
|
||||
style={{ alignItems: 'flex-end' }}
|
||||
header={t('componentInstance:server.information.domains')}
|
||||
content={instanceQuery.data?.stats?.domain_count?.toString() || undefined}
|
||||
potentialWidth={4}
|
||||
/>
|
||||
</View>
|
||||
</Placeholder>
|
||||
)}
|
||||
) : null}
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React from 'react'
|
||||
import { View } from 'react-native'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
|
||||
export interface Props {
|
||||
children: React.ReactNode
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from 'react'
|
||||
import { View } from 'react-native'
|
||||
import CustomText from '@components/Text'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import CustomText from '@components/Text'
|
||||
import React from 'react'
|
||||
import { View } from 'react-native'
|
||||
|
||||
export interface Props {
|
||||
heading: string
|
||||
|
@ -1,4 +0,0 @@
|
||||
import ParseEmojis from './Parse/Emojis'
|
||||
import ParseHTML from './Parse/HTML'
|
||||
|
||||
export { ParseEmojis, ParseHTML }
|
@ -1,13 +1,12 @@
|
||||
import CustomText from '@components/Text'
|
||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { getSettingsFontsize } from '@utils/slices/settingsSlice'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { adaptiveScale } from '@utils/styles/scaling'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { Platform, TextStyle } from 'react-native'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import { useSelector } from 'react-redux'
|
||||
import validUrl from 'valid-url'
|
||||
|
||||
const regexEmoji = new RegExp(/(:[A-Za-z0-9_]+:)/)
|
||||
@ -27,7 +26,7 @@ const ParseEmojis = React.memo(
|
||||
|
||||
const { reduceMotionEnabled } = useAccessibility()
|
||||
|
||||
const adaptiveFontsize = useSelector(getSettingsFontsize)
|
||||
const [adaptiveFontsize] = useGlobalStorage.number('app.font_size')
|
||||
const adaptedFontsize = adaptiveScale(
|
||||
StyleConstants.Font.Size[size],
|
||||
adaptiveSize ? adaptiveFontsize : 0
|
||||
|
@ -5,7 +5,7 @@ import { useNavigation, useRoute } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import { useFollowedTagsQuery } from '@utils/queryHooks/tags'
|
||||
import { getSettingsFontsize } from '@utils/slices/settingsSlice'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { adaptiveScale } from '@utils/styles/scaling'
|
||||
@ -16,7 +16,6 @@ import { isEqual } from 'lodash'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Pressable, Text, TextStyleIOS, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
export interface Props {
|
||||
content: string
|
||||
@ -52,7 +51,7 @@ const ParseHTML = React.memo(
|
||||
selectable = false,
|
||||
setSpoilerExpanded
|
||||
}: Props) => {
|
||||
const adaptiveFontsize = useSelector(getSettingsFontsize)
|
||||
const [adaptiveFontsize] = useGlobalStorage.number('app.font_size')
|
||||
const adaptedFontsize = adaptiveScale(
|
||||
StyleConstants.Font.Size[size],
|
||||
adaptiveSize ? adaptiveFontsize : 0
|
||||
|
4
src/components/Parse/index.tsx
Normal file
4
src/components/Parse/index.tsx
Normal file
@ -0,0 +1,4 @@
|
||||
import ParseEmojis from './Emojis'
|
||||
import ParseHTML from './HTML'
|
||||
|
||||
export { ParseEmojis, ParseHTML }
|
@ -1,6 +1,7 @@
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { QueryKeyRelationship, useRelationshipMutation } from '@utils/queryHooks/relationship'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
@ -8,7 +9,6 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
export interface Props {
|
||||
id: Mastodon.Account['id']
|
||||
|
@ -1,20 +1,19 @@
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { useRoute } from '@react-navigation/native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||
import {
|
||||
QueryKeyRelationship,
|
||||
useRelationshipMutation,
|
||||
useRelationshipQuery
|
||||
} from '@utils/queryHooks/relationship'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { checkInstanceFeature } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { View } from 'react-native'
|
||||
import { useRoute } from '@react-navigation/native'
|
||||
|
||||
export interface Props {
|
||||
id: Mastodon.Account['id']
|
||||
@ -24,7 +23,7 @@ const RelationshipOutgoing: React.FC<Props> = ({ id }: Props) => {
|
||||
const { theme } = useTheme()
|
||||
const { t } = useTranslation(['common', 'componentRelationship'])
|
||||
|
||||
const canFollowNotify = useSelector(checkInstanceFeature('account_follow_notify'))
|
||||
const canFollowNotify = featureCheck('account_follow_notify')
|
||||
|
||||
const query = useRelationshipQuery({ id })
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
import apiInstance from '@api/instance'
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback } from 'react'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import TimelineActions from './Shared/Actions'
|
||||
import TimelineContent from './Shared/Content'
|
||||
import StatusContext from './Shared/Context'
|
||||
|
@ -9,17 +9,18 @@ import TimelineCard from '@components/Timeline/Shared/Card'
|
||||
import TimelineContent from '@components/Timeline/Shared/Content'
|
||||
import TimelineHeaderDefault from '@components/Timeline/Shared/HeaderDefault'
|
||||
import TimelinePoll from '@components/Timeline/Shared/Poll'
|
||||
import removeHTML from '@helpers/removeHTML'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||
import removeHTML from '@utils/helpers/removeHTML'
|
||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { checkInstanceFeature, getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { Fragment, useRef, useState } from 'react'
|
||||
import { Pressable, StyleProp, View, ViewStyle } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import * as ContextMenu from 'zeego/context-menu'
|
||||
import StatusContext from './Shared/Context'
|
||||
import TimelineFeedback from './Shared/Feedback'
|
||||
@ -60,14 +61,15 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
const { colors } = useTheme()
|
||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||
|
||||
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
||||
const [accountId] = useAccountStorage.string('auth.account.id')
|
||||
const { data: preferences } = usePreferencesQuery()
|
||||
|
||||
const ownAccount = status.account?.id === instanceAccount?.id
|
||||
const ownAccount = status.account?.id === accountId
|
||||
const [spoilerExpanded, setSpoilerExpanded] = useState(
|
||||
instanceAccount?.preferences?.['reading:expand:spoilers'] || false
|
||||
preferences?.['reading:expand:spoilers'] || false
|
||||
)
|
||||
const spoilerHidden = status.spoiler_text?.length
|
||||
? !instanceAccount?.preferences?.['reading:expand:spoilers'] && !spoilerExpanded
|
||||
? !preferences?.['reading:expand:spoilers'] && !spoilerExpanded
|
||||
: false
|
||||
const detectedLanguage = useRef<string>(status.language || '')
|
||||
|
||||
@ -134,7 +136,7 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
if (!ownAccount) {
|
||||
let filterResults: FilteredProps['filterResults'] = []
|
||||
const [filterRevealed, setFilterRevealed] = useState(false)
|
||||
const hasFilterServerSide = useSelector(checkInstanceFeature('filter_server_side'))
|
||||
const hasFilterServerSide = featureCheck('filter_server_side')
|
||||
if (hasFilterServerSide) {
|
||||
if (status.filtered?.length) {
|
||||
filterResults = status.filtered?.map(filter => filter.filter)
|
||||
|
@ -11,14 +11,15 @@ import TimelineHeaderNotification from '@components/Timeline/Shared/HeaderNotifi
|
||||
import TimelinePoll from '@components/Timeline/Shared/Poll'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||
import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { checkInstanceFeature, getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { Fragment, useCallback, useRef, useState } from 'react'
|
||||
import React, { Fragment, useCallback, useState } from 'react'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import * as ContextMenu from 'zeego/context-menu'
|
||||
import StatusContext from './Shared/Context'
|
||||
import TimelineFiltered, { FilteredProps, shouldFilter } from './Shared/Filtered'
|
||||
@ -31,7 +32,8 @@ export interface Props {
|
||||
}
|
||||
|
||||
const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
|
||||
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
||||
const [accountId] = useAccountStorage.string('auth.account.id')
|
||||
const { data: preferences } = usePreferencesQuery()
|
||||
|
||||
const status = notification.status?.reblog ? notification.status.reblog : notification.status
|
||||
const account =
|
||||
@ -40,12 +42,12 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
|
||||
: notification.status
|
||||
? notification.status.account
|
||||
: notification.account
|
||||
const ownAccount = notification.account?.id === instanceAccount?.id
|
||||
const ownAccount = notification.account?.id === accountId
|
||||
const [spoilerExpanded, setSpoilerExpanded] = useState(
|
||||
instanceAccount.preferences?.['reading:expand:spoilers'] || false
|
||||
preferences?.['reading:expand:spoilers'] || false
|
||||
)
|
||||
const spoilerHidden = notification.status?.spoiler_text?.length
|
||||
? !instanceAccount.preferences?.['reading:expand:spoilers'] && !spoilerExpanded
|
||||
? !preferences?.['reading:expand:spoilers'] && !spoilerExpanded
|
||||
: false
|
||||
|
||||
const { colors } = useTheme()
|
||||
@ -117,7 +119,7 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
|
||||
if (!ownAccount) {
|
||||
let filterResults: FilteredProps['filterResults'] = []
|
||||
const [filterRevealed, setFilterRevealed] = useState(false)
|
||||
const hasFilterServerSide = useSelector(checkInstanceFeature('filter_server_side'))
|
||||
const hasFilterServerSide = featureCheck('filter_server_side')
|
||||
if (notification.status) {
|
||||
if (hasFilterServerSide) {
|
||||
if (notification.status.filtered?.length) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
import { InfiniteData, useQueryClient } from '@tanstack/react-query'
|
||||
import { QueryKeyTimeline, TimelineData, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
@ -16,7 +17,6 @@ import Animated, {
|
||||
useSharedValue,
|
||||
withTiming
|
||||
} from 'react-native-reanimated'
|
||||
import { InfiniteData, useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
export interface Props {
|
||||
flRef: RefObject<FlatList<any>>
|
||||
|
@ -2,24 +2,23 @@ import Icon from '@components/Icon'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import CustomText from '@components/Text'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { androidActionSheetStyles } from '@helpers/androidActionSheetStyles'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||
import {
|
||||
MutationVarsTimelineUpdateStatusProperty,
|
||||
QueryKeyTimeline,
|
||||
useTimelineMutation
|
||||
} from '@utils/queryHooks/timeline'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { uniqBy } from 'lodash'
|
||||
import React, { useCallback, useContext, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Pressable, StyleSheet, View } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
import StatusContext from './Context'
|
||||
|
||||
const TimelineActions: React.FC = () => {
|
||||
@ -76,12 +75,12 @@ const TimelineActions: React.FC = () => {
|
||||
}
|
||||
})
|
||||
|
||||
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
||||
const [accountId] = useAccountStorage.string('auth.account.id')
|
||||
const onPressReply = useCallback(() => {
|
||||
const accts = uniqBy(
|
||||
([status.account] as Mastodon.Account[] & Mastodon.Mention[])
|
||||
.concat(status.mentions)
|
||||
.filter(d => d?.id !== instanceAccount?.id),
|
||||
.filter(d => d?.id !== accountId),
|
||||
d => d?.id
|
||||
).map(d => d?.acct)
|
||||
navigation.navigate('Screen-Compose', {
|
||||
|
@ -7,13 +7,12 @@ import AttachmentVideo from '@components/Timeline/Shared/Attachment/Video'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import React, { useContext, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import StatusContext from './Context'
|
||||
|
||||
const TimelineAttachment = () => {
|
||||
@ -28,13 +27,10 @@ const TimelineAttachment = () => {
|
||||
|
||||
const { t } = useTranslation('componentTimeline')
|
||||
|
||||
const account = useSelector(
|
||||
getInstanceAccount,
|
||||
(prev, next) =>
|
||||
prev.preferences?.['reading:expand:media'] === next.preferences?.['reading:expand:media']
|
||||
)
|
||||
const { data: preferences } = usePreferencesQuery()
|
||||
|
||||
const defaultSensitive = () => {
|
||||
switch (account.preferences?.['reading:expand:media']) {
|
||||
switch (preferences?.['reading:expand:media']) {
|
||||
case 'show_all':
|
||||
return false
|
||||
case 'hide_all':
|
||||
|
@ -1,15 +1,14 @@
|
||||
import Button from '@components/Button'
|
||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { ResizeMode, Video, VideoFullscreenUpdate } from 'expo-av'
|
||||
import { Platform } from 'expo-modules-core'
|
||||
import React, { useRef, useState } from 'react'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import { Blurhash } from 'react-native-blurhash'
|
||||
import AttachmentAltText from './AltText'
|
||||
import { Platform } from 'expo-modules-core'
|
||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||
import { aspectRatio } from './dimensions'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { getSettingsAutoplayGifv } from '@utils/slices/settingsSlice'
|
||||
|
||||
export interface Props {
|
||||
total: number
|
||||
@ -27,7 +26,7 @@ const AttachmentVideo: React.FC<Props> = ({
|
||||
gifv = false
|
||||
}) => {
|
||||
const { reduceMotionEnabled } = useAccessibility()
|
||||
const autoplayGifv = useSelector(getSettingsAutoplayGifv)
|
||||
const [autoplayGifv] = useGlobalStorage.boolean('app.auto_play_gifv')
|
||||
|
||||
const videoPlayer = useRef<Video>(null)
|
||||
const [videoLoading, setVideoLoading] = useState(false)
|
||||
|
@ -2,8 +2,8 @@ import ComponentAccount from '@components/Account'
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import openLink from '@components/openLink'
|
||||
import CustomText from '@components/Text'
|
||||
import { matchAccount, matchStatus } from '@helpers/urlMatcher'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { matchAccount, matchStatus } from '@utils/helpers/urlMatcher'
|
||||
import { useAccountQuery } from '@utils/queryHooks/account'
|
||||
import { useSearchQuery } from '@utils/queryHooks/search'
|
||||
import { useStatusQuery } from '@utils/queryHooks/status'
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { ParseHTML } from '@components/Parse'
|
||||
import CustomText from '@components/Text'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Platform, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { isRtlLang } from 'rtl-detect'
|
||||
import StatusContext from './Context'
|
||||
|
||||
@ -21,7 +20,8 @@ const TimelineContent: React.FC<Props> = ({ notificationOwnToot = false, setSpoi
|
||||
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('componentTimeline')
|
||||
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
||||
|
||||
const { data: preferences } = usePreferencesQuery()
|
||||
|
||||
return (
|
||||
<View>
|
||||
@ -63,7 +63,7 @@ const TimelineContent: React.FC<Props> = ({ notificationOwnToot = false, setSpoi
|
||||
mentions={status.mentions}
|
||||
tags={status.tags}
|
||||
numberOfLines={
|
||||
instanceAccount.preferences?.['reading:expand:spoilers'] || inThread
|
||||
preferences?.['reading:expand:spoilers'] || inThread
|
||||
? notificationOwnToot
|
||||
? 2
|
||||
: 999
|
||||
|
@ -1,8 +1,8 @@
|
||||
import CustomText from '@components/Text'
|
||||
import removeHTML from '@helpers/removeHTML'
|
||||
import { store } from '@root/store'
|
||||
import queryClient from '@utils/queryHooks'
|
||||
import removeHTML from '@utils/helpers/removeHTML'
|
||||
import { QueryKeyFilters } from '@utils/queryHooks/filters'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { getInstance } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
@ -75,7 +75,6 @@ export const shouldFilter = ({
|
||||
status: Pick<Mastodon.Status, 'content' | 'spoiler_text'>
|
||||
}): FilteredProps['filterResults'] | undefined => {
|
||||
const page = queryKey[1]
|
||||
const instance = getInstance(store.getState())
|
||||
|
||||
let returnFilter: FilteredProps['filterResults'] | undefined
|
||||
|
||||
@ -100,7 +99,8 @@ export const shouldFilter = ({
|
||||
break
|
||||
}
|
||||
}
|
||||
instance?.filters?.forEach(filter => {
|
||||
const queryKeyFilters: QueryKeyFilters = ['Filters']
|
||||
queryClient.getQueryData<Mastodon.Filter<'v1'>[]>(queryKeyFilters)?.forEach(filter => {
|
||||
if (returnFilter) {
|
||||
return
|
||||
}
|
||||
|
@ -2,13 +2,13 @@ import Icon from '@components/Icon'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { ParseEmojis } from '@components/Parse'
|
||||
import CustomText from '@components/Text'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useTimelineMutation } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import StatusContext from './Context'
|
||||
import HeaderSharedCreated from './HeaderShared/Created'
|
||||
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||
|
@ -5,15 +5,14 @@ import menuShare from '@components/contextMenu/share'
|
||||
import menuStatus from '@components/contextMenu/status'
|
||||
import Icon from '@components/Icon'
|
||||
import { RelationshipIncoming, RelationshipOutgoing } from '@components/Relationship'
|
||||
import browserPackage from '@helpers/browserPackage'
|
||||
import { getInstanceUrl } from '@utils/slices/instancesSlice'
|
||||
import browserPackage from '@utils/helpers/browserPackage'
|
||||
import { getAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import React, { Fragment, useContext, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Platform, Pressable, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||
import StatusContext from './Context'
|
||||
import HeaderSharedAccount from './HeaderShared/Account'
|
||||
@ -48,8 +47,6 @@ const TimelineHeaderNotification: React.FC<Props> = ({ notification }) => {
|
||||
const mStatus = menuStatus({ status, queryKey })
|
||||
const mInstance = menuInstance({ status, queryKey })
|
||||
|
||||
const url = useSelector(getInstanceUrl)
|
||||
|
||||
const actions = () => {
|
||||
switch (notification.type) {
|
||||
case 'follow':
|
||||
@ -63,7 +60,9 @@ const TimelineHeaderNotification: React.FC<Props> = ({ notification }) => {
|
||||
content={t('shared.actions.openReport')}
|
||||
onPress={async () =>
|
||||
WebBrowser.openAuthSessionAsync(
|
||||
`https://${url}/admin/reports/${notification.report.id}`,
|
||||
`https://${getAccountStorage.string('auth.domain')}/admin/reports/${
|
||||
notification.report.id
|
||||
}`,
|
||||
'tooot://tooot',
|
||||
{
|
||||
...(await browserPackage()),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ParseEmojis } from '@components/Parse'
|
||||
import CustomText from '@components/Text'
|
||||
import { ParseEmojis } from '@root/components/Parse'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
|
@ -5,6 +5,7 @@ import { displayMessage } from '@components/Message'
|
||||
import { ParseEmojis } from '@components/Parse'
|
||||
import RelativeTime from '@components/RelativeTime'
|
||||
import CustomText from '@components/Text'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import {
|
||||
MutationVarsTimelineUpdateStatusProperty,
|
||||
useTimelineMutation
|
||||
@ -16,7 +17,6 @@ import { maxBy } from 'lodash'
|
||||
import React, { useCallback, useContext, useMemo, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import StatusContext from './Context'
|
||||
|
||||
const TimelinePoll: React.FC = () => {
|
||||
@ -58,6 +58,7 @@ const TimelinePoll: React.FC = () => {
|
||||
theme,
|
||||
type: 'error',
|
||||
message: t('common:message.error.message', {
|
||||
// @ts-ignore
|
||||
function: t(`componentTimeline:shared.poll.meta.button.${theParams.payload.type}` as any)
|
||||
}),
|
||||
...(err.status &&
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ParseHTML } from '@components/Parse'
|
||||
import CustomText from '@components/Text'
|
||||
import detectLanguage from '@helpers/detectLanguage'
|
||||
import getLanguage from '@helpers/getLanguage'
|
||||
import detectLanguage from '@utils/helpers/detectLanguage'
|
||||
import getLanguage from '@utils/helpers/getLanguage'
|
||||
import { useTranslateQuery } from '@utils/queryHooks/translate'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
|
@ -2,16 +2,15 @@ import ComponentSeparator from '@components/Separator'
|
||||
import { useScrollToTop } from '@react-navigation/native'
|
||||
import { UseInfiniteQueryOptions } from '@tanstack/react-query'
|
||||
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||
import { useGlobalStorageListener } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { RefObject, useCallback, useRef } from 'react'
|
||||
import { FlatList, FlatListProps, Platform, RefreshControl } from 'react-native'
|
||||
import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated'
|
||||
import { useSelector } from 'react-redux'
|
||||
import TimelineEmpty from './Timeline/Empty'
|
||||
import TimelineFooter from './Timeline/Footer'
|
||||
import TimelineRefresh, { SEPARATION_Y_1, SEPARATION_Y_2 } from './Timeline/Refresh'
|
||||
import TimelineEmpty from './Empty'
|
||||
import TimelineFooter from './Footer'
|
||||
import TimelineRefresh, { SEPARATION_Y_1, SEPARATION_Y_2 } from './Refresh'
|
||||
|
||||
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList)
|
||||
|
||||
@ -100,12 +99,9 @@ const Timeline: React.FC<Props> = ({
|
||||
})
|
||||
|
||||
useScrollToTop(flRef)
|
||||
useSelector(getInstanceActive, (prev, next) => {
|
||||
if (prev !== next) {
|
||||
useGlobalStorageListener('account.active', () =>
|
||||
flRef.current?.scrollToOffset({ offset: 0, animated: false })
|
||||
}
|
||||
return prev === next
|
||||
})
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
@ -2,6 +2,7 @@ import haptics from '@components/haptics'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { TabSharedStackParamList } from '@utils/navigation/navigators'
|
||||
import {
|
||||
QueryKeyRelationship,
|
||||
@ -13,12 +14,10 @@ import {
|
||||
QueryKeyTimeline,
|
||||
useTimelineMutation
|
||||
} from '@utils/queryHooks/timeline'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert, Platform } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const menuAccount = ({
|
||||
type,
|
||||
@ -43,8 +42,7 @@ const menuAccount = ({
|
||||
|
||||
const menus: ContextMenu[][] = [[]]
|
||||
|
||||
const instanceAccount = useSelector(getInstanceAccount)
|
||||
const ownAccount = instanceAccount?.id === account.id
|
||||
const ownAccount = useAccountStorage.string('auth.account.id')['0'] === account.id
|
||||
|
||||
const [enabled, setEnabled] = useState(openChange)
|
||||
useEffect(() => {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { getHost } from '@utils/helpers/urlMatcher'
|
||||
import { QueryKeyTimeline, useTimelineMutation } from '@utils/queryHooks/timeline'
|
||||
import { getInstanceUrl } from '@utils/slices/instancesSlice'
|
||||
import { getAccountStorage } from '@utils/storage/actions'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const menuInstance = ({
|
||||
status,
|
||||
@ -35,10 +35,9 @@ const menuInstance = ({
|
||||
|
||||
const menus: ContextMenu[][] = []
|
||||
|
||||
const currentInstance = useSelector(getInstanceUrl)
|
||||
const instance = status.uri && status.uri.split(new RegExp(/\/\/(.*?)\//))[1]
|
||||
const instance = getHost(status.uri)
|
||||
|
||||
if (currentInstance !== instance && instance) {
|
||||
if (instance === getAccountStorage.string('auth.domain')) {
|
||||
menus.push([
|
||||
{
|
||||
key: 'instance-block',
|
||||
|
@ -1,19 +1,19 @@
|
||||
import apiInstance from '@api/instance'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||
import {
|
||||
MutationVarsTimelineUpdateStatusProperty,
|
||||
QueryKeyTimeline,
|
||||
useTimelineMutation
|
||||
} from '@utils/queryHooks/timeline'
|
||||
import { checkInstanceFeature, getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const menuStatus = ({
|
||||
status,
|
||||
@ -57,10 +57,10 @@ const menuStatus = ({
|
||||
|
||||
const menus: ContextMenu[][] = []
|
||||
|
||||
const instanceAccount = useSelector(getInstanceAccount, (prev, next) => prev.id === next.id)
|
||||
const ownAccount = instanceAccount?.id === status.account?.id
|
||||
const [accountId] = useAccountStorage.string('auth.account.id')
|
||||
const ownAccount = accountId === status.account?.id
|
||||
|
||||
const canEditPost = useSelector(checkInstanceFeature('edit_post'))
|
||||
const canEditPost = featureCheck('edit_post')
|
||||
|
||||
menus.push([
|
||||
{
|
||||
@ -203,9 +203,7 @@ const menuStatus = ({
|
||||
}),
|
||||
disabled: false,
|
||||
destructive: false,
|
||||
hidden:
|
||||
!ownAccount &&
|
||||
!status.mentions.filter(mention => mention.id === instanceAccount.id).length
|
||||
hidden: !ownAccount && !status.mentions.filter(mention => mention.id === accountId).length
|
||||
},
|
||||
title: t('componentContextMenu:status.mute.action', {
|
||||
defaultValue: 'false',
|
||||
|
@ -1,9 +1,14 @@
|
||||
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
||||
import { store } from '@root/store'
|
||||
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
|
||||
import queryClient from '@utils/queryHooks'
|
||||
import { QueryKeyInstance } from '@utils/queryHooks/instance'
|
||||
import i18next from 'i18next'
|
||||
import { Asset, launchImageLibrary } from 'react-native-image-picker'
|
||||
|
||||
const queryKeyInstance: QueryKeyInstance = ['Instance']
|
||||
export const MAX_MEDIA_ATTACHMENTS: number =
|
||||
queryClient.getQueryData<Mastodon.Instance<any>>(queryKeyInstance)?.configuration?.statuses
|
||||
.max_media_attachments || 4
|
||||
|
||||
export interface Props {
|
||||
mediaType?: 'photo' | 'video'
|
||||
resize?: { width?: number; height?: number }
|
||||
@ -22,7 +27,7 @@ const mediaSelector = async ({
|
||||
indicateMaximum = false,
|
||||
showActionSheetWithOptions
|
||||
}: Props): Promise<Asset[]> => {
|
||||
const _maximum = maximum || getInstanceConfigurationStatusMaxAttachments(store.getState()) || 4
|
||||
const _maximum = maximum || MAX_MEDIA_ATTACHMENTS
|
||||
|
||||
const options = () => {
|
||||
switch (mediaType) {
|
||||
|
@ -1,10 +1,9 @@
|
||||
import apiInstance from '@api/instance'
|
||||
import browserPackage from '@helpers/browserPackage'
|
||||
import navigationRef from '@helpers/navigationRef'
|
||||
import { matchAccount, matchStatus } from '@helpers/urlMatcher'
|
||||
import { store } from '@root/store'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import browserPackage from '@utils/helpers/browserPackage'
|
||||
import { matchAccount, matchStatus } from '@utils/helpers/urlMatcher'
|
||||
import navigationRef from '@utils/navigation/navigationRef'
|
||||
import { SearchResult } from '@utils/queryHooks/search'
|
||||
import { getSettingsBrowser } from '@utils/slices/settingsSlice'
|
||||
import { getGlobalStorage } from '@utils/storage/actions'
|
||||
import * as Linking from 'expo-linking'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import validUrl from 'valid-url'
|
||||
@ -89,7 +88,7 @@ const openLink = async (url: string, navigation?: any) => {
|
||||
loadingLink = false
|
||||
const validatedUrl = validUrl.isWebUri(url)
|
||||
if (validatedUrl) {
|
||||
switch (getSettingsBrowser(store.getState())) {
|
||||
switch (getGlobalStorage.string('app.browser')) {
|
||||
// Some links might end with an empty space at the end that triggers an error
|
||||
case 'internal':
|
||||
await WebBrowser.openBrowserAsync(validatedUrl, {
|
||||
|
@ -1,12 +0,0 @@
|
||||
import { store } from '@root/store'
|
||||
import { getSettingsLanguage } from '@utils/slices/settingsSlice'
|
||||
import * as Localization from 'expo-localization'
|
||||
import { Platform } from "react-native"
|
||||
|
||||
const getLanguage = (): string => {
|
||||
return Platform.OS === 'ios'
|
||||
? Localization.locale
|
||||
: getSettingsLanguage(store.getState())
|
||||
}
|
||||
|
||||
export default getLanguage
|
@ -1,21 +1,21 @@
|
||||
import i18n from 'i18next'
|
||||
import { initReactI18next } from 'react-i18next'
|
||||
|
||||
import ca from '@root/i18n/ca'
|
||||
import de from '@root/i18n/de'
|
||||
import en from '@root/i18n/en'
|
||||
import es from '@root/i18n/es'
|
||||
import fr from '@root/i18n/fr'
|
||||
import it from '@root/i18n/it'
|
||||
import ja from '@root/i18n/ja'
|
||||
import ko from '@root/i18n/ko'
|
||||
import nl from '@root/i18n/nl'
|
||||
import pt_BR from '@root/i18n/pt_BR'
|
||||
import sv from '@root/i18n/sv'
|
||||
import uk from '@root/i18n/uk'
|
||||
import vi from '@root/i18n/vi'
|
||||
import zh_Hans from '@root/i18n/zh-Hans'
|
||||
import zh_Hant from '@root/i18n/zh-Hant'
|
||||
import ca from './ca'
|
||||
import de from './de'
|
||||
import en from './en'
|
||||
import es from './es'
|
||||
import fr from './fr'
|
||||
import it from './it'
|
||||
import ja from './ja'
|
||||
import ko from './ko'
|
||||
import nl from './nl'
|
||||
import pt_BR from './pt_BR'
|
||||
import sv from './sv'
|
||||
import uk from './uk'
|
||||
import vi from './vi'
|
||||
import zh_Hans from './zh-Hans'
|
||||
import zh_Hant from './zh-Hant'
|
||||
|
||||
import '@formatjs/intl-getcanonicallocales/polyfill'
|
||||
import '@formatjs/intl-locale/polyfill'
|
||||
@ -54,6 +54,7 @@ import '@formatjs/intl-numberformat/locale-data/zh-Hans'
|
||||
import '@formatjs/intl-numberformat/locale-data/zh-Hant'
|
||||
|
||||
import '@formatjs/intl-datetimeformat/polyfill'
|
||||
import '@formatjs/intl-datetimeformat/add-all-tz'
|
||||
import '@formatjs/intl-datetimeformat/locale-data/ca'
|
||||
import '@formatjs/intl-datetimeformat/locale-data/de'
|
||||
import '@formatjs/intl-datetimeformat/locale-data/en'
|
||||
@ -69,7 +70,6 @@ import '@formatjs/intl-datetimeformat/locale-data/uk'
|
||||
import '@formatjs/intl-datetimeformat/locale-data/vi'
|
||||
import '@formatjs/intl-datetimeformat/locale-data/zh-Hans'
|
||||
import '@formatjs/intl-datetimeformat/locale-data/zh-Hant'
|
||||
import '@formatjs/intl-datetimeformat/add-all-tz'
|
||||
|
||||
import '@formatjs/intl-relativetimeformat/polyfill'
|
||||
import '@formatjs/intl-relativetimeformat/locale-data/ca'
|
@ -1,15 +1,14 @@
|
||||
import AccountButton from '@components/AccountButton'
|
||||
import CustomText from '@components/Text'
|
||||
import navigationRef from '@helpers/navigationRef'
|
||||
import navigationRef from '@utils/navigation/navigationRef'
|
||||
import { RootStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { getInstances } from '@utils/slices/instancesSlice'
|
||||
import { getGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as VideoThumbnails from 'expo-video-thumbnails'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FlatList, Image, ScrollView, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const Share = ({
|
||||
text,
|
||||
@ -93,7 +92,7 @@ const ScreenAccountSelection = ({
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('screenAccountSelection')
|
||||
|
||||
const instances = useSelector(getInstances, () => true)
|
||||
const accounts = getGlobalStorage.object('accounts')
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
@ -126,27 +125,24 @@ const ScreenAccountSelection = ({
|
||||
marginTop: StyleConstants.Spacing.M
|
||||
}}
|
||||
>
|
||||
{instances.length
|
||||
? instances
|
||||
{accounts &&
|
||||
accounts
|
||||
.slice()
|
||||
.sort((a, b) =>
|
||||
`${a.uri}${a.account.acct}`.localeCompare(`${b.uri}${b.account.acct}`)
|
||||
)
|
||||
.map((instance, index) => {
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.map((account, index) => {
|
||||
return (
|
||||
<AccountButton
|
||||
key={index}
|
||||
instance={instance}
|
||||
additionalActions={() => {
|
||||
account={account}
|
||||
additionalActions={() =>
|
||||
navigationRef.navigate('Screen-Compose', {
|
||||
type: 'share',
|
||||
...share
|
||||
})
|
||||
}}
|
||||
}
|
||||
/>
|
||||
)
|
||||
})
|
||||
: null}
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
@ -1,12 +1,11 @@
|
||||
import apiInstance from '@api/instance'
|
||||
import { HeaderLeft } from '@components/Header'
|
||||
import Icon from '@components/Icon'
|
||||
import ComponentSeparator from '@components/Separator'
|
||||
import CustomText from '@components/Text'
|
||||
import HeaderSharedCreated from '@components/Timeline/Shared/HeaderShared/Created'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { ScreenComposeStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { getInstanceDrafts, removeInstanceDraft } from '@utils/slices/instancesSlice'
|
||||
import { getAccountStorage, setAccountStorage, useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
@ -15,11 +14,19 @@ import { Dimensions, Modal, Platform, Pressable, View } from 'react-native'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import { PanGestureHandler } from 'react-native-gesture-handler'
|
||||
import { SwipeListView } from 'react-native-swipe-list-view'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeContext from './utils/createContext'
|
||||
import { formatText } from './utils/processText'
|
||||
import { ComposeStateDraft, ExtendedAttachment } from './utils/types'
|
||||
|
||||
export const removeDraft = (timestamp: number) =>
|
||||
setAccountStorage([
|
||||
{
|
||||
key: 'drafts',
|
||||
value:
|
||||
getAccountStorage.object('drafts')?.filter(draft => draft.timestamp !== timestamp) || []
|
||||
}
|
||||
])
|
||||
|
||||
const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-DraftsList'>> = ({
|
||||
navigation,
|
||||
route: {
|
||||
@ -39,11 +46,8 @@ const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-
|
||||
}, [])
|
||||
|
||||
const { composeDispatch } = useContext(ComposeContext)
|
||||
const instanceDrafts = useSelector(getInstanceDrafts)?.filter(
|
||||
draft => draft.timestamp !== timestamp
|
||||
)
|
||||
const [drafts] = useAccountStorage.object('drafts')
|
||||
const [checkingAttachments, setCheckingAttachments] = useState(false)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const actionWidth = StyleConstants.Font.Size.L + StyleConstants.Spacing.Global.PagePadding * 4
|
||||
|
||||
@ -72,7 +76,7 @@ const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-
|
||||
</View>
|
||||
<PanGestureHandler enabled={Platform.OS === 'ios'}>
|
||||
<SwipeListView
|
||||
data={instanceDrafts}
|
||||
data={drafts.filter(draft => draft.timestamp !== timestamp)}
|
||||
renderItem={({ item }: { item: ComposeStateDraft }) => {
|
||||
return (
|
||||
<Pressable
|
||||
@ -113,7 +117,7 @@ const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-
|
||||
type: 'loadDraft',
|
||||
payload: tempDraft
|
||||
})
|
||||
dispatch(removeInstanceDraft(item.timestamp))
|
||||
removeDraft(item.timestamp)
|
||||
navigation.goBack()
|
||||
}}
|
||||
>
|
||||
@ -172,7 +176,7 @@ const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-
|
||||
justifyContent: 'flex-end',
|
||||
backgroundColor: colors.red
|
||||
}}
|
||||
onPress={() => dispatch(removeInstanceDraft(item.timestamp))}
|
||||
onPress={() => removeDraft(item.timestamp)}
|
||||
children={
|
||||
<View
|
||||
style={{
|
||||
|
@ -1,6 +1,6 @@
|
||||
import apiInstance from '@api/instance'
|
||||
import haptics from '@components/haptics'
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { ScreenComposeStackScreenProps } from '@utils/navigation/navigators'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext } from 'react'
|
||||
import { Modal, View } from 'react-native'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import ComposeContext from './utils/createContext'
|
||||
|
||||
const ComposePosting = () => {
|
||||
|
@ -1,15 +1,14 @@
|
||||
import { emojis } from '@components/Emojis'
|
||||
import EmojisContext from '@components/Emojis/helpers/EmojisContext'
|
||||
import EmojisContext from '@components/Emojis/Context'
|
||||
import Icon from '@components/Icon'
|
||||
import { MAX_MEDIA_ATTACHMENTS } from '@components/mediaSelector'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { androidActionSheetStyles } from '@helpers/androidActionSheetStyles'
|
||||
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Keyboard, Pressable, StyleSheet, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeContext from '../utils/createContext'
|
||||
import chooseAndUploadAttachment from './Footer/addAttachment'
|
||||
|
||||
@ -18,10 +17,6 @@ const ComposeActions: React.FC = () => {
|
||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
||||
const { t } = useTranslation(['common', 'screenCompose'])
|
||||
const { colors } = useTheme()
|
||||
const instanceConfigurationStatusMaxAttachments = useSelector(
|
||||
getInstanceConfigurationStatusMaxAttachments,
|
||||
() => true
|
||||
)
|
||||
|
||||
const attachmentColor = useMemo(() => {
|
||||
if (composeState.poll.active) return colors.disabled
|
||||
@ -35,11 +30,8 @@ const ComposeActions: React.FC = () => {
|
||||
const attachmentOnPress = () => {
|
||||
if (composeState.poll.active) return
|
||||
|
||||
if (composeState.attachments.uploads.length < instanceConfigurationStatusMaxAttachments) {
|
||||
return chooseAndUploadAttachment({
|
||||
composeDispatch,
|
||||
showActionSheetWithOptions
|
||||
})
|
||||
if (composeState.attachments.uploads.length < MAX_MEDIA_ATTACHMENTS) {
|
||||
return chooseAndUploadAttachment({ composeDispatch, showActionSheetWithOptions })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
import Button from '@components/Button'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { getInstanceDrafts } from '@utils/slices/instancesSlice'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import React, { RefObject, useContext, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeContext from '../utils/createContext'
|
||||
|
||||
export interface Props {
|
||||
@ -17,15 +16,14 @@ const ComposeDrafts: React.FC<Props> = ({ accessibleRefDrafts }) => {
|
||||
const { t } = useTranslation('screenCompose')
|
||||
const navigation = useNavigation<any>()
|
||||
const { composeState } = useContext(ComposeContext)
|
||||
const instanceDrafts = useSelector(getInstanceDrafts)?.filter(
|
||||
draft => draft.timestamp !== composeState.timestamp
|
||||
)
|
||||
const [drafts] = useAccountStorage.object('drafts')
|
||||
const draftsCount = drafts.filter(draft => draft.timestamp !== composeState.timestamp).length
|
||||
|
||||
useEffect(() => {
|
||||
layoutAnimation()
|
||||
}, [composeState.dirty])
|
||||
|
||||
if (!composeState.dirty && instanceDrafts?.length) {
|
||||
if (!composeState.dirty && draftsCount) {
|
||||
return (
|
||||
<View
|
||||
accessible
|
||||
@ -34,9 +32,7 @@ const ComposeDrafts: React.FC<Props> = ({ accessibleRefDrafts }) => {
|
||||
children={
|
||||
<Button
|
||||
type='text'
|
||||
content={t('content.root.drafts', {
|
||||
count: instanceDrafts.length
|
||||
})}
|
||||
content={t('content.root.drafts', { count: draftsCount })}
|
||||
onPress={() =>
|
||||
navigation.navigate('Screen-Compose-DraftsList', {
|
||||
timestamp: composeState.timestamp
|
||||
|
@ -1,10 +1,10 @@
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
import { MAX_MEDIA_ATTACHMENTS } from '@components/mediaSelector'
|
||||
import CustomText from '@components/Text'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
@ -13,7 +13,6 @@ import { useTranslation } from 'react-i18next'
|
||||
import { FlatList, Pressable, StyleSheet, View } from 'react-native'
|
||||
import { Circle } from 'react-native-animated-spinkit'
|
||||
import FastImage from 'react-native-fast-image'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeContext from '../../utils/createContext'
|
||||
import { ExtendedAttachment } from '../../utils/types'
|
||||
import chooseAndUploadAttachment from './addAttachment'
|
||||
@ -31,8 +30,6 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
||||
const { colors } = useTheme()
|
||||
const navigation = useNavigation<any>()
|
||||
|
||||
const maxAttachments = useSelector(getInstanceConfigurationStatusMaxAttachments, () => true)
|
||||
|
||||
const flatListRef = useRef<FlatList>(null)
|
||||
|
||||
const sensitiveOnPress = useCallback(
|
||||
@ -285,7 +282,7 @@ const ComposeAttachments: React.FC<Props> = ({ accessibleRefAttachments }) => {
|
||||
data={composeState.attachments.uploads}
|
||||
keyExtractor={item => item.local?.uri || item.remote?.url || Math.random().toString()}
|
||||
ListFooterComponent={
|
||||
composeState.attachments.uploads.length < maxAttachments ? listFooter : null
|
||||
composeState.attachments.uploads.length < MAX_MEDIA_ATTACHMENTS ? listFooter : null
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
|
@ -3,14 +3,13 @@ import Icon from '@components/Icon'
|
||||
import { MenuRow } from '@components/Menu'
|
||||
import CustomText from '@components/Text'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { androidActionSheetStyles } from '@helpers/androidActionSheetStyles'
|
||||
import { getInstanceConfigurationPoll } from '@utils/slices/instancesSlice'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import { useInstanceQuery } from '@utils/queryHooks/instance'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { StyleSheet, TextInput, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeContext from '../../utils/createContext'
|
||||
|
||||
const ComposePoll: React.FC = () => {
|
||||
@ -24,11 +23,11 @@ const ComposePoll: React.FC = () => {
|
||||
const { t } = useTranslation(['common', 'screenCompose'])
|
||||
const { colors, mode } = useTheme()
|
||||
|
||||
const instanceConfigurationPoll = useSelector(getInstanceConfigurationPoll, () => true)
|
||||
const MAX_OPTIONS = instanceConfigurationPoll.max_options
|
||||
const MAX_CHARS_PER_OPTION = instanceConfigurationPoll.max_characters_per_option
|
||||
const MIN_EXPIRATION = instanceConfigurationPoll.min_expiration
|
||||
const MAX_EXPIRATION = instanceConfigurationPoll.max_expiration
|
||||
const { data } = useInstanceQuery()
|
||||
const MAX_OPTIONS = data?.configuration?.polls.max_options || 4
|
||||
const MAX_CHARS_PER_OPTION = data?.configuration?.polls.max_characters_per_option
|
||||
const MIN_EXPIRATION = data?.configuration?.polls.min_expiration || 300
|
||||
const MAX_EXPIRATION = data?.configuration?.polls.max_expiration || 2629746
|
||||
|
||||
const [firstRender, setFirstRender] = useState(true)
|
||||
useEffect(() => {
|
||||
|
@ -1,13 +1,13 @@
|
||||
import mediaSelector from '@components/mediaSelector'
|
||||
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import * as Crypto from 'expo-crypto'
|
||||
import * as VideoThumbnails from 'expo-video-thumbnails'
|
||||
import i18next from 'i18next'
|
||||
import { Dispatch } from 'react'
|
||||
import { Alert } from 'react-native'
|
||||
import { ComposeAction } from '../../utils/types'
|
||||
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
||||
import i18next from 'i18next'
|
||||
import apiInstance from '@api/instance'
|
||||
import mediaSelector from '@components/mediaSelector'
|
||||
import { Asset } from 'react-native-image-picker'
|
||||
import { ComposeAction } from '../../utils/types'
|
||||
|
||||
export interface Props {
|
||||
composeDispatch: Dispatch<ComposeAction>
|
||||
|
@ -1,36 +0,0 @@
|
||||
import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React, { useContext } from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeContext from '../utils/createContext'
|
||||
import ComposePostingAs from './Header/PostingAs'
|
||||
import ComposeSpoilerInput from './Header/SpoilerInput'
|
||||
import ComposeTextInput from './Header/TextInput'
|
||||
|
||||
const ComposeRootHeader: React.FC = () => {
|
||||
const { composeState } = useContext(ComposeContext)
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
const localInstances = useSelector(getInstances, (prev, next) => prev.length === next.length)
|
||||
|
||||
return (
|
||||
<View>
|
||||
{instanceActive !== -1 && localInstances.length > 1 ? (
|
||||
<View style={styles.postingAs}>
|
||||
<ComposePostingAs />
|
||||
</View>
|
||||
) : null}
|
||||
{composeState.spoiler.active ? <ComposeSpoilerInput /> : null}
|
||||
<ComposeTextInput />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
postingAs: {
|
||||
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||
marginTop: StyleConstants.Spacing.S
|
||||
}
|
||||
})
|
||||
|
||||
export default ComposeRootHeader
|
@ -1,24 +1,32 @@
|
||||
import CustomText from '@components/Text'
|
||||
import { getInstanceAccount, getInstanceUri } from '@utils/slices/instancesSlice'
|
||||
import { getAccountStorage, useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { View } from 'react-native'
|
||||
|
||||
const ComposePostingAs = () => {
|
||||
const accounts = useGlobalStorage.object('accounts')
|
||||
if (!accounts.length) return null
|
||||
|
||||
const { t } = useTranslation('screenCompose')
|
||||
const { colors } = useTheme()
|
||||
|
||||
const instanceAccount = useSelector(getInstanceAccount, (prev, next) => prev?.acct === next?.acct)
|
||||
const instanceUri = useSelector(getInstanceUri)
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||
marginTop: StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
<CustomText fontStyle='S' style={{ color: colors.secondary }}>
|
||||
{t('content.root.header.postingAs', {
|
||||
acct: instanceAccount?.acct,
|
||||
domain: instanceUri
|
||||
acct: getAccountStorage.string('auth.account.acct'),
|
||||
domain: getAccountStorage.string('auth.domain')
|
||||
})}
|
||||
</CustomText>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
import CustomText from '@components/Text'
|
||||
import { getSettingsFontsize } from '@utils/slices/settingsSlice'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { adaptiveScale } from '@utils/styles/scaling'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { TextInput } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeContext from '../../utils/createContext'
|
||||
import { formatText } from '../../utils/processText'
|
||||
|
||||
@ -15,7 +14,7 @@ const ComposeSpoilerInput: React.FC = () => {
|
||||
const { t } = useTranslation('screenCompose')
|
||||
const { colors, mode } = useTheme()
|
||||
|
||||
const adaptiveFontsize = useSelector(getSettingsFontsize)
|
||||
const [adaptiveFontsize] = useGlobalStorage.number('app.font_size')
|
||||
const adaptedFontsize = adaptiveScale(StyleConstants.Font.Size.M, adaptiveFontsize)
|
||||
const adaptedLineheight = adaptiveScale(StyleConstants.Font.LineHeight.M, adaptiveFontsize)
|
||||
|
||||
|
@ -1,14 +1,13 @@
|
||||
import { MAX_MEDIA_ATTACHMENTS } from '@components/mediaSelector'
|
||||
import CustomText from '@components/Text'
|
||||
import PasteInput, { PastedFile } from '@mattermost/react-native-paste-input'
|
||||
import { getInstanceConfigurationStatusMaxAttachments } from '@utils/slices/instancesSlice'
|
||||
import { getSettingsFontsize } from '@utils/slices/settingsSlice'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { adaptiveScale } from '@utils/styles/scaling'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeContext from '../../utils/createContext'
|
||||
import { formatText } from '../../utils/processText'
|
||||
import { uploadAttachment } from '../Footer/addAttachment'
|
||||
@ -18,9 +17,7 @@ const ComposeTextInput: React.FC = () => {
|
||||
const { t } = useTranslation(['common', 'screenCompose'])
|
||||
const { colors, mode } = useTheme()
|
||||
|
||||
const maxAttachments = useSelector(getInstanceConfigurationStatusMaxAttachments, () => true)
|
||||
|
||||
const adaptiveFontsize = useSelector(getSettingsFontsize)
|
||||
const [adaptiveFontsize] = useGlobalStorage.number('app.font_size')
|
||||
const adaptedFontsize = adaptiveScale(StyleConstants.Font.Size.M, adaptiveFontsize)
|
||||
const adaptedLineheight = adaptiveScale(StyleConstants.Font.LineHeight.M, adaptiveFontsize)
|
||||
|
||||
@ -72,7 +69,7 @@ const ComposeTextInput: React.FC = () => {
|
||||
scrollEnabled={false}
|
||||
disableCopyPaste={false}
|
||||
onPaste={(error: string | null | undefined, files: PastedFile[]) => {
|
||||
if (composeState.attachments.uploads.length + files.length > maxAttachments) {
|
||||
if (composeState.attachments.uploads.length + files.length > MAX_MEDIA_ATTACHMENTS) {
|
||||
Alert.alert(
|
||||
t('screenCompose:content.root.header.textInput.keyboardImage.exceedMaximum.title'),
|
||||
undefined,
|
||||
|
20
src/screens/Compose/Root/Header/index.tsx
Normal file
20
src/screens/Compose/Root/Header/index.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { View } from 'react-native'
|
||||
import ComposeContext from '../../utils/createContext'
|
||||
import ComposePostingAs from './PostingAs'
|
||||
import ComposeSpoilerInput from './SpoilerInput'
|
||||
import ComposeTextInput from './TextInput'
|
||||
|
||||
const ComposeRootHeader: React.FC = () => {
|
||||
const { composeState } = useContext(ComposeContext)
|
||||
|
||||
return (
|
||||
<View>
|
||||
<ComposePostingAs />
|
||||
{composeState.spoiler.active ? <ComposeSpoilerInput /> : null}
|
||||
<ComposeTextInput />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default ComposeRootHeader
|
@ -5,26 +5,17 @@ import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext, useEffect, useMemo, useRef } from 'react'
|
||||
import { AccessibilityInfo, findNodeHandle, FlatList, View } from 'react-native'
|
||||
import { Circle } from 'react-native-animated-spinkit'
|
||||
import ComposeActions from './Root/Actions'
|
||||
import ComposePosting from './Posting'
|
||||
import ComposeRootFooter from './Root/Footer'
|
||||
import ComposeRootHeader from './Root/Header'
|
||||
import ComposeRootSuggestion from './Root/Suggestion'
|
||||
import ComposeContext from './utils/createContext'
|
||||
import ComposeDrafts from './Root/Drafts'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { getInstanceConfigurationStatusCharsURL } from '@utils/slices/instancesSlice'
|
||||
|
||||
export let instanceConfigurationStatusCharsURL = 23
|
||||
import ComposePosting from '../Posting'
|
||||
import ComposeContext from '../utils/createContext'
|
||||
import ComposeActions from './Actions'
|
||||
import ComposeDrafts from './Drafts'
|
||||
import ComposeRootFooter from './Footer'
|
||||
import ComposeRootHeader from './Header'
|
||||
import ComposeRootSuggestion from './Suggestion'
|
||||
|
||||
const ComposeRoot = () => {
|
||||
const { colors } = useTheme()
|
||||
|
||||
instanceConfigurationStatusCharsURL = useSelector(
|
||||
getInstanceConfigurationStatusCharsURL,
|
||||
() => true
|
||||
)
|
||||
|
||||
const accessibleRefDrafts = useRef(null)
|
||||
const accessibleRefAttachments = useRef(null)
|
||||
|
@ -1,37 +1,37 @@
|
||||
import { handleError } from '@api/helpers'
|
||||
import { ComponentEmojis } from '@components/Emojis'
|
||||
import { EmojisState } from '@components/Emojis/helpers/EmojisContext'
|
||||
import { EmojisState } from '@components/Emojis/Context'
|
||||
import haptics from '@components/haptics'
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import haptics from '@root/components/haptics'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import ComposeRoot from '@screens/Compose/Root'
|
||||
import { formatText } from '@screens/Compose/utils/processText'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { handleError } from '@utils/api/helpers'
|
||||
import { RootStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { useInstanceQuery } from '@utils/queryHooks/instance'
|
||||
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
||||
import { useTimelineMutation } from '@utils/queryHooks/timeline'
|
||||
import { updateStoreReview } from '@utils/slices/contextsSlice'
|
||||
import {
|
||||
getInstanceAccount,
|
||||
getInstanceConfigurationStatusMaxChars,
|
||||
removeInstanceDraft,
|
||||
updateInstanceDraft
|
||||
} from '@utils/slices/instancesSlice'
|
||||
getAccountStorage,
|
||||
getGlobalStorage,
|
||||
setAccountStorage,
|
||||
setGlobalStorage
|
||||
} from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as StoreReview from 'expo-store-review'
|
||||
import { filter } from 'lodash'
|
||||
import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert, Keyboard, Platform } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
import ComposeDraftsList from './Compose/DraftsList'
|
||||
import ComposeEditAttachment from './Compose/EditAttachment'
|
||||
import { uploadAttachment } from './Compose/Root/Footer/addAttachment'
|
||||
import ComposeContext from './Compose/utils/createContext'
|
||||
import composeInitialState from './Compose/utils/initialState'
|
||||
import composeParseState from './Compose/utils/parseState'
|
||||
import composePost from './Compose/utils/post'
|
||||
import composeReducer from './Compose/utils/reducer'
|
||||
import ComposeDraftsList, { removeDraft } from './DraftsList'
|
||||
import ComposeEditAttachment from './EditAttachment'
|
||||
import { uploadAttachment } from './Root/Footer/addAttachment'
|
||||
import ComposeContext from './utils/createContext'
|
||||
import composeInitialState from './utils/initialState'
|
||||
import composeParseState from './utils/parseState'
|
||||
import composePost from './utils/post'
|
||||
import composeReducer from './utils/reducer'
|
||||
|
||||
const Stack = createNativeStackNavigator()
|
||||
|
||||
@ -54,12 +54,8 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
}
|
||||
}, [])
|
||||
|
||||
const localAccount = useSelector(getInstanceAccount, (prev, next) =>
|
||||
prev?.preferences && next?.preferences
|
||||
? prev?.preferences['posting:default:visibility'] ===
|
||||
next?.preferences['posting:default:visibility']
|
||||
: true
|
||||
)
|
||||
const { data: preferences } = usePreferencesQuery()
|
||||
|
||||
const initialReducerState = useMemo(() => {
|
||||
if (params) {
|
||||
return composeParseState(params)
|
||||
@ -70,21 +66,19 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
attachments: {
|
||||
...composeInitialState.attachments,
|
||||
sensitive:
|
||||
localAccount?.preferences && localAccount?.preferences['posting:default:sensitive']
|
||||
? localAccount?.preferences['posting:default:sensitive']
|
||||
preferences?.['posting:default:sensitive'] !== undefined
|
||||
? preferences['posting:default:sensitive']
|
||||
: false
|
||||
},
|
||||
visibility:
|
||||
localAccount?.preferences && localAccount.preferences['posting:default:visibility']
|
||||
? localAccount.preferences['posting:default:visibility']
|
||||
: 'public'
|
||||
visibility: preferences?.['posting:default:visibility'] || 'public'
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const [composeState, composeDispatch] = useReducer(composeReducer, initialReducerState)
|
||||
|
||||
const maxTootChars = useSelector(getInstanceConfigurationStatusMaxChars, () => true)
|
||||
const { data: dataInstance } = useInstanceQuery()
|
||||
const maxTootChars = dataInstance?.configuration?.statuses.max_characters || 500
|
||||
const totalTextCount =
|
||||
(composeState.spoiler.active ? composeState.spoiler.count : 0) + composeState.text.count
|
||||
|
||||
@ -177,8 +171,7 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
}, [params?.type])
|
||||
|
||||
const saveDraft = () => {
|
||||
dispatch(
|
||||
updateInstanceDraft({
|
||||
const payload = {
|
||||
timestamp: composeState.timestamp,
|
||||
spoiler: composeState.spoiler.raw,
|
||||
text: composeState.text.raw,
|
||||
@ -187,19 +180,26 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
visibility: composeState.visibility,
|
||||
visibilityLock: composeState.visibilityLock,
|
||||
replyToStatus: composeState.replyToStatus
|
||||
})
|
||||
)
|
||||
}
|
||||
const removeDraft = useCallback(() => {
|
||||
dispatch(removeInstanceDraft(composeState.timestamp))
|
||||
}, [composeState.timestamp])
|
||||
|
||||
const currentDrafts = getAccountStorage.object('drafts') || []
|
||||
const draftIndex = currentDrafts?.findIndex(
|
||||
({ timestamp }) => timestamp === composeState.timestamp
|
||||
)
|
||||
if (draftIndex === -1) {
|
||||
currentDrafts?.unshift(payload)
|
||||
} else {
|
||||
currentDrafts[draftIndex] = payload
|
||||
}
|
||||
setAccountStorage([{ key: 'drafts', value: currentDrafts }])
|
||||
}
|
||||
useEffect(() => {
|
||||
const autoSave = composeState.dirty
|
||||
? setInterval(() => {
|
||||
saveDraft()
|
||||
}, 1000)
|
||||
: removeDraft()
|
||||
return () => autoSave && clearInterval(autoSave)
|
||||
: removeDraft(composeState.timestamp)
|
||||
return () => (autoSave ? clearInterval(autoSave) : undefined)
|
||||
}, [composeState])
|
||||
|
||||
const headerLeft = useCallback(
|
||||
@ -217,7 +217,7 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
text: t('screenCompose:heading.left.alert.buttons.delete'),
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
removeDraft()
|
||||
removeDraft(composeState.timestamp)
|
||||
navigation.goBack()
|
||||
}
|
||||
},
|
||||
@ -239,7 +239,6 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
),
|
||||
[composeState]
|
||||
)
|
||||
const dispatch = useAppDispatch()
|
||||
const headerRightDisabled = useMemo(() => {
|
||||
if (totalTextCount > maxTootChars) {
|
||||
return true
|
||||
@ -277,7 +276,14 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
if (Platform.OS === 'ios' && Platform.constants.osVersion === '13.3') {
|
||||
// https://github.com/tooot-app/app/issues/59
|
||||
} else {
|
||||
dispatch(updateStoreReview(1))
|
||||
const currentCount = getGlobalStorage.number('app.count_till_store_review')
|
||||
if (currentCount === 10) {
|
||||
StoreReview?.isAvailableAsync()
|
||||
.then(() => StoreReview.requestReview())
|
||||
.catch(() => {})
|
||||
} else {
|
||||
setGlobalStorage('app.count_till_store_review', (currentCount || 0) + 1)
|
||||
}
|
||||
}
|
||||
|
||||
switch (params?.type) {
|
||||
@ -296,7 +302,7 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
||||
}
|
||||
break
|
||||
}
|
||||
removeDraft()
|
||||
removeDraft(composeState.timestamp)
|
||||
navigation.goBack()
|
||||
})
|
||||
.catch(error => {
|
@ -1,14 +1,12 @@
|
||||
import { store } from '@root/store'
|
||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { getAccountStorage } from '@utils/storage/actions'
|
||||
import composeInitialState from './initialState'
|
||||
import { ComposeState } from './types'
|
||||
|
||||
const assignVisibility = (
|
||||
target: ComposeState['visibility']
|
||||
): Pick<ComposeState, 'visibility' | 'visibilityLock'> => {
|
||||
const accountPreference =
|
||||
getInstanceAccount(store.getState())?.preferences?.['posting:default:visibility'] || 'public'
|
||||
const preferences = getAccountStorage.object('preferences')
|
||||
|
||||
switch (target) {
|
||||
case 'direct':
|
||||
@ -16,13 +14,13 @@ const assignVisibility = (
|
||||
case 'private':
|
||||
return { visibility: 'private', visibilityLock: false }
|
||||
case 'unlisted':
|
||||
if (accountPreference === 'private') {
|
||||
if (preferences?.['posting:default:visibility'] === 'private') {
|
||||
return { visibility: 'private', visibilityLock: false }
|
||||
} else {
|
||||
return { visibility: 'unlisted', visibilityLock: false }
|
||||
}
|
||||
case 'public':
|
||||
switch (accountPreference) {
|
||||
switch (preferences) {
|
||||
case 'private':
|
||||
return { visibility: 'private', visibilityLock: false }
|
||||
case 'unlisted':
|
||||
|
@ -1,6 +1,6 @@
|
||||
import apiInstance from '@api/instance'
|
||||
import detectLanguage from '@helpers/detectLanguage'
|
||||
import { ComposeState } from '@screens/Compose/utils/types'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import detectLanguage from '@utils/helpers/detectLanguage'
|
||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||
import * as Crypto from 'expo-crypto'
|
||||
import { getPureContent } from './processText'
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { emojis } from '@components/Emojis'
|
||||
import CustomText from '@components/Text'
|
||||
import queryClient from '@utils/queryHooks'
|
||||
import { QueryKeyInstance } from '@utils/queryHooks/instance'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import LinkifyIt from 'linkify-it'
|
||||
import { debounce, differenceWith, isEqual } from 'lodash'
|
||||
import React, { Dispatch } from 'react'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { ComposeAction, ComposeState } from './types'
|
||||
import { instanceConfigurationStatusCharsURL } from '../Root'
|
||||
import CustomText from '@components/Text'
|
||||
import { emojis } from '@components/Emojis'
|
||||
|
||||
export interface Params {
|
||||
textInput: ComposeState['textInputFocus']['current']
|
||||
@ -140,7 +141,11 @@ const formatText = ({ textInput, composeDispatch, content, disableDebounce = fal
|
||||
contentLength = contentLength + main.length
|
||||
break
|
||||
default:
|
||||
contentLength = contentLength + instanceConfigurationStatusCharsURL
|
||||
const queryKeyInstance: QueryKeyInstance = ['Instance']
|
||||
contentLength =
|
||||
contentLength +
|
||||
(queryClient.getQueryData<Mastodon.Instance<any>>(queryKeyInstance)?.configuration
|
||||
?.statuses.characters_reserved_per_url || 23)
|
||||
break
|
||||
}
|
||||
_content = next
|
||||
|
2
src/screens/Compose/utils/types.d.ts
vendored
2
src/screens/Compose/utils/types.d.ts
vendored
@ -1,4 +1,4 @@
|
||||
import { RefObject } from 'react';
|
||||
import { RefObject } from 'react'
|
||||
import { Asset } from 'react-native-image-picker'
|
||||
|
||||
export type ExtendedAttachment = {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import { HeaderCenter, HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { androidActionSheetStyles } from '@helpers/androidActionSheetStyles'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import { RootStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
@ -18,9 +18,9 @@ import {
|
||||
} from 'react-native'
|
||||
import { Directions, Gesture, LongPressGestureHandler } from 'react-native-gesture-handler'
|
||||
import { runOnJS, useSharedValue } from 'react-native-reanimated'
|
||||
import { Zoom, createZoomListComponent } from 'react-native-reanimated-zoom'
|
||||
import { createZoomListComponent, Zoom } from 'react-native-reanimated-zoom'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import saveImage from './ImageViewer/save'
|
||||
import saveImage from './save'
|
||||
|
||||
const ZoomFlatList = createZoomListComponent(FlatList)
|
||||
|
@ -8,13 +8,12 @@ import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||
import usePopToTop from '@utils/navigation/usePopToTop'
|
||||
import { useListsQuery } from '@utils/queryHooks/lists'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { getInstanceFollowingPage, updateInstanceFollowingPage } from '@utils/slices/instancesSlice'
|
||||
import { setAccountStorage, useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { View } from 'react-native'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||
|
||||
const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-Root'>> = ({
|
||||
@ -25,11 +24,10 @@ const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-R
|
||||
|
||||
const { data: lists } = useListsQuery()
|
||||
|
||||
const dispatch = useDispatch()
|
||||
const instanceFollowingPage = useSelector(getInstanceFollowingPage)
|
||||
const [pageLocal] = useAccountStorage.object('page_local')
|
||||
const [queryKey, setQueryKey] = useState<QueryKeyTimeline>([
|
||||
'Timeline',
|
||||
{ page: 'Following', ...instanceFollowingPage }
|
||||
{ page: 'Following', ...pageLocal }
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
@ -59,7 +57,7 @@ const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-R
|
||||
: t('tabs.local.name')
|
||||
}
|
||||
/>
|
||||
{page.page === 'Following' && !instanceFollowingPage.showBoosts ? (
|
||||
{page.page === 'Following' && !pageLocal.showBoosts ? (
|
||||
<Icon
|
||||
name='Repeat'
|
||||
size={StyleConstants.Font.Size.M}
|
||||
@ -68,7 +66,7 @@ const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-R
|
||||
crossOut
|
||||
/>
|
||||
) : null}
|
||||
{page.page === 'Following' && !instanceFollowingPage.showReplies ? (
|
||||
{page.page === 'Following' && !pageLocal.showReplies ? (
|
||||
<Icon
|
||||
name='MessageCircle'
|
||||
size={StyleConstants.Font.Size.M}
|
||||
@ -90,9 +88,7 @@ const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-R
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Item
|
||||
key='default'
|
||||
onSelect={() =>
|
||||
setQueryKey(['Timeline', { page: 'Following', ...instanceFollowingPage }])
|
||||
}
|
||||
onSelect={() => setQueryKey(['Timeline', { page: 'Following', ...pageLocal }])}
|
||||
disabled={page.page === 'Following'}
|
||||
>
|
||||
<DropdownMenu.ItemTitle children={t('tabs.local.name')} />
|
||||
@ -100,19 +96,22 @@ const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-R
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.CheckboxItem
|
||||
key='showBoosts'
|
||||
value={instanceFollowingPage.showBoosts ? 'on' : 'off'}
|
||||
value={pageLocal.showBoosts ? 'on' : 'off'}
|
||||
onValueChange={() => {
|
||||
setQueryKey([
|
||||
'Timeline',
|
||||
{
|
||||
page: 'Following',
|
||||
showBoosts: !instanceFollowingPage.showBoosts,
|
||||
showReplies: instanceFollowingPage.showReplies
|
||||
showBoosts: !pageLocal.showBoosts,
|
||||
showReplies: pageLocal.showReplies
|
||||
}
|
||||
])
|
||||
setAccountStorage([
|
||||
{
|
||||
key: 'page_local',
|
||||
value: { ...pageLocal, showBoosts: !pageLocal.showBoosts }
|
||||
}
|
||||
])
|
||||
dispatch(
|
||||
updateInstanceFollowingPage({ showBoosts: !instanceFollowingPage.showBoosts })
|
||||
)
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.ItemIndicator />
|
||||
@ -120,19 +119,22 @@ const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-R
|
||||
</DropdownMenu.CheckboxItem>
|
||||
<DropdownMenu.CheckboxItem
|
||||
key='showReplies'
|
||||
value={instanceFollowingPage.showReplies ? 'on' : 'off'}
|
||||
value={pageLocal.showReplies ? 'on' : 'off'}
|
||||
onValueChange={() => {
|
||||
setQueryKey([
|
||||
'Timeline',
|
||||
{
|
||||
page: 'Following',
|
||||
showBoosts: instanceFollowingPage.showBoosts,
|
||||
showReplies: !instanceFollowingPage.showReplies
|
||||
showBoosts: pageLocal.showBoosts,
|
||||
showReplies: !pageLocal.showReplies
|
||||
}
|
||||
])
|
||||
setAccountStorage([
|
||||
{
|
||||
key: 'page_local',
|
||||
value: { ...pageLocal, showReplies: !pageLocal.showReplies }
|
||||
}
|
||||
])
|
||||
dispatch(
|
||||
updateInstanceFollowingPage({ showReplies: !instanceFollowingPage.showReplies })
|
||||
)
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.ItemTitle children={t('tabs.local.options.showReplies')} />
|
||||
@ -174,7 +176,7 @@ const Root: React.FC<NativeStackScreenProps<TabLocalStackParamList, 'Tab-Local-R
|
||||
/>
|
||||
)
|
||||
})
|
||||
}, [mode, queryKey[1], instanceFollowingPage, lists])
|
||||
}, [mode, queryKey[1], pageLocal, lists])
|
||||
|
||||
usePopToTop()
|
||||
|
||||
|
@ -1,19 +1,19 @@
|
||||
import { EmojisState } from '@components/Emojis/helpers/EmojisContext'
|
||||
import { EmojisState } from '@components/Emojis/Context'
|
||||
import haptics from '@components/haptics'
|
||||
import { HeaderCenter, HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import ComponentInput from '@components/Input'
|
||||
import { displayMessage, Message } from '@components/Message'
|
||||
import Selections from '@components/Selections'
|
||||
import CustomText from '@components/Text'
|
||||
import { CommonActions } from '@react-navigation/native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { TabMeStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { QueryKeyLists, useListsMutation } from '@utils/queryHooks/lists'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert, Platform, ScrollView, TextInput } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { Alert, ScrollView, TextInput } from 'react-native'
|
||||
|
||||
const TabMeListEdit: React.FC<TabMeStackScreenProps<'Tab-Me-List-Edit'>> = ({
|
||||
navigation,
|
||||
|
@ -2,6 +2,7 @@ import Icon from '@components/Icon'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import Timeline from '@components/Timeline'
|
||||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { TabMeStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { QueryKeyLists, useListsMutation } from '@utils/queryHooks/lists'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
@ -9,7 +10,6 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||
import { menuListAccounts, menuListDelete, menuListEdit } from './menus'
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import navigationRef from '@helpers/navigationRef'
|
||||
import { UseMutationResult } from '@tanstack/react-query'
|
||||
import navigationRef from '@utils/navigation/navigationRef'
|
||||
import i18next from 'i18next'
|
||||
import { Alert } from 'react-native'
|
||||
import { UseMutationResult } from '@tanstack/react-query'
|
||||
|
||||
export const menuListAccounts = ({ params }: { params: Mastodon.List }) => ({
|
||||
key: 'list-accounts',
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ComponentEmojis } from '@components/Emojis'
|
||||
import { EmojisState } from '@components/Emojis/helpers/EmojisContext'
|
||||
import { EmojisState } from '@components/Emojis/Context'
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import ComponentInput from '@components/Input'
|
||||
import CustomText from '@components/Text'
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ComponentEmojis } from '@components/Emojis'
|
||||
import { EmojisState } from '@components/Emojis/helpers/EmojisContext'
|
||||
import { EmojisState } from '@components/Emojis/Context'
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import ComponentInput from '@components/Input'
|
||||
import { TabMeProfileStackScreenProps } from '@utils/navigation/navigators'
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ComponentEmojis } from '@components/Emojis'
|
||||
import { EmojisState } from '@components/Emojis/helpers/EmojisContext'
|
||||
import { EmojisState } from '@components/Emojis/Context'
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import ComponentInput from '@components/Input'
|
||||
import { TabMeProfileStackScreenProps } from '@utils/navigation/navigators'
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { androidActionSheetStyles } from '@helpers/androidActionSheetStyles'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import queryClient from '@utils/queryHooks'
|
||||
import { TabMeProfileStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { QueryKeyPreferences } from '@utils/queryHooks/preferences'
|
||||
import { useProfileMutation, useProfileQuery } from '@utils/queryHooks/profile'
|
||||
import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { RefObject } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -24,7 +24,11 @@ const TabMeProfileRoot: React.FC<
|
||||
|
||||
const { data, isFetching } = useProfileQuery()
|
||||
const { mutateAsync } = useProfileMutation()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const refetchPreferences = () => {
|
||||
const queryKeyPreferences: QueryKeyPreferences = ['Preferences']
|
||||
queryClient.refetchQueries(queryKeyPreferences)
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
@ -117,7 +121,7 @@ const TabMeProfileRoot: React.FC<
|
||||
},
|
||||
type: 'source[privacy]',
|
||||
data: indexVisibilityMapping[buttonIndex]
|
||||
}).then(() => dispatch(updateAccountPreferences()))
|
||||
}).then(() => refetchPreferences())
|
||||
}
|
||||
break
|
||||
}
|
||||
@ -139,7 +143,7 @@ const TabMeProfileRoot: React.FC<
|
||||
},
|
||||
type: 'source[sensitive]',
|
||||
data: data?.source.sensitive === undefined ? true : !data.source.sensitive
|
||||
}).then(() => dispatch(updateAccountPreferences()))
|
||||
}).then(() => refetchPreferences())
|
||||
}
|
||||
loading={isFetching}
|
||||
/>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { HeaderCenter, HeaderLeft } from '@components/Header'
|
||||
import { HeaderLeft } from '@components/Header'
|
||||
import { Message } from '@components/Message'
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import { TabMeProfileStackParamList, TabMeStackScreenProps } from '@utils/navigation/navigators'
|
||||
@ -6,10 +6,10 @@ import React, { useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { KeyboardAvoidingView, Platform } from 'react-native'
|
||||
import FlashMessage from 'react-native-flash-message'
|
||||
import TabMeProfileFields from './Profile/Fields'
|
||||
import TabMeProfileName from './Profile/Name'
|
||||
import TabMeProfileNote from './Profile/Note'
|
||||
import TabMeProfileRoot from './Profile/Root'
|
||||
import TabMeProfileFields from './Fields'
|
||||
import TabMeProfileName from './Name'
|
||||
import TabMeProfileNote from './Note'
|
||||
import TabMeProfileRoot from './Root'
|
||||
|
||||
const Stack = createNativeStackNavigator<TabMeProfileStackParamList>()
|
||||
|
@ -1,40 +1,39 @@
|
||||
import Button from '@components/Button'
|
||||
import Icon from '@components/Icon'
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import CustomText from '@components/Text'
|
||||
import browserPackage from '@helpers/browserPackage'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import { isDevelopment } from '@utils/checkEnvironment'
|
||||
import * as Sentry from '@sentry/react-native'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import apiTooot, { TOOOT_API_DOMAIN } from '@utils/api/tooot'
|
||||
import browserPackage from '@utils/helpers/browserPackage'
|
||||
import { isDevelopment } from '@utils/helpers/checkEnvironment'
|
||||
import { PUSH_ADMIN, PUSH_DEFAULT, setChannels } from '@utils/push/constants'
|
||||
import { updateExpoToken } from '@utils/push/updateExpoToken'
|
||||
import { useAppsQuery } from '@utils/queryHooks/apps'
|
||||
import { useProfileQuery } from '@utils/queryHooks/profile'
|
||||
import { getExpoToken, retrieveExpoToken } from '@utils/slices/appSlice'
|
||||
import { PUSH_ADMIN, PUSH_DEFAULT, usePushFeatures } from '@utils/slices/instances/push/utils'
|
||||
import { updateInstancePush } from '@utils/slices/instances/updatePush'
|
||||
import { updateInstancePushAlert } from '@utils/slices/instances/updatePushAlert'
|
||||
import { updateInstancePushDecode } from '@utils/slices/instances/updatePushDecode'
|
||||
import { getInstance, getInstancePush } from '@utils/slices/instancesSlice'
|
||||
import { setAccountStorage, useAccountStorage, useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as Notifications from 'expo-notifications'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AppState, Linking, ScrollView, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { AppState, Linking, Platform, ScrollView, View } from 'react-native'
|
||||
|
||||
const TabMePush: React.FC = () => {
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('screenTabs')
|
||||
|
||||
const instance = useSelector(getInstance)
|
||||
const expoToken = useSelector(getExpoToken)
|
||||
const [expoToken] = useGlobalStorage.string('app.expo_token')
|
||||
const [push] = useAccountStorage.object('push')
|
||||
const [domain] = useAccountStorage.string('auth.domain')
|
||||
const [accountId] = useAccountStorage.string('auth.account.id')
|
||||
const [accountAcct] = useAccountStorage.string('auth.account.acct')
|
||||
|
||||
const appsQuery = useAppsQuery()
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
const instancePush = useSelector(getInstancePush)
|
||||
|
||||
const [pushAvailable, setPushAvailable] = useState<boolean>()
|
||||
const [pushEnabled, setPushEnabled] = useState<boolean>()
|
||||
const [pushCanAskAgain, setPushCanAskAgain] = useState<boolean>()
|
||||
@ -45,7 +44,7 @@ const TabMePush: React.FC = () => {
|
||||
setPushEnabled(permissions.granted)
|
||||
setPushCanAskAgain(permissions.canAskAgain)
|
||||
layoutAnimation()
|
||||
dispatch(retrieveExpoToken())
|
||||
await updateExpoToken()
|
||||
}
|
||||
|
||||
if (appsQuery.data?.vapid_key) {
|
||||
@ -54,7 +53,7 @@ const TabMePush: React.FC = () => {
|
||||
if (isDevelopment) {
|
||||
setPushAvailable(true)
|
||||
} else {
|
||||
setPushAvailable(!!expoToken)
|
||||
setPushAvailable(!!expoToken?.length)
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,26 +63,29 @@ const TabMePush: React.FC = () => {
|
||||
}
|
||||
}, [appsQuery.data?.vapid_key])
|
||||
|
||||
const pushFeatures = usePushFeatures()
|
||||
|
||||
const alerts = () =>
|
||||
instancePush?.alerts
|
||||
? PUSH_DEFAULT(pushFeatures).map(alert => (
|
||||
push?.alerts
|
||||
? PUSH_DEFAULT.map(alert => (
|
||||
<MenuRow
|
||||
key={alert}
|
||||
title={t(`me.push.${alert}.heading`)}
|
||||
switchDisabled={!pushEnabled || !instancePush.global}
|
||||
switchValue={instancePush?.alerts[alert]}
|
||||
switchOnValueChange={() =>
|
||||
dispatch(
|
||||
updateInstancePushAlert({
|
||||
alerts: {
|
||||
...instancePush?.alerts,
|
||||
[alert]: !instancePush?.alerts[alert]
|
||||
switchDisabled={!pushEnabled || !push.global}
|
||||
switchValue={push?.alerts[alert]}
|
||||
switchOnValueChange={async () => {
|
||||
const alerts = { ...push?.alerts, [alert]: !push?.alerts[alert] }
|
||||
const formData = new FormData()
|
||||
for (const [key, value] of Object.entries(alerts)) {
|
||||
formData.append(`data[alerts][${key}]`, value.toString())
|
||||
}
|
||||
|
||||
await apiInstance<Mastodon.PushSubscription>({
|
||||
method: 'put',
|
||||
url: 'push/subscription',
|
||||
body: formData
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
setAccountStorage([{ key: 'push', value: { ...push, alerts } }])
|
||||
}}
|
||||
/>
|
||||
))
|
||||
: null
|
||||
@ -91,26 +93,34 @@ const TabMePush: React.FC = () => {
|
||||
const profileQuery = useProfileQuery()
|
||||
const adminAlerts = () =>
|
||||
profileQuery.data?.role?.permissions
|
||||
? PUSH_ADMIN(pushFeatures, profileQuery.data?.role?.permissions).map(({ type }) => (
|
||||
? PUSH_ADMIN.map(({ type }) => (
|
||||
<MenuRow
|
||||
key={type}
|
||||
title={t(`me.push.${type}.heading`)}
|
||||
switchDisabled={!pushEnabled || !instancePush.global}
|
||||
switchValue={instancePush?.alerts[type]}
|
||||
switchOnValueChange={() =>
|
||||
dispatch(
|
||||
updateInstancePushAlert({
|
||||
alerts: {
|
||||
...instancePush?.alerts,
|
||||
[type]: !instancePush?.alerts[type]
|
||||
switchDisabled={!pushEnabled || !push.global}
|
||||
switchValue={push?.alerts[type]}
|
||||
switchOnValueChange={async () => {
|
||||
const alerts = { ...push?.alerts, [type]: !push?.alerts[type] }
|
||||
const formData = new FormData()
|
||||
for (const [key, value] of Object.entries(alerts)) {
|
||||
formData.append(`data[alerts][${key}]`, value.toString())
|
||||
}
|
||||
|
||||
await apiInstance<Mastodon.PushSubscription>({
|
||||
method: 'put',
|
||||
url: 'push/subscription',
|
||||
body: formData
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
setAccountStorage([{ key: 'push', value: { ...push, alerts } }])
|
||||
}}
|
||||
/>
|
||||
))
|
||||
: null
|
||||
|
||||
const pushPath = `${expoToken}/${domain}/${accountId}`
|
||||
const accountFull = `@${accountAcct}@${domain}`
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
{!!appsQuery.data?.vapid_key ? (
|
||||
@ -142,24 +152,103 @@ const TabMePush: React.FC = () => {
|
||||
) : null}
|
||||
<MenuContainer>
|
||||
<MenuRow
|
||||
title={t('me.push.global.heading', {
|
||||
acct: `@${instance.account.acct}@${instance.uri}`
|
||||
})}
|
||||
title={t('me.push.global.heading', { acct: `@${accountAcct}@${domain}` })}
|
||||
description={t('me.push.global.description')}
|
||||
switchDisabled={!pushEnabled}
|
||||
switchValue={pushEnabled === false ? false : instancePush?.global}
|
||||
switchOnValueChange={() => dispatch(updateInstancePush(!instancePush?.global))}
|
||||
switchValue={pushEnabled === false ? false : push?.global}
|
||||
switchOnValueChange={async () => {
|
||||
if (push.global) {
|
||||
// Turning off
|
||||
await apiInstance({
|
||||
method: 'delete',
|
||||
url: 'push/subscription'
|
||||
})
|
||||
await apiTooot({
|
||||
method: 'delete',
|
||||
url: `push/unsubscribe/${pushPath}`
|
||||
})
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
Notifications.deleteNotificationChannelGroupAsync(accountFull)
|
||||
}
|
||||
|
||||
setAccountStorage([{ key: 'push', value: { ...push, global: false } }])
|
||||
} else {
|
||||
// Turning on
|
||||
const randomPath = (Math.random() + 1).toString(36).substring(2)
|
||||
|
||||
const endpoint = `https://${TOOOT_API_DOMAIN}/push/send/${pushPath}/${randomPath}`
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('subscription[endpoint]', endpoint)
|
||||
formData.append(
|
||||
'subscription[keys][p256dh]',
|
||||
'BMn2PLpZrMefG981elzG6SB1EY9gU7QZwmtZ/a/J2vUeWG+zXgeskMPwHh4T/bxsD4l7/8QT94F57CbZqYRRfJo='
|
||||
)
|
||||
formData.append('subscription[keys][auth]', push.key)
|
||||
for (const [key, value] of Object.entries(push.alerts)) {
|
||||
formData.append(`data[alerts][${key}]`, value.toString())
|
||||
}
|
||||
|
||||
const res = await apiInstance<Mastodon.PushSubscription>({
|
||||
method: 'post',
|
||||
url: 'push/subscription',
|
||||
body: formData
|
||||
})
|
||||
|
||||
if (!res.body.server_key?.length) {
|
||||
displayMessage({
|
||||
type: 'danger',
|
||||
duration: 'long',
|
||||
message: t('me.push.missingServerKey.message'),
|
||||
description: t('me.push.missingServerKey.description')
|
||||
})
|
||||
Sentry.setContext('Push server key', {
|
||||
instance: domain,
|
||||
resBody: res.body
|
||||
})
|
||||
Sentry.captureMessage('Push register error')
|
||||
return Promise.reject()
|
||||
}
|
||||
|
||||
await apiTooot({
|
||||
method: 'post',
|
||||
url: `push/subscribe/${pushPath}`,
|
||||
body: {
|
||||
accountFull,
|
||||
serverKey: res.body.server_key,
|
||||
auth: push.decode === false ? null : push.key
|
||||
}
|
||||
})
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
setChannels(true)
|
||||
}
|
||||
|
||||
setAccountStorage([{ key: 'push', value: { ...push, global: true } }])
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</MenuContainer>
|
||||
<MenuContainer>
|
||||
<MenuRow
|
||||
title={t('me.push.decode.heading')}
|
||||
description={t('me.push.decode.description')}
|
||||
switchDisabled={!pushEnabled || !instancePush?.global}
|
||||
switchValue={instancePush?.decode}
|
||||
switchOnValueChange={() =>
|
||||
dispatch(updateInstancePushDecode(!instancePush?.decode))
|
||||
switchDisabled={!pushEnabled || !push?.global}
|
||||
switchValue={push?.decode}
|
||||
switchOnValueChange={async () => {
|
||||
await apiTooot({
|
||||
method: 'put',
|
||||
url: `push/update-decode/${pushPath}`,
|
||||
body: { auth: push?.decode ? null : push.key }
|
||||
})
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
setChannels(true)
|
||||
}
|
||||
|
||||
setAccountStorage([{ key: 'push', value: { ...push, decode: !push.decode } }])
|
||||
}}
|
||||
/>
|
||||
<MenuRow
|
||||
title={t('me.push.howitworks')}
|
||||
|
@ -1,58 +1,44 @@
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import { useAnnouncementQuery } from '@utils/queryHooks/announcement'
|
||||
import { useListsQuery } from '@utils/queryHooks/lists'
|
||||
import { useFollowedTagsQuery } from '@utils/queryHooks/tags'
|
||||
import { getInstanceMePage, updateInstanceMePage } from '@utils/slices/instancesSlice'
|
||||
import { getInstancePush } from '@utils/slices/instancesSlice'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const Collections: React.FC = () => {
|
||||
const { t } = useTranslation(['screenAnnouncements', 'screenTabs'])
|
||||
const navigation = useNavigation<any>()
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
const mePage = useSelector(getInstanceMePage)
|
||||
const [pageMe, setPageMe] = useAccountStorage.object('page_me')
|
||||
|
||||
useFollowedTagsQuery({
|
||||
options: {
|
||||
onSuccess: data =>
|
||||
dispatch(
|
||||
updateInstanceMePage({
|
||||
followedTags: { shown: !!data?.pages?.[0].body?.length }
|
||||
})
|
||||
)
|
||||
setPageMe({ ...pageMe, followedTags: { shown: !!data?.pages?.[0].body?.length } })
|
||||
}
|
||||
})
|
||||
useListsQuery({
|
||||
options: {
|
||||
onSuccess: data =>
|
||||
dispatch(
|
||||
updateInstanceMePage({
|
||||
lists: { shown: !!data?.length }
|
||||
})
|
||||
)
|
||||
onSuccess: data => setPageMe({ ...pageMe, lists: { shown: !!data?.length } })
|
||||
}
|
||||
})
|
||||
useAnnouncementQuery({
|
||||
showAll: true,
|
||||
options: {
|
||||
onSuccess: data =>
|
||||
dispatch(
|
||||
updateInstanceMePage({
|
||||
setPageMe({
|
||||
...pageMe,
|
||||
announcements: {
|
||||
shown: !!data?.length ? true : false,
|
||||
unread: data?.filter(announcement => !announcement.read).length
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const instancePush = useSelector(getInstancePush, (prev, next) => prev?.global === next?.global)
|
||||
const [instancePush] = useAccountStorage.object('push')
|
||||
|
||||
return (
|
||||
<MenuContainer>
|
||||
@ -74,7 +60,7 @@ const Collections: React.FC = () => {
|
||||
title={t('screenTabs:me.stacks.favourites.name')}
|
||||
onPress={() => navigation.navigate('Tab-Me-Favourites')}
|
||||
/>
|
||||
{mePage.lists.shown ? (
|
||||
{pageMe.lists.shown ? (
|
||||
<MenuRow
|
||||
iconFront='List'
|
||||
iconBack='ChevronRight'
|
||||
@ -82,7 +68,7 @@ const Collections: React.FC = () => {
|
||||
onPress={() => navigation.navigate('Tab-Me-List-List')}
|
||||
/>
|
||||
) : null}
|
||||
{mePage.followedTags.shown ? (
|
||||
{pageMe.followedTags.shown ? (
|
||||
<MenuRow
|
||||
iconFront='Hash'
|
||||
iconBack='ChevronRight'
|
||||
@ -90,15 +76,15 @@ const Collections: React.FC = () => {
|
||||
onPress={() => navigation.navigate('Tab-Me-FollowedTags')}
|
||||
/>
|
||||
) : null}
|
||||
{mePage.announcements.shown ? (
|
||||
{pageMe.announcements.shown ? (
|
||||
<MenuRow
|
||||
iconFront='Clipboard'
|
||||
iconBack='ChevronRight'
|
||||
title={t('screenAnnouncements:heading')}
|
||||
content={
|
||||
mePage.announcements.unread
|
||||
pageMe.announcements.unread
|
||||
? t('screenTabs:me.root.announcements.content.unread', {
|
||||
amount: mePage.announcements.unread
|
||||
amount: pageMe.announcements.unread
|
||||
})
|
||||
: t('screenTabs:me.root.announcements.content.read')
|
||||
}
|
||||
|
@ -1,20 +1,15 @@
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@root/components/haptics'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import removeInstance from '@utils/slices/instances/remove'
|
||||
import { getInstance } from '@utils/slices/instancesSlice'
|
||||
import haptics from '@components/haptics'
|
||||
import { removeAccount, useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert } from 'react-native'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const Logout: React.FC = () => {
|
||||
const { t } = useTranslation(['common', 'screenTabs'])
|
||||
const dispatch = useAppDispatch()
|
||||
const queryClient = useQueryClient()
|
||||
const instance = useSelector(getInstance)
|
||||
|
||||
const [accountActive] = useGlobalStorage.string('account.active')
|
||||
|
||||
return (
|
||||
<Button
|
||||
@ -35,10 +30,9 @@ const Logout: React.FC = () => {
|
||||
text: t('screenTabs:me.root.logout.alert.buttons.logout'),
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
if (instance) {
|
||||
haptics('Success')
|
||||
queryClient.clear()
|
||||
dispatch(removeInstance(instance))
|
||||
if (accountActive) {
|
||||
haptics('Light')
|
||||
removeAccount(accountActive)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1,17 +1,16 @@
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import browserPackage from '@helpers/browserPackage'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { getInstanceActive, getInstanceUrl } from '@utils/slices/instancesSlice'
|
||||
import browserPackage from '@utils/helpers/browserPackage'
|
||||
import { getAccountStorage, useGlobalStorage } from '@utils/storage/actions'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const Settings: React.FC = () => {
|
||||
const { t } = useTranslation('screenTabs')
|
||||
const navigation = useNavigation<any>()
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
const url = useSelector(getInstanceUrl)
|
||||
|
||||
const [accountActive] = useGlobalStorage.string('account.active')
|
||||
|
||||
return (
|
||||
<MenuContainer>
|
||||
@ -21,14 +20,14 @@ const Settings: React.FC = () => {
|
||||
title={t('me.stacks.settings.name')}
|
||||
onPress={() => navigation.navigate('Tab-Me-Settings')}
|
||||
/>
|
||||
{instanceActive !== -1 ? (
|
||||
{accountActive ? (
|
||||
<MenuRow
|
||||
iconFront='Sliders'
|
||||
iconBack='ExternalLink'
|
||||
title={t('me.stacks.webSettings.name')}
|
||||
onPress={async () =>
|
||||
WebBrowser.openAuthSessionAsync(
|
||||
`https://${url}/settings/preferences`,
|
||||
`https://${getAccountStorage.string('auth.domain')}/settings/preferences`,
|
||||
'tooot://tooot',
|
||||
{
|
||||
...(await browserPackage()),
|
||||
|
@ -10,16 +10,15 @@ import AccountContext from '@screens/Tabs/Shared/Account/utils/createContext'
|
||||
import accountInitialState from '@screens/Tabs/Shared/Account/utils/initialState'
|
||||
import accountReducer from '@screens/Tabs/Shared/Account/utils/reducer'
|
||||
import { useProfileQuery } from '@utils/queryHooks/profile'
|
||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import React, { useReducer, useRef } from 'react'
|
||||
import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const TabMeRoot: React.FC = () => {
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
const [accountActive] = useGlobalStorage.string('account.active')
|
||||
|
||||
const { data } = useProfileQuery({
|
||||
options: { enabled: instanceActive !== -1, keepPreviousData: false }
|
||||
options: { enabled: !!accountActive, keepPreviousData: false }
|
||||
})
|
||||
|
||||
const scrollRef = useRef<Animated.ScrollView>(null)
|
||||
@ -34,18 +33,18 @@ const TabMeRoot: React.FC = () => {
|
||||
|
||||
return (
|
||||
<AccountContext.Provider value={{ accountState, accountDispatch }}>
|
||||
{instanceActive !== -1 && data ? <AccountNav scrollY={scrollY} account={data} /> : null}
|
||||
{accountActive && data ? <AccountNav scrollY={scrollY} account={data} /> : null}
|
||||
<Animated.ScrollView
|
||||
ref={scrollRef}
|
||||
keyboardShouldPersistTaps='handled'
|
||||
onScroll={onScroll}
|
||||
scrollEventThrottle={16}
|
||||
>
|
||||
{instanceActive !== -1 ? <MyInfo account={data} /> : <ComponentInstance />}
|
||||
{instanceActive !== -1 ? <Collections /> : null}
|
||||
{accountActive ? <MyInfo account={data} /> : <ComponentInstance />}
|
||||
{accountActive ? <Collections /> : null}
|
||||
<Settings />
|
||||
{instanceActive !== -1 ? <AccountInformationSwitch /> : null}
|
||||
{instanceActive !== -1 ? <Logout /> : null}
|
||||
{accountActive ? <AccountInformationSwitch /> : null}
|
||||
{accountActive ? <Logout /> : null}
|
||||
</Animated.ScrollView>
|
||||
</AccountContext.Provider>
|
||||
)
|
@ -1,19 +1,16 @@
|
||||
import { MenuContainer } from '@components/Menu'
|
||||
import CustomText from '@components/Text'
|
||||
import { getInstanceVersion } from '@utils/slices/instancesSlice'
|
||||
import { getAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import Constants from 'expo-constants'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const SettingsAnalytics: React.FC = () => {
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('screenTabs')
|
||||
|
||||
const instanceVersion = useSelector(getInstanceVersion, () => true)
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuContainer>
|
||||
@ -34,7 +31,7 @@ const SettingsAnalytics: React.FC = () => {
|
||||
color: colors.secondary
|
||||
}}
|
||||
>
|
||||
{t('me.settings.instanceVersion', { version: instanceVersion })}
|
||||
{t('me.settings.instanceVersion', { version: getAccountStorage.string('version') })}
|
||||
</CustomText>
|
||||
</MenuContainer>
|
||||
</>
|
||||
|
@ -1,47 +1,34 @@
|
||||
import haptics from '@components/haptics'
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { androidActionSheetStyles } from '@helpers/androidActionSheetStyles'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { LOCALES } from '@root/i18n/locales'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import {
|
||||
changeBrowser,
|
||||
changeTheme,
|
||||
getSettingsTheme,
|
||||
getSettingsBrowser,
|
||||
getSettingsFontsize,
|
||||
getSettingsDarkTheme,
|
||||
changeDarkTheme,
|
||||
getSettingsAutoplayGifv,
|
||||
changeAutoplayGifv
|
||||
} from '@utils/slices/settingsSlice'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as Localization from 'expo-localization'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Linking, Platform } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { mapFontsizeToName } from '../SettingsFontsize'
|
||||
import { LOCALES } from '@i18n/locales'
|
||||
|
||||
const SettingsApp: React.FC = () => {
|
||||
const navigation = useNavigation<any>()
|
||||
const dispatch = useAppDispatch()
|
||||
const { showActionSheetWithOptions } = useActionSheet()
|
||||
const { colors } = useTheme()
|
||||
const { t, i18n } = useTranslation(['common', 'screenTabs'])
|
||||
|
||||
const settingsFontsize = useSelector(getSettingsFontsize)
|
||||
const settingsTheme = useSelector(getSettingsTheme)
|
||||
const settingsDarkTheme = useSelector(getSettingsDarkTheme)
|
||||
const settingsBrowser = useSelector(getSettingsBrowser)
|
||||
const settingsAutoplayGifv = useSelector(getSettingsAutoplayGifv)
|
||||
const [fontSize] = useGlobalStorage.number('app.font_size')
|
||||
const [theme, setTheme] = useGlobalStorage.string('app.theme')
|
||||
const [themeDark, setThemeDark] = useGlobalStorage.string('app.theme.dark')
|
||||
const [browser, setBrowser] = useGlobalStorage.string('app.browser')
|
||||
const [autoplayGifv, setAutoplayGifv] = useGlobalStorage.boolean('app.auto_play_gifv')
|
||||
|
||||
return (
|
||||
<MenuContainer>
|
||||
<MenuRow
|
||||
title={t('screenTabs:me.stacks.fontSize.name')}
|
||||
content={t(`screenTabs:me.fontSize.sizes.${mapFontsizeToName(settingsFontsize)}`)}
|
||||
content={t(`screenTabs:me.fontSize.sizes.${mapFontsizeToName(fontSize || 0)}`)}
|
||||
iconBack='ChevronRight'
|
||||
onPress={() => navigation.navigate('Tab-Me-Settings-Fontsize')}
|
||||
/>
|
||||
@ -50,7 +37,9 @@ const SettingsApp: React.FC = () => {
|
||||
content={
|
||||
// @ts-ignore
|
||||
LOCALES[
|
||||
Platform.OS === 'ios' ? Localization.locale.toLowerCase() : i18n.language.toLowerCase()
|
||||
Platform.OS === 'ios'
|
||||
? Localization.locale.toLowerCase().replace(new RegExp(/.*-.*(-.*)/, 'i'), '')
|
||||
: i18n.language.toLowerCase()
|
||||
]
|
||||
}
|
||||
iconBack='ChevronRight'
|
||||
@ -62,7 +51,7 @@ const SettingsApp: React.FC = () => {
|
||||
/>
|
||||
<MenuRow
|
||||
title={t('screenTabs:me.settings.theme.heading')}
|
||||
content={t(`screenTabs:me.settings.theme.options.${settingsTheme}`)}
|
||||
content={t(`screenTabs:me.settings.theme.options.${theme || 'auto'}`)}
|
||||
iconBack='ChevronRight'
|
||||
onPress={() =>
|
||||
showActionSheetWithOptions(
|
||||
@ -80,16 +69,16 @@ const SettingsApp: React.FC = () => {
|
||||
buttonIndex => {
|
||||
switch (buttonIndex) {
|
||||
case 0:
|
||||
haptics('Success')
|
||||
dispatch(changeTheme('auto'))
|
||||
haptics('Light')
|
||||
setTheme('auto')
|
||||
break
|
||||
case 1:
|
||||
haptics('Success')
|
||||
dispatch(changeTheme('light'))
|
||||
haptics('Light')
|
||||
setTheme('light')
|
||||
break
|
||||
case 2:
|
||||
haptics('Success')
|
||||
dispatch(changeTheme('dark'))
|
||||
haptics('Light')
|
||||
setTheme('dark')
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -98,7 +87,7 @@ const SettingsApp: React.FC = () => {
|
||||
/>
|
||||
<MenuRow
|
||||
title={t('screenTabs:me.settings.darkTheme.heading')}
|
||||
content={t(`screenTabs:me.settings.darkTheme.options.${settingsDarkTheme}`)}
|
||||
content={t(`screenTabs:me.settings.darkTheme.options.${themeDark || 'lighter'}`)}
|
||||
iconBack='ChevronRight'
|
||||
onPress={() =>
|
||||
showActionSheetWithOptions(
|
||||
@ -115,12 +104,12 @@ const SettingsApp: React.FC = () => {
|
||||
buttonIndex => {
|
||||
switch (buttonIndex) {
|
||||
case 0:
|
||||
haptics('Success')
|
||||
dispatch(changeDarkTheme('lighter'))
|
||||
haptics('Light')
|
||||
setThemeDark('lighter')
|
||||
break
|
||||
case 1:
|
||||
haptics('Success')
|
||||
dispatch(changeDarkTheme('darker'))
|
||||
haptics('Light')
|
||||
setThemeDark('darker')
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -129,7 +118,7 @@ const SettingsApp: React.FC = () => {
|
||||
/>
|
||||
<MenuRow
|
||||
title={t('screenTabs:me.settings.browser.heading')}
|
||||
content={t(`screenTabs:me.settings.browser.options.${settingsBrowser}`)}
|
||||
content={t(`screenTabs:me.settings.browser.options.${browser || 'internal'}`)}
|
||||
iconBack='ChevronRight'
|
||||
onPress={() =>
|
||||
showActionSheetWithOptions(
|
||||
@ -146,12 +135,12 @@ const SettingsApp: React.FC = () => {
|
||||
buttonIndex => {
|
||||
switch (buttonIndex) {
|
||||
case 0:
|
||||
haptics('Success')
|
||||
dispatch(changeBrowser('internal'))
|
||||
haptics('Light')
|
||||
setBrowser('internal')
|
||||
break
|
||||
case 1:
|
||||
haptics('Success')
|
||||
dispatch(changeBrowser('external'))
|
||||
haptics('Light')
|
||||
setBrowser('external')
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -160,8 +149,8 @@ const SettingsApp: React.FC = () => {
|
||||
/>
|
||||
<MenuRow
|
||||
title={t('screenTabs:me.settings.autoplayGifv.heading')}
|
||||
switchValue={settingsAutoplayGifv}
|
||||
switchOnValueChange={() => dispatch(changeAutoplayGifv(!settingsAutoplayGifv))}
|
||||
switchValue={autoplayGifv}
|
||||
switchOnValueChange={() => setAutoplayGifv(!autoplayGifv)}
|
||||
/>
|
||||
</MenuContainer>
|
||||
)
|
||||
|
@ -1,64 +1,34 @@
|
||||
import Button from '@components/Button'
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import CustomText from '@components/Text'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { androidActionSheetStyles } from '@helpers/androidActionSheetStyles'
|
||||
import { persistor } from '@root/store'
|
||||
import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import { storage } from '@utils/storage'
|
||||
import { getGlobalStorage, useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as Localization from 'expo-localization'
|
||||
import React from 'react'
|
||||
import { DevSettings } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { MMKV } from 'react-native-mmkv'
|
||||
|
||||
const SettingsDev: React.FC = () => {
|
||||
const { colors } = useTheme()
|
||||
const { showActionSheetWithOptions } = useActionSheet()
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
const instances = useSelector(getInstances, () => true)
|
||||
|
||||
const [accounts] = useGlobalStorage.object('accounts')
|
||||
const [account] = useGlobalStorage.string('account.active')
|
||||
|
||||
return (
|
||||
<MenuContainer>
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
selectable
|
||||
style={{
|
||||
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||
color: colors.primaryDefault
|
||||
}}
|
||||
>
|
||||
{JSON.stringify(Localization.locales)}
|
||||
</CustomText>
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
selectable
|
||||
style={{
|
||||
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||
color: colors.primaryDefault
|
||||
}}
|
||||
>
|
||||
{instances[instanceActive]?.token}
|
||||
</CustomText>
|
||||
<MenuRow
|
||||
title={'Local active index'}
|
||||
content={typeof instanceActive + ' - ' + instanceActive}
|
||||
onPress={() => {}}
|
||||
/>
|
||||
<MenuRow title='Active account' content={account || '-'} onPress={() => {}} />
|
||||
<MenuRow
|
||||
title={'Saved local instances'}
|
||||
content={instances.length.toString()}
|
||||
content={accounts?.length.toString()}
|
||||
iconBack='ChevronRight'
|
||||
onPress={() =>
|
||||
showActionSheetWithOptions(
|
||||
{
|
||||
options: instances
|
||||
.map(instance => {
|
||||
return instance.url + ': ' + instance.account.id
|
||||
})
|
||||
.concat(['Cancel']),
|
||||
cancelButtonIndex: instances.length,
|
||||
options: (accounts || []).concat(['Cancel']),
|
||||
cancelButtonIndex: accounts?.length,
|
||||
...androidActionSheetStyles(colors)
|
||||
},
|
||||
() => {}
|
||||
@ -76,14 +46,24 @@ const SettingsDev: React.FC = () => {
|
||||
/>
|
||||
<Button
|
||||
type='text'
|
||||
content={'Purge secure storage'}
|
||||
content={'Purge MMKV'}
|
||||
style={{
|
||||
marginHorizontal: StyleConstants.Spacing.Global.PagePadding * 2,
|
||||
marginBottom: StyleConstants.Spacing.Global.PagePadding
|
||||
}}
|
||||
destructive
|
||||
onPress={() => {
|
||||
persistor.purge().then(() => DevSettings.reload())
|
||||
const accounts = getGlobalStorage.object('accounts')
|
||||
if (!accounts) return
|
||||
|
||||
for (const account of accounts) {
|
||||
console.log('Clearing', account)
|
||||
const temp = new MMKV({ id: account })
|
||||
temp.clearAll()
|
||||
}
|
||||
|
||||
console.log('Clearing', 'global')
|
||||
storage.global.clearAll()
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
|
@ -1,27 +1,24 @@
|
||||
import Icon from '@components/Icon'
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import browserPackage from '@utils/helpers/browserPackage'
|
||||
import { getAccountStorage, useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import Constants from 'expo-constants'
|
||||
import * as Linking from 'expo-linking'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { getInstanceActive, getInstanceVersion } from '@utils/slices/instancesSlice'
|
||||
import { Platform } from 'react-native'
|
||||
import Constants from 'expo-constants'
|
||||
import { getExpoToken } from '@utils/slices/appSlice'
|
||||
import browserPackage from '@helpers/browserPackage'
|
||||
|
||||
const SettingsTooot: React.FC = () => {
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
const navigation = useNavigation<any>()
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('screenTabs')
|
||||
|
||||
const instanceVersion = useSelector(getInstanceVersion, () => true)
|
||||
const expoToken = useSelector(getExpoToken)
|
||||
const [accountActive] = useGlobalStorage.string('account.active')
|
||||
const [expoToken] = useGlobalStorage.string('app.expo_token')
|
||||
|
||||
return (
|
||||
<MenuContainer>
|
||||
@ -44,7 +41,7 @@ const SettingsTooot: React.FC = () => {
|
||||
content={<Icon name='Mail' size={StyleConstants.Font.Size.M} color={colors.secondary} />}
|
||||
iconBack='ChevronRight'
|
||||
onPress={async () => {
|
||||
if (instanceActive !== -1) {
|
||||
if (accountActive) {
|
||||
navigation.navigate('Screen-Compose', {
|
||||
type: 'conversation',
|
||||
accts: ['tooot@xmflsct.com'],
|
||||
@ -55,9 +52,9 @@ const SettingsTooot: React.FC = () => {
|
||||
' - ' +
|
||||
(Constants.expoConfig?.version ? `t/${Constants.expoConfig?.version}` : '') +
|
||||
' - ' +
|
||||
(instanceVersion ? `m/${instanceVersion}` : '') +
|
||||
`m/${getAccountStorage.string('version')}` +
|
||||
' - ' +
|
||||
(expoToken
|
||||
(expoToken?.length
|
||||
? `e/${expoToken.replace(/^ExponentPushToken\[/, '').replace(/\]$/, '')}`
|
||||
: '') +
|
||||
']'
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { isDevelopment } from '@utils/checkEnvironment'
|
||||
import { isDevelopment } from '@utils/helpers/checkEnvironment'
|
||||
import React from 'react'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
import SettingsAnalytics from './Settings/Analytics'
|
||||
import SettingsApp from './Settings/App'
|
||||
import SettingsDev from './Settings/Dev'
|
||||
import SettingsTooot from './Settings/Tooot'
|
||||
import SettingsAnalytics from './Analytics'
|
||||
import SettingsApp from './App'
|
||||
import SettingsDev from './Dev'
|
||||
import SettingsTooot from './Tooot'
|
||||
|
||||
const TabMeSettings: React.FC = () => {
|
||||
return (
|
@ -3,20 +3,18 @@ import haptics from '@components/haptics'
|
||||
import ComponentSeparator from '@components/Separator'
|
||||
import CustomText from '@components/Text'
|
||||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import { SettingsLatest } from '@utils/migrations/settings/migration'
|
||||
import { TabMeStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { changeFontsize, getSettingsFontsize } from '@utils/slices/settingsSlice'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StorageGlobal } from '@utils/storage/global'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { adaptiveScale } from '@utils/styles/scaling'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useMemo } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
export const mapFontsizeToName = (size: SettingsLatest['fontsize']) => {
|
||||
export const mapFontsizeToName = (size: StorageGlobal['app.font_size']) => {
|
||||
switch (size) {
|
||||
case -1:
|
||||
return 'S'
|
||||
@ -28,14 +26,16 @@ export const mapFontsizeToName = (size: SettingsLatest['fontsize']) => {
|
||||
return 'XL'
|
||||
case 3:
|
||||
return 'XXL'
|
||||
default:
|
||||
return 'M'
|
||||
}
|
||||
}
|
||||
|
||||
const TabMeSettingsFontsize: React.FC<TabMeStackScreenProps<'Tab-Me-Settings-Fontsize'>> = () => {
|
||||
const { colors, theme } = useTheme()
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('screenTabs')
|
||||
const initialSize = useSelector(getSettingsFontsize)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const [fontSize, setFontSize] = useGlobalStorage.number('app.font_size')
|
||||
|
||||
const item = {
|
||||
id: 'demo',
|
||||
@ -69,31 +69,6 @@ const TabMeSettingsFontsize: React.FC<TabMeStackScreenProps<'Tab-Me-Settings-Fon
|
||||
mentions: []
|
||||
}
|
||||
|
||||
const sizesDemo = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
{([-1, 0, 1, 2, 3] as [-1, 0, 1, 2, 3]).map(size => (
|
||||
<CustomText
|
||||
key={size}
|
||||
style={{
|
||||
marginHorizontal: StyleConstants.Spacing.XS,
|
||||
paddingHorizontal: StyleConstants.Spacing.XS,
|
||||
marginBottom: StyleConstants.Spacing.M,
|
||||
fontSize: adaptiveScale(StyleConstants.Font.Size.M, size),
|
||||
lineHeight: adaptiveScale(StyleConstants.Font.LineHeight.M, size),
|
||||
color: initialSize === size ? colors.primaryDefault : colors.secondary,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: colors.border
|
||||
}}
|
||||
fontWeight={initialSize === size ? 'Bold' : undefined}
|
||||
>
|
||||
{t(`me.fontSize.sizes.${mapFontsizeToName(size)}`)}
|
||||
</CustomText>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}, [theme, initialSize])
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
<View
|
||||
@ -104,7 +79,24 @@ const TabMeSettingsFontsize: React.FC<TabMeStackScreenProps<'Tab-Me-Settings-Fon
|
||||
marginTop: StyleConstants.Spacing.M
|
||||
}}
|
||||
>
|
||||
{sizesDemo}
|
||||
{([-1, 0, 1, 2, 3] as [-1, 0, 1, 2, 3]).map(size => (
|
||||
<CustomText
|
||||
key={size}
|
||||
style={{
|
||||
marginHorizontal: StyleConstants.Spacing.XS,
|
||||
paddingHorizontal: StyleConstants.Spacing.XS,
|
||||
marginBottom: StyleConstants.Spacing.M,
|
||||
fontSize: adaptiveScale(StyleConstants.Font.Size.M, size),
|
||||
lineHeight: adaptiveScale(StyleConstants.Font.LineHeight.M, size),
|
||||
color: fontSize === size ? colors.primaryDefault : colors.secondary,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: colors.border
|
||||
}}
|
||||
fontWeight={fontSize === size ? 'Bold' : undefined}
|
||||
>
|
||||
{t(`me.fontSize.sizes.${mapFontsizeToName(size)}`)}
|
||||
</CustomText>
|
||||
))}
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
@ -115,38 +107,34 @@ const TabMeSettingsFontsize: React.FC<TabMeStackScreenProps<'Tab-Me-Settings-Fon
|
||||
>
|
||||
<Button
|
||||
onPress={() => {
|
||||
if (initialSize > -1) {
|
||||
if (fontSize && fontSize > -1) {
|
||||
haptics('Light')
|
||||
// @ts-ignore
|
||||
dispatch(changeFontsize(initialSize - 1))
|
||||
setFontSize(fontSize - 1)
|
||||
}
|
||||
}}
|
||||
type='icon'
|
||||
content='Minus'
|
||||
round
|
||||
disabled={initialSize <= -1}
|
||||
disabled={(fontSize || 0) <= -1}
|
||||
style={{ marginHorizontal: StyleConstants.Spacing.S }}
|
||||
/>
|
||||
<Button
|
||||
onPress={() => {
|
||||
if (initialSize < 3) {
|
||||
if (fontSize && fontSize < 3) {
|
||||
haptics('Light')
|
||||
// @ts-ignore
|
||||
dispatch(changeFontsize(initialSize + 1))
|
||||
setFontSize(fontSize + 1)
|
||||
}
|
||||
}}
|
||||
type='icon'
|
||||
content='Plus'
|
||||
round
|
||||
disabled={initialSize >= 3}
|
||||
disabled={(fontSize || 0) >= 3}
|
||||
style={{ marginHorizontal: StyleConstants.Spacing.S }}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
marginVertical: StyleConstants.Spacing.L
|
||||
}}
|
||||
>
|
||||
<View style={{ marginVertical: StyleConstants.Spacing.L }}>
|
||||
<ComponentSeparator
|
||||
extraMarginLeft={-StyleConstants.Spacing.Global.PagePadding}
|
||||
extraMarginRight={-StyleConstants.Spacing.Global.PagePadding}
|
||||
|
@ -1,33 +1,32 @@
|
||||
import haptics from '@components/haptics'
|
||||
import { MenuRow } from '@components/Menu'
|
||||
import { LOCALES } from '@root/i18n/locales'
|
||||
import { LOCALES } from '@i18n/locales'
|
||||
import { TabMeStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { setChannels } from '@utils/slices/instances/push/utils'
|
||||
import { getInstances } from '@utils/slices/instancesSlice'
|
||||
import { changeLanguage } from '@utils/slices/settingsSlice'
|
||||
import { setChannels } from '@utils/push/constants'
|
||||
import { getGlobalStorage, useGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
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 } = useTranslation('screenTabs')
|
||||
const languages = Object.entries(LOCALES)
|
||||
const instances = useSelector(getInstances)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const [_, setLanguage] = useGlobalStorage.string('app.language')
|
||||
|
||||
const change = (lang: string) => {
|
||||
haptics('Success')
|
||||
|
||||
dispatch(changeLanguage(lang))
|
||||
setLanguage(lang)
|
||||
i18n.changeLanguage(lang)
|
||||
|
||||
// Update Android notification channel language
|
||||
if (Platform.OS === 'android') {
|
||||
instances.forEach(instance => setChannels(instance, true))
|
||||
const accounts = getGlobalStorage.object('accounts')
|
||||
accounts?.forEach(account => setChannels(true, account))
|
||||
}
|
||||
|
||||
navigation.pop(1)
|
||||
|
@ -1,27 +1,23 @@
|
||||
import AccountButton from '@components/AccountButton'
|
||||
import ComponentInstance from '@components/Instance'
|
||||
import CustomText from '@components/Text'
|
||||
import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice'
|
||||
import { getGlobalStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { KeyboardAvoidingView, Platform, StyleSheet, View } from 'react-native'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const TabMeSwitch: React.FC = () => {
|
||||
const { t } = useTranslation('screenTabs')
|
||||
const { colors } = useTheme()
|
||||
const instances = useSelector(getInstances, () => true)
|
||||
const instanceActive = useSelector(getInstanceActive, () => true)
|
||||
const accounts = getGlobalStorage.object('accounts')
|
||||
const accountActive = getGlobalStorage.string('account.active')
|
||||
|
||||
const scrollViewRef = useRef<ScrollView>(null)
|
||||
useEffect(() => {
|
||||
setTimeout(
|
||||
() => scrollViewRef.current?.scrollToEnd({ animated: true }),
|
||||
150
|
||||
)
|
||||
setTimeout(() => scrollViewRef.current?.scrollToEnd({ animated: true }), 150)
|
||||
}, [scrollViewRef.current])
|
||||
|
||||
return (
|
||||
@ -45,11 +41,7 @@ const TabMeSwitch: React.FC = () => {
|
||||
>
|
||||
{t('me.switch.new')}
|
||||
</CustomText>
|
||||
<ComponentInstance
|
||||
scrollViewRef={scrollViewRef}
|
||||
disableHeaderImage
|
||||
goBack
|
||||
/>
|
||||
<ComponentInstance scrollViewRef={scrollViewRef} disableHeaderImage goBack />
|
||||
</View>
|
||||
|
||||
<View
|
||||
@ -79,29 +71,19 @@ const TabMeSwitch: React.FC = () => {
|
||||
marginTop: StyleConstants.Spacing.M
|
||||
}}
|
||||
>
|
||||
{instances.length
|
||||
? instances
|
||||
{accounts &&
|
||||
accounts
|
||||
.slice()
|
||||
.sort((a, b) =>
|
||||
`${a.uri}${a.account.acct}`.localeCompare(
|
||||
`${b.uri}${b.account.acct}`
|
||||
)
|
||||
)
|
||||
.map((instance, index) => {
|
||||
const localAccount = instances[instanceActive!]
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.map((account, index) => {
|
||||
return (
|
||||
<AccountButton
|
||||
key={index}
|
||||
instance={instance}
|
||||
selected={
|
||||
instance.url === localAccount.url &&
|
||||
instance.token === localAccount.token &&
|
||||
instance.account.id === localAccount.account.id
|
||||
}
|
||||
account={account}
|
||||
selected={account === accountActive}
|
||||
/>
|
||||
)
|
||||
})
|
||||
: null}
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
@ -3,6 +3,7 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import { TabMeStackParamList } from '@utils/navigation/navigators'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import TabShared from '../Shared'
|
||||
import TabMeBookmarks from './Bookmarks'
|
||||
import TabMeConversations from './Cconversations'
|
||||
import TabMeFavourites from './Favourites'
|
||||
@ -18,7 +19,6 @@ import TabMeSettings from './Settings'
|
||||
import TabMeSettingsFontsize from './SettingsFontsize'
|
||||
import TabMeSettingsLanguage from './SettingsLanguage'
|
||||
import TabMeSwitch from './Switch'
|
||||
import TabShared from '../Shared'
|
||||
|
||||
const Stack = createNativeStackNavigator<TabMeStackParamList>()
|
||||
|
||||
|
@ -1,32 +1,22 @@
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { TabNotificationsStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { useProfileQuery } from '@utils/queryHooks/profile'
|
||||
import { PUSH_ADMIN, PUSH_DEFAULT } from '@utils/push/constants'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { PUSH_ADMIN, PUSH_DEFAULT, usePushFeatures } from '@utils/slices/instances/push/utils'
|
||||
import {
|
||||
getInstanceNotificationsFilter,
|
||||
updateInstanceNotificationsFilter
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import { setAccountStorage, useAccountStorage } from '@utils/storage/actions'
|
||||
import { isEqual } from 'lodash'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert } from 'react-native'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const TabNotificationsFilters: React.FC<
|
||||
TabNotificationsStackScreenProps<'Tab-Notifications-Filters'>
|
||||
> = ({ navigation }) => {
|
||||
const { t } = useTranslation(['common', 'screenTabs'])
|
||||
|
||||
const pushFeatures = usePushFeatures()
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const instanceNotificationsFilter = useSelector(getInstanceNotificationsFilter)
|
||||
const [instanceNotificationsFilter] = useAccountStorage.object('notifications')
|
||||
const [filters, setFilters] = useState(instanceNotificationsFilter)
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
@ -62,7 +52,7 @@ const TabNotificationsFilters: React.FC<
|
||||
content={t('common:buttons.apply')}
|
||||
onPress={() => {
|
||||
if (changed) {
|
||||
dispatch(updateInstanceNotificationsFilter(filters))
|
||||
setAccountStorage([{ key: 'notifications', value: filters }])
|
||||
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Notifications' }]
|
||||
queryClient.invalidateQueries({ queryKey })
|
||||
}
|
||||
@ -73,12 +63,10 @@ const TabNotificationsFilters: React.FC<
|
||||
})
|
||||
}, [filters])
|
||||
|
||||
const profileQuery = useProfileQuery()
|
||||
|
||||
return (
|
||||
<ScrollView style={{ flex: 1 }}>
|
||||
<MenuContainer>
|
||||
{PUSH_DEFAULT(pushFeatures).map((type, index) => (
|
||||
{PUSH_DEFAULT.map((type, index) => (
|
||||
<MenuRow
|
||||
key={index}
|
||||
title={t(`screenTabs:me.push.${type}.heading`)}
|
||||
@ -86,7 +74,7 @@ const TabNotificationsFilters: React.FC<
|
||||
switchOnValueChange={() => setFilters({ ...filters, [type]: !filters[type] })}
|
||||
/>
|
||||
))}
|
||||
{PUSH_ADMIN(pushFeatures, profileQuery.data?.role?.permissions).map(({ type }) => (
|
||||
{PUSH_ADMIN.map(({ type }) => (
|
||||
<MenuRow
|
||||
key={type}
|
||||
title={t(`screenTabs:me.push.${type}.heading`)}
|
||||
|
@ -3,18 +3,16 @@ import Timeline from '@components/Timeline'
|
||||
import TimelineDefault from '@components/Timeline/Default'
|
||||
import SegmentedControl from '@react-native-community/segmented-control'
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack'
|
||||
import { useAppDispatch } from '@root/store'
|
||||
import { ContextsLatest } from '@utils/migrations/contexts/migration'
|
||||
import { TabPublicStackParamList } from '@utils/navigation/navigators'
|
||||
import usePopToTop from '@utils/navigation/usePopToTop'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { getPreviousSegment, updatePreviousSegment } from '@utils/slices/contextsSlice'
|
||||
import { getGlobalStorage, setGlobalStorage } from '@utils/storage/actions'
|
||||
import { StorageGlobal } from '@utils/storage/global'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Dimensions } from 'react-native'
|
||||
import { SceneMap, TabView } from 'react-native-tab-view'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const Route = ({ route: { key: page } }: { route: any }) => {
|
||||
const queryKey: QueryKeyTimeline = ['Timeline', { page }]
|
||||
@ -41,9 +39,8 @@ const Root: React.FC<NativeStackScreenProps<TabPublicStackParamList, 'Tab-Public
|
||||
const { mode } = useTheme()
|
||||
const { t } = useTranslation('screenTabs')
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
const previousSegment = useSelector(getPreviousSegment, () => true)
|
||||
const segments: ContextsLatest['previousSegment'][] = ['Local', 'LocalPublic', 'Trending']
|
||||
const previousSegment = getGlobalStorage.string('app.prev_public_segment')
|
||||
const segments: StorageGlobal['app.prev_public_segment'][] = ['Local', 'LocalPublic', 'Trending']
|
||||
const [segment, setSegment] = useState<number>(
|
||||
segments.findIndex(segment => segment === previousSegment)
|
||||
)
|
||||
@ -62,7 +59,7 @@ const Root: React.FC<NativeStackScreenProps<TabPublicStackParamList, 'Tab-Public
|
||||
selectedIndex={segment}
|
||||
onChange={({ nativeEvent }) => {
|
||||
setSegment(nativeEvent.selectedSegmentIndex)
|
||||
dispatch(updatePreviousSegment(segments[nativeEvent.selectedSegmentIndex]))
|
||||
setGlobalStorage('app.prev_public_segment', segments[nativeEvent.selectedSegmentIndex])
|
||||
}}
|
||||
style={{ flexBasis: '65%' }}
|
||||
/>
|
||||
|
@ -1,10 +1,9 @@
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import navigationRef from '@helpers/navigationRef'
|
||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||
import navigationRef from '@utils/navigation/navigationRef'
|
||||
import { useGlobalStorage } from '@utils/storage/actions'
|
||||
import React from 'react'
|
||||
import { Dimensions, Image } from 'react-native'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
export interface Props {
|
||||
account?: Mastodon.Account
|
||||
@ -13,7 +12,7 @@ export interface Props {
|
||||
const AccountHeader: React.FC<Props> = ({ account }) => {
|
||||
const topInset = useSafeAreaInsets().top
|
||||
|
||||
useSelector(getInstanceActive)
|
||||
useGlobalStorage.string('account.active')
|
||||
|
||||
return (
|
||||
<GracefullyImage
|
||||
|
@ -3,7 +3,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { Placeholder, Fade } from 'rn-placeholder'
|
||||
import { Fade, Placeholder } from 'rn-placeholder'
|
||||
import AccountInformationAccount from './Information/Account'
|
||||
import AccountInformationActions from './Information/Actions'
|
||||
import AccountInformationAvatar from './Information/Avatar'
|
||||
@ -37,7 +37,7 @@ const AccountInformation: React.FC<Props> = ({ account }) => {
|
||||
|
||||
<AccountInformationName account={account} />
|
||||
|
||||
<AccountInformationAccount account={account} />
|
||||
<AccountInformationAccount account={account} myInfo={myInfo} />
|
||||
|
||||
<AccountInformationFields account={account} myInfo={myInfo} />
|
||||
|
||||
|
@ -1,35 +1,34 @@
|
||||
import Icon from '@components/Icon'
|
||||
import CustomText from '@components/Text'
|
||||
import { useRelationshipQuery } from '@utils/queryHooks/relationship'
|
||||
import { getInstanceAccount, getInstanceUri } from '@utils/slices/instancesSlice'
|
||||
import { getAccountStorage, useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { PlaceholderLine } from 'rn-placeholder'
|
||||
|
||||
export interface Props {
|
||||
account: Mastodon.Account | undefined
|
||||
myInfo?: boolean
|
||||
}
|
||||
|
||||
const AccountInformationAccount: React.FC<Props> = ({ account }) => {
|
||||
const AccountInformationAccount: React.FC<Props> = ({ account, myInfo }) => {
|
||||
const { t } = useTranslation('screenTabs')
|
||||
const { colors } = useTheme()
|
||||
const instanceAccount = useSelector(getInstanceAccount, (prev, next) => prev?.acct === next?.acct)
|
||||
const instanceUri = useSelector(getInstanceUri)
|
||||
|
||||
const [acct] = useAccountStorage.string('auth.account.acct')
|
||||
const domain = getAccountStorage.string('auth.domain')
|
||||
|
||||
const { data: relationship } = useRelationshipQuery({
|
||||
id: account?.id || '',
|
||||
options: { enabled: account !== undefined }
|
||||
})
|
||||
|
||||
const localInstance = account?.acct.includes('@')
|
||||
? account?.acct.includes(`@${instanceUri}`)
|
||||
: true
|
||||
const localInstance = account?.acct.includes('@') ? account?.acct.includes(`@${domain}`) : true
|
||||
|
||||
if (account || (localInstance && instanceAccount)) {
|
||||
if (account || localInstance) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
@ -52,8 +51,8 @@ const AccountInformationAccount: React.FC<Props> = ({ account }) => {
|
||||
}}
|
||||
selectable
|
||||
>
|
||||
@{account?.acct}
|
||||
{localInstance ? `@${instanceUri}` : null}
|
||||
@{myInfo ? acct : account?.acct}
|
||||
{localInstance ? `@${domain}` : null}
|
||||
</CustomText>
|
||||
{relationship?.followed_by ? t('shared.account.followed_by') : null}
|
||||
</CustomText>
|
||||
|
@ -3,12 +3,11 @@ import menuAt from '@components/contextMenu/at'
|
||||
import { RelationshipOutgoing } from '@components/Relationship'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { useRelationshipQuery } from '@utils/queryHooks/relationship'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||
|
||||
export interface Props {
|
||||
@ -50,8 +49,8 @@ const AccountInformationActions: React.FC<Props> = ({ account, myInfo }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const instanceAccount = useSelector(getInstanceAccount, () => true)
|
||||
const ownAccount = account?.id === instanceAccount?.id && account?.acct === instanceAccount?.acct
|
||||
const [accountId] = useAccountStorage.string('auth.account.id')
|
||||
const ownAccount = account?.id === accountId
|
||||
|
||||
const query = useRelationshipQuery({ id: account.id })
|
||||
const mAt = menuAt({ account })
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user