mirror of
https://github.com/tooot-app/app
synced 2025-03-31 20:00:46 +02:00
Restructure removing remote
This commit is contained in:
parent
9fdf3ab640
commit
45681fc1f5
@ -30,6 +30,9 @@ export default (): ExpoConfig => ({
|
||||
}
|
||||
]
|
||||
},
|
||||
ios: {
|
||||
bundleIdentifier: 'com.xmflsct.app.tooot'
|
||||
},
|
||||
android: {
|
||||
versionCode: 4,
|
||||
package: 'com.xmflsct.app.tooot',
|
||||
|
@ -49,6 +49,9 @@ PODS:
|
||||
- UMCore
|
||||
- UMPermissionsInterface
|
||||
- UMTaskManagerInterface
|
||||
- EXNotifications (0.8.2):
|
||||
- UMCore
|
||||
- UMPermissionsInterface
|
||||
- EXPermissions (10.0.0):
|
||||
- UMCore
|
||||
- UMPermissionsInterface
|
||||
@ -499,6 +502,7 @@ DEPENDENCIES:
|
||||
- EXLinearGradient (from `../node_modules/expo-linear-gradient/ios`)
|
||||
- EXLocalization (from `../node_modules/expo-localization/ios`)
|
||||
- EXLocation (from `../node_modules/expo-location/ios`)
|
||||
- EXNotifications (from `../node_modules/expo-notifications/ios`)
|
||||
- EXPermissions (from `../node_modules/expo-permissions/ios`)
|
||||
- EXRandom (from `../node_modules/expo-random/ios`)
|
||||
- EXScreenCapture (from `../node_modules/expo-screen-capture/ios`)
|
||||
@ -622,6 +626,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/expo-localization/ios"
|
||||
EXLocation:
|
||||
:path: "../node_modules/expo-location/ios"
|
||||
EXNotifications:
|
||||
:path: "../node_modules/expo-notifications/ios"
|
||||
EXPermissions:
|
||||
:path: "../node_modules/expo-permissions/ios"
|
||||
EXRandom:
|
||||
@ -769,6 +775,7 @@ SPEC CHECKSUMS:
|
||||
EXLinearGradient: c803fbd1aa974be038177b1e45524bc35759fe9c
|
||||
EXLocalization: 8b9463c81843da214476b541a27811dd885c9a76
|
||||
EXLocation: d55e2a37f61bcfb4eba9c813b3f4621d896c4c00
|
||||
EXNotifications: fba3319b1555961b99ddd185021b4c7ff978d5dd
|
||||
EXPermissions: 17d4846ad1880f6891c74ae58ca1acb43e47ed47
|
||||
EXRandom: d7e0f3dd64810aabd27d59f8ecffee359177e2c3
|
||||
EXScreenCapture: 5b8447139e56e2b922e93ccdc7c773c103fb44fd
|
||||
|
@ -34,6 +34,7 @@
|
||||
"expo-linear-gradient": "~8.4.0",
|
||||
"expo-linking": "~2.0.1",
|
||||
"expo-localization": "~9.1.0",
|
||||
"expo-notifications": "~0.8.2",
|
||||
"expo-random": "~10.0.0",
|
||||
"expo-screen-capture": "^3.0.0",
|
||||
"expo-secure-store": "~9.3.0",
|
||||
|
13
src/@types/mastodon.d.ts
vendored
13
src/@types/mastodon.d.ts
vendored
@ -343,6 +343,19 @@ declare namespace Mastodon {
|
||||
'reading:expand:spoilers'?: boolean
|
||||
}
|
||||
|
||||
type PushSubscription = {
|
||||
id: string
|
||||
endpoint: string
|
||||
alerts: {
|
||||
follow: boolean
|
||||
favourite: boolean
|
||||
reblog: boolean
|
||||
mention: boolean
|
||||
poll: boolean
|
||||
}
|
||||
server_key: string
|
||||
}
|
||||
|
||||
type Relationship = {
|
||||
id: string
|
||||
following: boolean
|
||||
|
2
src/@types/react-navigation.d.ts
vendored
2
src/@types/react-navigation.d.ts
vendored
@ -117,7 +117,7 @@ declare namespace Nav {
|
||||
title: Mastodon.List['title']
|
||||
}
|
||||
'Tab-Me-Settings': undefined
|
||||
'Tab-Me-Settings-UpdateRemote': undefined
|
||||
'Tab-Me-Settings-Notification': undefined
|
||||
'Tab-Me-Switch': undefined
|
||||
} & TabSharedStackParamList
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import client from '@api/client'
|
||||
import apiInstance from '@api/instance'
|
||||
import { toast, toastConfig } from '@components/toast'
|
||||
import {
|
||||
NavigationContainer,
|
||||
@ -10,10 +10,8 @@ import ScreenCompose from '@screens/Compose'
|
||||
import ScreenImagesViewer from '@screens/ImagesViewer'
|
||||
import ScreenTabs from '@screens/Tabs'
|
||||
import { updatePreviousTab } from '@utils/slices/contextsSlice'
|
||||
import {
|
||||
getLocalActiveIndex,
|
||||
updateLocalAccountPreferences
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences'
|
||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { themes } from '@utils/styles/themes'
|
||||
import * as Analytics from 'expo-firebase-analytics'
|
||||
@ -36,7 +34,7 @@ export const navigationRef = createRef<NavigationContainerRef>()
|
||||
const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
||||
const { t } = useTranslation('common')
|
||||
const dispatch = useDispatch()
|
||||
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
const { mode, theme } = useTheme()
|
||||
enum barStyle {
|
||||
light = 'dark-content',
|
||||
@ -89,10 +87,9 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
||||
|
||||
// On launch check if there is any unread announcements
|
||||
useEffect(() => {
|
||||
localActiveIndex !== null &&
|
||||
client<Mastodon.Announcement[]>({
|
||||
instanceActive !== -1 &&
|
||||
apiInstance<Mastodon.Announcement[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `announcements`
|
||||
})
|
||||
.then(res => {
|
||||
@ -107,8 +104,8 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
||||
|
||||
// Lazily update users's preferences, for e.g. composing default visibility
|
||||
useEffect(() => {
|
||||
if (localActiveIndex !== null) {
|
||||
dispatch(updateLocalAccountPreferences())
|
||||
if (instanceActive !== -1) {
|
||||
dispatch(updateAccountPreferences())
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
87
src/api/general.ts
Normal file
87
src/api/general.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import axios from 'axios'
|
||||
import chalk from 'chalk'
|
||||
|
||||
const ctx = new chalk.Instance({ level: 3 })
|
||||
|
||||
export type Params = {
|
||||
method: 'get' | 'post' | 'put' | 'delete'
|
||||
domain?: string
|
||||
url: string
|
||||
params?: {
|
||||
[key: string]: string | number | boolean
|
||||
}
|
||||
headers?: { [key: string]: string }
|
||||
body?: FormData | Object
|
||||
}
|
||||
|
||||
const apiGeneral = async <T = unknown>({
|
||||
method,
|
||||
domain,
|
||||
url,
|
||||
params,
|
||||
headers,
|
||||
body
|
||||
}: Params): Promise<{ body: T }> => {
|
||||
if (!domain) {
|
||||
return Promise.reject()
|
||||
}
|
||||
|
||||
console.log(
|
||||
ctx.bgGreen.bold(' API general ') +
|
||||
' ' +
|
||||
domain +
|
||||
' ' +
|
||||
method +
|
||||
ctx.green(' -> ') +
|
||||
`/${url}` +
|
||||
(params ? ctx.green(' -> ') : ''),
|
||||
params ? params : ''
|
||||
)
|
||||
|
||||
return axios({
|
||||
timeout: method === 'post' ? 1000 * 60 : 1000 * 15,
|
||||
method,
|
||||
baseURL: `https://${domain}/`,
|
||||
url,
|
||||
params,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...headers
|
||||
},
|
||||
...(body && { data: body })
|
||||
})
|
||||
.then(response => {
|
||||
return Promise.resolve({
|
||||
body: response.data
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.response) {
|
||||
// The request was made and the server responded with a status code
|
||||
// that falls out of the range of 2xx
|
||||
console.error(
|
||||
ctx.bold(' API general '),
|
||||
ctx.bold('response'),
|
||||
error.response.status,
|
||||
error.response.data.error
|
||||
)
|
||||
return Promise.reject(error.response)
|
||||
} else if (error.request) {
|
||||
// The request was made but no response was received
|
||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||
// http.ClientRequest in node.js
|
||||
console.error(ctx.bold(' API general '), ctx.bold('request'), error)
|
||||
return Promise.reject()
|
||||
} else {
|
||||
console.error(
|
||||
ctx.bold(' API general '),
|
||||
ctx.bold('internal'),
|
||||
error.message,
|
||||
url
|
||||
)
|
||||
return Promise.reject()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default apiGeneral
|
@ -5,22 +5,8 @@ import li from 'li'
|
||||
|
||||
const ctx = new chalk.Instance({ level: 3 })
|
||||
|
||||
const client = async <T = unknown>({
|
||||
method,
|
||||
instance,
|
||||
localIndex,
|
||||
instanceDomain,
|
||||
version = 'v1',
|
||||
url,
|
||||
params,
|
||||
headers,
|
||||
body,
|
||||
onUploadProgress
|
||||
}: {
|
||||
export type Params = {
|
||||
method: 'get' | 'post' | 'put' | 'delete'
|
||||
instance: 'local' | 'remote'
|
||||
localIndex?: number
|
||||
instanceDomain?: string
|
||||
version?: 'v1' | 'v2'
|
||||
url: string
|
||||
params?: {
|
||||
@ -29,30 +15,37 @@ const client = async <T = unknown>({
|
||||
headers?: { [key: string]: string }
|
||||
body?: FormData
|
||||
onUploadProgress?: (progressEvent: any) => void
|
||||
}): Promise<{ body: T; links: { prev?: string; next?: string } }> => {
|
||||
const { store } = require('@root/store')
|
||||
const state = (store.getState() as RootState).instances
|
||||
const theLocalIndex =
|
||||
localIndex !== undefined ? localIndex : state.local.activeIndex
|
||||
}
|
||||
|
||||
let domain = null
|
||||
let token = null
|
||||
if (instance === 'remote') {
|
||||
domain = instanceDomain || state.remote.url
|
||||
const apiInstance = async <T = unknown>({
|
||||
method,
|
||||
version = 'v1',
|
||||
url,
|
||||
params,
|
||||
headers,
|
||||
body,
|
||||
onUploadProgress
|
||||
}: Params): Promise<{ body: T; links: { prev?: string; next?: string } }> => {
|
||||
const { store } = require('@root/store')
|
||||
const state = store.getState() as RootState
|
||||
const instanceActive = state.instances.instances.findIndex(
|
||||
instance => instance.active
|
||||
)
|
||||
|
||||
let domain
|
||||
let token
|
||||
if (instanceActive !== -1 && state.instances.instances[instanceActive]) {
|
||||
domain = state.instances.instances[instanceActive].url
|
||||
token = state.instances.instances[instanceActive].token
|
||||
} else {
|
||||
if (theLocalIndex !== null && state.local.instances[theLocalIndex]) {
|
||||
domain = state.local.instances[theLocalIndex].url
|
||||
token = state.local.instances[theLocalIndex].token
|
||||
} else {
|
||||
console.error(
|
||||
ctx.bgRed.white.bold(' API ') + ' ' + 'No instance domain is provided'
|
||||
)
|
||||
return Promise.reject()
|
||||
}
|
||||
console.error(
|
||||
ctx.bgRed.white.bold(' API ') + ' ' + 'No instance domain is provided'
|
||||
)
|
||||
return Promise.reject()
|
||||
}
|
||||
|
||||
console.log(
|
||||
ctx.bgGreen.bold(' API ') +
|
||||
ctx.bgGreen.bold(' API instance ') +
|
||||
' ' +
|
||||
domain +
|
||||
' ' +
|
||||
@ -97,7 +90,7 @@ const client = async <T = unknown>({
|
||||
// The request was made and the server responded with a status code
|
||||
// that falls out of the range of 2xx
|
||||
console.error(
|
||||
ctx.bold(' API '),
|
||||
ctx.bold(' API instance '),
|
||||
ctx.bold('response'),
|
||||
error.response.status,
|
||||
error.response.data.error
|
||||
@ -107,11 +100,11 @@ const client = async <T = unknown>({
|
||||
// The request was made but no response was received
|
||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||
// http.ClientRequest in node.js
|
||||
console.error(ctx.bold(' API '), ctx.bold('request'), error)
|
||||
console.error(ctx.bold(' API instance '), ctx.bold('request'), error)
|
||||
return Promise.reject()
|
||||
} else {
|
||||
console.error(
|
||||
ctx.bold(' API '),
|
||||
ctx.bold(' API instance '),
|
||||
ctx.bold('internal'),
|
||||
error.message,
|
||||
url
|
||||
@ -121,4 +114,4 @@ const client = async <T = unknown>({
|
||||
})
|
||||
}
|
||||
|
||||
export default client
|
||||
export default apiInstance
|
@ -1,7 +1,7 @@
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import {
|
||||
getLocalInstance,
|
||||
updateLocalNotification
|
||||
getInstance,
|
||||
updateInstanceNotification
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useQueryClient } from 'react-query'
|
||||
@ -18,7 +18,7 @@ const useWebsocket = ({
|
||||
const queryClient = useQueryClient()
|
||||
const dispatch = useDispatch()
|
||||
const localInstance = useSelector(
|
||||
getLocalInstance,
|
||||
getInstance,
|
||||
(prev, next) =>
|
||||
prev?.urls.streaming_api === next?.urls.streaming_api &&
|
||||
prev?.token === next?.token
|
||||
@ -39,7 +39,7 @@ const useWebsocket = ({
|
||||
case 'notification':
|
||||
const payload: Mastodon.Notification = JSON.parse(message.payload)
|
||||
dispatch(
|
||||
updateLocalNotification({ latestTime: payload.created_at })
|
||||
updateInstanceNotification({ latestTime: payload.created_at })
|
||||
)
|
||||
const queryKey: QueryKeyTimeline = [
|
||||
'Timeline',
|
||||
|
@ -2,7 +2,7 @@ import Button from '@components/Button'
|
||||
import Icon from '@components/Icon'
|
||||
import { useAppsQuery } from '@utils/queryHooks/apps'
|
||||
import { useInstanceQuery } from '@utils/queryHooks/instance'
|
||||
import { getLocalInstances } from '@utils/slices/instancesSlice'
|
||||
import { getInstances } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
@ -14,7 +14,6 @@ import { useSelector } from 'react-redux'
|
||||
import { Placeholder, Fade } from 'rn-placeholder'
|
||||
import analytics from './analytics'
|
||||
import InstanceAuth from './Instance/Auth'
|
||||
import EULA from './Instance/EULA'
|
||||
import InstanceInfo from './Instance/Info'
|
||||
|
||||
export interface Props {
|
||||
@ -29,26 +28,23 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
const { t } = useTranslation('componentInstance')
|
||||
const { theme } = useTheme()
|
||||
|
||||
const localInstances = useSelector(getLocalInstances, () => true)
|
||||
const [instanceDomain, setInstanceDomain] = useState<string>()
|
||||
const instances = useSelector(getInstances, () => true)
|
||||
const [domain, setDomain] = useState<string>()
|
||||
|
||||
const instanceQuery = useInstanceQuery({
|
||||
instanceDomain,
|
||||
options: { enabled: false, retry: false }
|
||||
domain,
|
||||
options: { enabled: !!domain, retry: false }
|
||||
})
|
||||
const appsQuery = useAppsQuery({
|
||||
instanceDomain,
|
||||
domain,
|
||||
options: { enabled: false, retry: false }
|
||||
})
|
||||
|
||||
const onChangeText = useCallback(
|
||||
debounce(
|
||||
text => {
|
||||
setInstanceDomain(text.replace(/^http(s)?\:\/\//i, ''))
|
||||
setDomain(text.replace(/^http(s)?\:\/\//i, ''))
|
||||
appsQuery.remove()
|
||||
if (text) {
|
||||
instanceQuery.refetch()
|
||||
}
|
||||
},
|
||||
1000,
|
||||
{ trailing: true }
|
||||
@ -57,40 +53,35 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
)
|
||||
|
||||
const processUpdate = useCallback(() => {
|
||||
if (instanceDomain) {
|
||||
analytics('instance_local_login')
|
||||
if (domain) {
|
||||
analytics('instance_login')
|
||||
if (
|
||||
localInstances &&
|
||||
localInstances.filter(instance => instance.url === instanceDomain)
|
||||
.length
|
||||
instances &&
|
||||
instances.filter(instance => instance.url === domain).length
|
||||
) {
|
||||
Alert.alert(
|
||||
t('update.local.alert.title'),
|
||||
t('update.local.alert.message'),
|
||||
[
|
||||
{
|
||||
text: t('update.local.alert.buttons.cancel'),
|
||||
style: 'cancel'
|
||||
},
|
||||
{
|
||||
text: t('update.local.alert.buttons.continue'),
|
||||
onPress: () => {
|
||||
appsQuery.refetch()
|
||||
}
|
||||
Alert.alert(t('update.alert.title'), t('update.alert.message'), [
|
||||
{
|
||||
text: t('update.alert.buttons.cancel'),
|
||||
style: 'cancel'
|
||||
},
|
||||
{
|
||||
text: t('update.alert.buttons.continue'),
|
||||
onPress: () => {
|
||||
appsQuery.refetch()
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
])
|
||||
} else {
|
||||
appsQuery.refetch()
|
||||
}
|
||||
}
|
||||
}, [instanceDomain])
|
||||
}, [domain])
|
||||
|
||||
const onSubmitEditing = useCallback(
|
||||
({ nativeEvent: { text } }) => {
|
||||
analytics('instance_textinput_submit', { match: text === instanceDomain })
|
||||
analytics('instance_textinput_submit', { match: text === domain })
|
||||
if (
|
||||
text === instanceDomain &&
|
||||
text === domain &&
|
||||
instanceQuery.isSuccess &&
|
||||
instanceQuery.data &&
|
||||
instanceQuery.data.uri
|
||||
@ -98,12 +89,12 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
processUpdate()
|
||||
}
|
||||
},
|
||||
[instanceDomain, instanceQuery.isSuccess, instanceQuery.data]
|
||||
[domain, instanceQuery.isSuccess, instanceQuery.data]
|
||||
)
|
||||
|
||||
const requestAuth = useMemo(() => {
|
||||
if (
|
||||
instanceDomain &&
|
||||
domain &&
|
||||
instanceQuery.data?.uri &&
|
||||
appsQuery.data?.client_id &&
|
||||
appsQuery.data.client_secret
|
||||
@ -111,7 +102,7 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
return (
|
||||
<InstanceAuth
|
||||
key={Math.random()}
|
||||
instanceDomain={instanceDomain}
|
||||
instanceDomain={domain}
|
||||
instance={instanceQuery.data}
|
||||
appData={{
|
||||
clientId: appsQuery.data.client_id,
|
||||
@ -121,9 +112,7 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
}, [instanceDomain, instanceQuery.data, appsQuery.data])
|
||||
|
||||
const [agreed, setAgreed] = useState(false)
|
||||
}, [domain, instanceQuery.data, appsQuery.data])
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -160,15 +149,13 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
/>
|
||||
<Button
|
||||
type='text'
|
||||
content={t('server.button.local')}
|
||||
content={t('server.button')}
|
||||
onPress={processUpdate}
|
||||
disabled={!instanceQuery.data?.uri}
|
||||
loading={instanceQuery.isFetching || appsQuery.isFetching}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* <EULA agreed={agreed} setAgreed={setAgreed} /> */}
|
||||
|
||||
<View>
|
||||
<Placeholder
|
||||
{...(instanceQuery.isFetching && {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { InstanceLocal, localAddInstance } from '@utils/slices/instancesSlice'
|
||||
import addInstance from '@utils/slices/instances/add'
|
||||
import { Instance } from '@utils/slices/instancesSlice'
|
||||
import * as AuthSession from 'expo-auth-session'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useQueryClient } from 'react-query'
|
||||
@ -9,7 +10,7 @@ export interface Props {
|
||||
instanceDomain: string
|
||||
// Domain can be different than uri
|
||||
instance: Mastodon.Instance
|
||||
appData: InstanceLocal['appData']
|
||||
appData: Instance['appData']
|
||||
goBack?: boolean
|
||||
}
|
||||
|
||||
@ -62,8 +63,8 @@ const InstanceAuth = React.memo(
|
||||
)
|
||||
queryClient.clear()
|
||||
dispatch(
|
||||
localAddInstance({
|
||||
url: instanceDomain,
|
||||
addInstance({
|
||||
domain: instanceDomain,
|
||||
token: accessToken,
|
||||
instance,
|
||||
max_toot_chars: instance.max_toot_chars,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import ComponentSeparator from '@components/Separator'
|
||||
import { useNavigation, useScrollToTop } from '@react-navigation/native'
|
||||
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import { getLocalActiveIndex } from '@utils/slices/instancesSlice'
|
||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { findIndex } from 'lodash'
|
||||
@ -54,7 +54,7 @@ const Timeline: React.FC<Props> = ({
|
||||
const { theme } = useTheme()
|
||||
|
||||
// Update timeline when account switched
|
||||
useSelector(getLocalActiveIndex)
|
||||
useSelector(getInstanceActive)
|
||||
|
||||
const queryKeyParams = {
|
||||
page,
|
||||
|
@ -1,10 +1,10 @@
|
||||
import client from '@api/client'
|
||||
import apiInstance from '@api/instance'
|
||||
import analytics from '@components/analytics'
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback } from 'react'
|
||||
@ -58,17 +58,16 @@ const TimelineConversation: React.FC<Props> = ({
|
||||
queryKey,
|
||||
highlighted = false
|
||||
}) => {
|
||||
const localAccount = useSelector(
|
||||
getLocalAccount,
|
||||
const instanceAccount = useSelector(
|
||||
getInstanceAccount,
|
||||
(prev, next) => prev?.id === next?.id
|
||||
)
|
||||
const { theme } = useTheme()
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const fireMutation = useCallback(() => {
|
||||
return client<Mastodon.Conversation>({
|
||||
return apiInstance<Mastodon.Conversation>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `conversations/${conversation.id}/read`
|
||||
})
|
||||
}, [])
|
||||
@ -135,7 +134,9 @@ const TimelineConversation: React.FC<Props> = ({
|
||||
statusId={conversation.last_status.id}
|
||||
poll={conversation.last_status.poll}
|
||||
reblog={false}
|
||||
sameAccount={conversation.last_status.id === localAccount?.id}
|
||||
sameAccount={
|
||||
conversation.last_status.id === instanceAccount?.id
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
@ -10,7 +10,7 @@ import TimelinePoll from '@components/Timeline/Shared/Poll'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { uniqBy } from 'lodash'
|
||||
@ -41,8 +41,8 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
pinned
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const localAccount = useSelector(
|
||||
getLocalAccount,
|
||||
const instanceAccount = useSelector(
|
||||
getInstanceAccount,
|
||||
(prev, next) => prev?.id === next?.id
|
||||
)
|
||||
const navigation = useNavigation<
|
||||
@ -118,7 +118,7 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
statusId={actualStatus.id}
|
||||
poll={actualStatus.poll}
|
||||
reblog={item.reblog ? true : false}
|
||||
sameAccount={actualStatus.account.id === localAccount?.id}
|
||||
sameAccount={actualStatus.account.id === instanceAccount?.id}
|
||||
/>
|
||||
) : null}
|
||||
{!disableDetails &&
|
||||
@ -147,7 +147,7 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
([actualStatus.account] as Mastodon.Account[] &
|
||||
Mastodon.Mention[])
|
||||
.concat(actualStatus.mentions)
|
||||
.filter(d => d.id !== localAccount?.id),
|
||||
.filter(d => d.id !== instanceAccount?.id),
|
||||
d => d.id
|
||||
).map(d => d.acct)}
|
||||
reblog={item.reblog ? true : false}
|
||||
|
@ -10,7 +10,7 @@ import TimelinePoll from '@components/Timeline/Shared/Poll'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { uniqBy } from 'lodash'
|
||||
@ -30,8 +30,8 @@ const TimelineNotifications: React.FC<Props> = ({
|
||||
highlighted = false
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const localAccount = useSelector(
|
||||
getLocalAccount,
|
||||
const instanceAccount = useSelector(
|
||||
getInstanceAccount,
|
||||
(prev, next) => prev?.id === next?.id
|
||||
)
|
||||
const navigation = useNavigation<
|
||||
@ -103,7 +103,7 @@ const TimelineNotifications: React.FC<Props> = ({
|
||||
statusId={notification.status.id}
|
||||
poll={notification.status.poll}
|
||||
reblog={false}
|
||||
sameAccount={notification.account.id === localAccount?.id}
|
||||
sameAccount={notification.account.id === instanceAccount?.id}
|
||||
/>
|
||||
)}
|
||||
{notification.status.media_attachments.length > 0 && (
|
||||
@ -131,7 +131,7 @@ const TimelineNotifications: React.FC<Props> = ({
|
||||
([notification.status.account] as Mastodon.Account[] &
|
||||
Mastodon.Mention[])
|
||||
.concat(notification.status.mentions)
|
||||
.filter(d => d.id !== localAccount?.id),
|
||||
.filter(d => d.id !== instanceAccount?.id),
|
||||
d => d.id
|
||||
).map(d => d.acct)}
|
||||
reblog={false}
|
||||
|
@ -3,10 +3,7 @@ export default {
|
||||
textInput: { placeholder: "Instance' domain" },
|
||||
privateInstance: 'Private instance, peeping not allowed',
|
||||
EULA: { base: 'I have read and agreed to ', EULA: 'EULA' },
|
||||
button: {
|
||||
local: 'Login',
|
||||
remote: 'Peep'
|
||||
},
|
||||
button: 'Login',
|
||||
information: {
|
||||
name: 'Name',
|
||||
description: { heading: 'Description', expandHint: 'description' },
|
||||
@ -21,19 +18,14 @@ export default {
|
||||
}
|
||||
},
|
||||
update: {
|
||||
local: {
|
||||
alert: {
|
||||
title: 'Logged in to this instance',
|
||||
message:
|
||||
'You can login to another account, keeping existing logged in account',
|
||||
buttons: {
|
||||
cancel: '$t(common:buttons.cancel)',
|
||||
continue: 'Continue'
|
||||
}
|
||||
alert: {
|
||||
title: 'Logged in to this instance',
|
||||
message:
|
||||
'You can login to another account, keeping existing logged in account',
|
||||
buttons: {
|
||||
cancel: '$t(common:buttons.cancel)',
|
||||
continue: 'Continue'
|
||||
}
|
||||
},
|
||||
remote: {
|
||||
succeed: 'Register peeping succeed'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,10 +26,6 @@ export default {
|
||||
cancel: '$t(common:buttons.cancel)'
|
||||
}
|
||||
},
|
||||
remote: {
|
||||
heading: '$t(meSettingsUpdateRemote:heading)',
|
||||
description: 'External instance can only be read'
|
||||
},
|
||||
cache: {
|
||||
heading: 'Clear cache',
|
||||
empty: 'Cache empty'
|
||||
|
@ -23,8 +23,7 @@ i18n.use(initReactI18next).init({
|
||||
},
|
||||
react: {
|
||||
useSuspense: false
|
||||
},
|
||||
debug: true
|
||||
}
|
||||
})
|
||||
|
||||
export default i18n
|
||||
|
@ -14,6 +14,7 @@ export default {
|
||||
meLists: require('./screens/meLists').default,
|
||||
meListsList: require('./screens/meListsList').default,
|
||||
meSettings: require('./screens/meSettings').default,
|
||||
meSettingsNotification: require('./screens/meSettingsNotification').default,
|
||||
meSwitch: require('./screens/meSwitch').default,
|
||||
|
||||
sharedAccount: require('./screens/sharedAccount').default,
|
||||
|
@ -3,10 +3,7 @@ export default {
|
||||
textInput: { placeholder: '输入社区服务器地址' },
|
||||
privateInstance: '非公开社区, 不能围观',
|
||||
EULA: { base: '我阅读并同意 ', EULA: '最终用户条款' },
|
||||
button: {
|
||||
local: '登录',
|
||||
remote: '围观'
|
||||
},
|
||||
button: '登录',
|
||||
information: {
|
||||
name: '社区名称',
|
||||
description: { heading: '社区简介', expandHint: '简介' },
|
||||
@ -21,18 +18,13 @@ export default {
|
||||
}
|
||||
},
|
||||
update: {
|
||||
local: {
|
||||
alert: {
|
||||
title: '此社区已登录',
|
||||
message: '你可以登录同个社区的另一个账号,不影响已登录的账号',
|
||||
buttons: {
|
||||
cancel: '$t(common:buttons.cancel)',
|
||||
continue: '继续'
|
||||
}
|
||||
alert: {
|
||||
title: '此社区已登录',
|
||||
message: '你可以登录同个社区的另一个账号,不影响已登录的账号',
|
||||
buttons: {
|
||||
cancel: '$t(common:buttons.cancel)',
|
||||
continue: '继续'
|
||||
}
|
||||
},
|
||||
remote: {
|
||||
succeed: '围观登记成功'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
export default {
|
||||
heading: '设置',
|
||||
content: {
|
||||
notification: {
|
||||
heading: '$t(meSettingsNotification:heading)'
|
||||
},
|
||||
language: {
|
||||
heading: '切换语言',
|
||||
options: {
|
||||
@ -26,10 +29,6 @@ export default {
|
||||
cancel: '$t(common:buttons.cancel)'
|
||||
}
|
||||
},
|
||||
remote: {
|
||||
heading: '$t(meSettingsUpdateRemote:heading)',
|
||||
description: '外站只能浏览不能玩'
|
||||
},
|
||||
cache: {
|
||||
heading: '清空缓存',
|
||||
empty: '暂无缓存'
|
||||
|
24
src/i18n/zh-Hans/screens/meSettingsNotification.ts
Normal file
24
src/i18n/zh-Hans/screens/meSettingsNotification.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export default {
|
||||
heading: '通知',
|
||||
content: {
|
||||
global: {
|
||||
heading: '启用通知',
|
||||
description: 'blahblahblah'
|
||||
},
|
||||
follow: {
|
||||
heading: '新关注者'
|
||||
},
|
||||
favourite: {
|
||||
heading: '嘟文被喜欢'
|
||||
},
|
||||
reblog: {
|
||||
heading: '嘟文被转嘟'
|
||||
},
|
||||
mention: {
|
||||
heading: '提及你'
|
||||
},
|
||||
poll: {
|
||||
heading: '投票'
|
||||
}
|
||||
}
|
||||
}
|
@ -7,10 +7,10 @@ import ComposeRoot from '@screens/Compose/Root'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { updateStoreReview } from '@utils/slices/contextsSlice'
|
||||
import {
|
||||
getLocalAccount,
|
||||
getLocalMaxTootChar,
|
||||
removeLocalDraft,
|
||||
updateLocalDraft
|
||||
getInstanceAccount,
|
||||
getInstanceMaxTootChar,
|
||||
removeInstanceDraft,
|
||||
updateInstanceDraft
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
@ -75,7 +75,7 @@ const ScreenCompose: React.FC<ScreenComposeProp> = ({
|
||||
setHasKeyboard(false)
|
||||
}
|
||||
|
||||
const localAccount = useSelector(getLocalAccount, (prev, next) =>
|
||||
const localAccount = useSelector(getInstanceAccount, (prev, next) =>
|
||||
prev?.preferences && next?.preferences
|
||||
? prev?.preferences['posting:default:visibility'] ===
|
||||
next?.preferences['posting:default:visibility']
|
||||
@ -102,7 +102,7 @@ const ScreenCompose: React.FC<ScreenComposeProp> = ({
|
||||
initialReducerState
|
||||
)
|
||||
|
||||
const maxTootChars = useSelector(getLocalMaxTootChar)
|
||||
const maxTootChars = useSelector(getInstanceMaxTootChar, () => true)
|
||||
const totalTextCount =
|
||||
(composeState.spoiler.active ? composeState.spoiler.count : 0) +
|
||||
composeState.text.count
|
||||
@ -158,7 +158,7 @@ const ScreenCompose: React.FC<ScreenComposeProp> = ({
|
||||
|
||||
const saveDraft = () => {
|
||||
dispatch(
|
||||
updateLocalDraft({
|
||||
updateInstanceDraft({
|
||||
timestamp: composeState.timestamp,
|
||||
spoiler: composeState.spoiler.raw,
|
||||
text: composeState.text.raw,
|
||||
@ -171,7 +171,7 @@ const ScreenCompose: React.FC<ScreenComposeProp> = ({
|
||||
)
|
||||
}
|
||||
const removeDraft = useCallback(() => {
|
||||
dispatch(removeLocalDraft(composeState.timestamp))
|
||||
dispatch(removeInstanceDraft(composeState.timestamp))
|
||||
}, [composeState.timestamp])
|
||||
useEffect(() => {
|
||||
const autoSave = composeState.dirty
|
||||
|
@ -1,9 +1,12 @@
|
||||
import client from '@api/client'
|
||||
import apiInstance from '@api/instance'
|
||||
import Icon from '@components/Icon'
|
||||
import ComponentSeparator from '@components/Separator'
|
||||
import HeaderSharedCreated from '@components/Timeline/Shared/HeaderShared/Created'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { getLocalDrafts, removeLocalDraft } from '@utils/slices/instancesSlice'
|
||||
import {
|
||||
getInstanceDrafts,
|
||||
removeInstanceDraft
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useContext, useState } from 'react'
|
||||
@ -34,7 +37,7 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
|
||||
const navigation = useNavigation()
|
||||
const dispatch = useDispatch()
|
||||
const { mode, theme } = useTheme()
|
||||
const localDrafts = useSelector(getLocalDrafts)?.filter(
|
||||
const instanceDrafts = useSelector(getInstanceDrafts)?.filter(
|
||||
draft => draft.timestamp !== timestamp
|
||||
)
|
||||
|
||||
@ -44,7 +47,7 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
|
||||
const [checkingAttachments, setCheckingAttachments] = useState(false)
|
||||
|
||||
const removeDraft = useCallback(ts => {
|
||||
dispatch(removeLocalDraft(ts))
|
||||
dispatch(removeInstanceDraft(ts))
|
||||
}, [])
|
||||
|
||||
const renderItem = useCallback(
|
||||
@ -58,9 +61,8 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
|
||||
let tempUploads: ExtendedAttachment[] = []
|
||||
if (item.attachments && item.attachments.uploads.length) {
|
||||
for (const attachment of item.attachments.uploads) {
|
||||
await client<Mastodon.Attachment>({
|
||||
await apiInstance<Mastodon.Attachment>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `media/${attachment.remote?.id}`
|
||||
})
|
||||
.then(res => {
|
||||
@ -92,7 +94,7 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
|
||||
type: 'loadDraft',
|
||||
payload: tempDraft
|
||||
})
|
||||
dispatch(removeLocalDraft(item.timestamp))
|
||||
dispatch(removeInstanceDraft(item.timestamp))
|
||||
navigation.goBack()
|
||||
}}
|
||||
>
|
||||
@ -156,14 +158,14 @@ const ComposeDraftsListRoot: React.FC<Props> = ({ timestamp }) => {
|
||||
<>
|
||||
<PanGestureHandler enabled={true}>
|
||||
<SwipeListView
|
||||
data={localDrafts}
|
||||
data={instanceDrafts}
|
||||
renderItem={renderItem}
|
||||
renderHiddenItem={renderHiddenItem}
|
||||
disableRightSwipe={true}
|
||||
rightOpenValue={-actionWidth}
|
||||
previewRowKey={
|
||||
localDrafts?.length
|
||||
? localDrafts[0].timestamp.toString()
|
||||
instanceDrafts?.length
|
||||
? instanceDrafts[0].timestamp.toString()
|
||||
: undefined
|
||||
}
|
||||
// previewDuration={350}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import client from '@api/client'
|
||||
import apiInstance from '@api/instance'
|
||||
import analytics from '@components/analytics'
|
||||
import haptics from '@components/haptics'
|
||||
import { HeaderCenter, HeaderLeft, HeaderRight } from '@components/Header'
|
||||
@ -91,9 +91,8 @@ const ComposeEditAttachment: React.FC<ScreenComposeEditAttachmentProp> = ({
|
||||
formData.append('focus', `${focus.value.x},${focus.value.y}`)
|
||||
}
|
||||
|
||||
client<Mastodon.Attachment>({
|
||||
apiInstance<Mastodon.Attachment>({
|
||||
method: 'put',
|
||||
instance: 'local',
|
||||
url: `media/${theAttachment.id}`,
|
||||
body: formData
|
||||
})
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Button from '@components/Button'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { getLocalDrafts } from '@utils/slices/instancesSlice'
|
||||
import { getInstanceDrafts } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import React, { useContext, useEffect } from 'react'
|
||||
@ -13,7 +13,7 @@ const ComposeDrafts: React.FC = () => {
|
||||
const { t } = useTranslation('sharedCompose')
|
||||
const navigation = useNavigation()
|
||||
const { composeState } = useContext(ComposeContext)
|
||||
const localDrafts = useSelector(getLocalDrafts)?.filter(
|
||||
const instanceDrafts = useSelector(getInstanceDrafts)?.filter(
|
||||
draft => draft.timestamp !== composeState.timestamp
|
||||
)
|
||||
|
||||
@ -21,7 +21,7 @@ const ComposeDrafts: React.FC = () => {
|
||||
layoutAnimation()
|
||||
}, [composeState.dirty])
|
||||
|
||||
if (!composeState.dirty && localDrafts?.length) {
|
||||
if (!composeState.dirty && instanceDrafts?.length) {
|
||||
return (
|
||||
<View
|
||||
style={styles.base}
|
||||
@ -29,7 +29,7 @@ const ComposeDrafts: React.FC = () => {
|
||||
<Button
|
||||
type='text'
|
||||
content={t('content.root.drafts', {
|
||||
count: localDrafts.length
|
||||
count: instanceDrafts.length
|
||||
})}
|
||||
onPress={() =>
|
||||
navigation.navigate('Screen-Compose-DraftsList', {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import client from '@api/client'
|
||||
import * as ImagePicker from 'expo-image-picker'
|
||||
import * as Crypto from 'expo-crypto'
|
||||
import { ImageInfo } from 'expo-image-picker/build/ImagePicker.types'
|
||||
@ -9,6 +8,7 @@ import { ComposeAction } from '../../utils/types'
|
||||
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
||||
import i18next from 'i18next'
|
||||
import analytics from '@components/analytics'
|
||||
import apiInstance from '@api/instance'
|
||||
|
||||
export interface Props {
|
||||
composeDispatch: Dispatch<ComposeAction>
|
||||
@ -106,9 +106,8 @@ const addAttachment = async ({
|
||||
type: attachmentType
|
||||
})
|
||||
|
||||
return client<Mastodon.Attachment>({
|
||||
return apiInstance<Mastodon.Attachment>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: 'media',
|
||||
body: formData
|
||||
})
|
||||
|
@ -1,7 +1,4 @@
|
||||
import {
|
||||
getLocalActiveIndex,
|
||||
getLocalInstances
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React, { useContext } from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
@ -13,15 +10,15 @@ import ComposeTextInput from './Header/TextInput'
|
||||
|
||||
const ComposeRootHeader: React.FC = () => {
|
||||
const { composeState } = useContext(ComposeContext)
|
||||
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
const localInstances = useSelector(
|
||||
getLocalInstances,
|
||||
getInstances,
|
||||
(prev, next) => prev.length === next.length
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{localActiveIndex !== null &&
|
||||
{instanceActive !== -1 &&
|
||||
localInstances.length &&
|
||||
localInstances.length > 1 && (
|
||||
<View style={styles.postingAs}>
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { getLocalAccount, getLocalUri } from '@utils/slices/instancesSlice'
|
||||
import {
|
||||
getInstanceAccount,
|
||||
getInstanceUri
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
@ -11,17 +14,17 @@ const ComposePostingAs = React.memo(
|
||||
const { t } = useTranslation('sharedCompose')
|
||||
const { theme } = useTheme()
|
||||
|
||||
const localAccount = useSelector(
|
||||
getLocalAccount,
|
||||
const instanceAccount = useSelector(
|
||||
getInstanceAccount,
|
||||
(prev, next) => prev?.acct === next?.acct
|
||||
)
|
||||
const localUri = useSelector(getLocalUri)
|
||||
const instanceUri = useSelector(getInstanceUri)
|
||||
|
||||
return (
|
||||
<Text style={[styles.text, { color: theme.secondary }]}>
|
||||
{t('content.root.header.postingAs', {
|
||||
acct: localAccount?.acct,
|
||||
domain: localUri
|
||||
acct: instanceAccount?.acct,
|
||||
domain: instanceUri
|
||||
})}
|
||||
</Text>
|
||||
)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { store } from '@root/store'
|
||||
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import composeInitialState from './initialState'
|
||||
import { ComposeState } from './types'
|
||||
|
||||
@ -39,7 +39,7 @@ const composeParseState = (
|
||||
}),
|
||||
visibility:
|
||||
params.incomingStatus.visibility ||
|
||||
getLocalAccount(store.getState())?.preferences[
|
||||
getInstanceAccount(store.getState()).preferences[
|
||||
'posting:default:visibility'
|
||||
] ||
|
||||
'public',
|
||||
|
@ -1,4 +1,4 @@
|
||||
import client from '@root/api/client'
|
||||
import apiInstance from '@api/instance'
|
||||
import { ComposeState } from '@screens/Compose/utils/types'
|
||||
import * as Crypto from 'expo-crypto'
|
||||
|
||||
@ -39,9 +39,8 @@ const composePost = async (
|
||||
|
||||
formData.append('visibility', composeState.visibility)
|
||||
|
||||
return client<Mastodon.Status>({
|
||||
return apiInstance<Mastodon.Status>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: 'statuses',
|
||||
headers: {
|
||||
'Idempotency-Key': await Crypto.digestStringAsync(
|
||||
|
@ -10,10 +10,10 @@ import { StackScreenProps } from '@react-navigation/stack'
|
||||
import { useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import { getPreviousTab } from '@utils/slices/contextsSlice'
|
||||
import {
|
||||
getLocalAccount,
|
||||
getLocalActiveIndex,
|
||||
getLocalNotification,
|
||||
updateLocalNotification
|
||||
getInstanceAccount,
|
||||
getInstanceActive,
|
||||
getInstanceNotification,
|
||||
updateInstanceNotification
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
@ -44,14 +44,15 @@ const ScreenTabs = React.memo(
|
||||
({ navigation }: ScreenTabsProp) => {
|
||||
const { mode, theme } = useTheme()
|
||||
const dispatch = useDispatch()
|
||||
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
const localAccount = useSelector(
|
||||
getLocalAccount,
|
||||
getInstanceAccount,
|
||||
(prev, next) => prev?.avatarStatic === next?.avatarStatic
|
||||
)
|
||||
|
||||
const screenOptions = useCallback(
|
||||
({ route }): BottomTabNavigationOptions => ({
|
||||
tabBarVisible: instanceActive !== -1,
|
||||
tabBarIcon: ({
|
||||
focused,
|
||||
color,
|
||||
@ -71,7 +72,7 @@ const ScreenTabs = React.memo(
|
||||
case 'Tab-Notifications':
|
||||
return <Icon name='Bell' size={size} color={color} />
|
||||
case 'Tab-Me':
|
||||
return localActiveIndex !== null ? (
|
||||
return instanceActive !== -1 ? (
|
||||
<FastImage
|
||||
source={{ uri: localAccount?.avatarStatic }}
|
||||
style={{
|
||||
@ -94,61 +95,39 @@ const ScreenTabs = React.memo(
|
||||
}
|
||||
}
|
||||
}),
|
||||
[localActiveIndex, localAccount?.avatarStatic]
|
||||
[instanceActive, localAccount?.avatarStatic]
|
||||
)
|
||||
const tabBarOptions = useMemo(
|
||||
() => ({
|
||||
activeTintColor: theme.primary,
|
||||
inactiveTintColor:
|
||||
localActiveIndex !== null ? theme.secondary : theme.disabled,
|
||||
inactiveTintColor: theme.secondary,
|
||||
showLabel: false,
|
||||
...(Platform.OS === 'android' && { keyboardHidesTabBar: true })
|
||||
}),
|
||||
[mode, localActiveIndex]
|
||||
)
|
||||
const localListeners = useCallback(
|
||||
() => ({
|
||||
tabPress: (e: any) => {
|
||||
if (!(localActiveIndex !== null)) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
}),
|
||||
[localActiveIndex]
|
||||
[mode]
|
||||
)
|
||||
const composeListeners = useMemo(
|
||||
() => ({
|
||||
tabPress: (e: any) => {
|
||||
e.preventDefault()
|
||||
if (localActiveIndex !== null) {
|
||||
haptics('Light')
|
||||
navigation.navigate('Screen-Compose')
|
||||
}
|
||||
haptics('Light')
|
||||
navigation.navigate('Screen-Compose')
|
||||
}
|
||||
}),
|
||||
[localActiveIndex]
|
||||
[]
|
||||
)
|
||||
const composeComponent = useCallback(() => null, [])
|
||||
const notificationsListeners = useCallback(
|
||||
() => ({
|
||||
tabPress: (e: any) => {
|
||||
if (!(localActiveIndex !== null)) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
}),
|
||||
[localActiveIndex]
|
||||
)
|
||||
|
||||
// On launch check if there is any unread noficiations
|
||||
useTimelineQuery({
|
||||
page: 'Notifications',
|
||||
options: {
|
||||
enabled: instanceActive !== -1,
|
||||
notifyOnChangeProps: [],
|
||||
select: data => {
|
||||
if (data.pages[0].body.length) {
|
||||
dispatch(
|
||||
updateLocalNotification({
|
||||
updateInstanceNotification({
|
||||
// @ts-ignore
|
||||
latestTime: data.pages[0].body[0].created_at
|
||||
})
|
||||
@ -160,27 +139,21 @@ const ScreenTabs = React.memo(
|
||||
})
|
||||
useWebsocket({ stream: 'user', event: 'notification' })
|
||||
const localNotification = useSelector(
|
||||
getLocalNotification,
|
||||
getInstanceNotification,
|
||||
(prev, next) =>
|
||||
prev?.readTime === next?.readTime &&
|
||||
prev?.latestTime === next?.latestTime
|
||||
)
|
||||
|
||||
const previousTab = useSelector(getPreviousTab, () => true)
|
||||
|
||||
return (
|
||||
<Tab.Navigator
|
||||
initialRouteName={
|
||||
localActiveIndex !== null
|
||||
? useSelector(getPreviousTab, () => true)
|
||||
: 'Tab-Me'
|
||||
}
|
||||
initialRouteName={instanceActive !== -1 ? previousTab : 'Tab-Me'}
|
||||
screenOptions={screenOptions}
|
||||
tabBarOptions={tabBarOptions}
|
||||
>
|
||||
<Tab.Screen
|
||||
name='Tab-Local'
|
||||
component={TabLocal}
|
||||
listeners={localListeners}
|
||||
/>
|
||||
<Tab.Screen name='Tab-Local' component={TabLocal} />
|
||||
<Tab.Screen name='Tab-Public' component={TabPublic} />
|
||||
<Tab.Screen
|
||||
name='Tab-Compose'
|
||||
@ -190,7 +163,6 @@ const ScreenTabs = React.memo(
|
||||
<Tab.Screen
|
||||
name='Tab-Notifications'
|
||||
component={TabNotifications}
|
||||
listeners={notificationsListeners}
|
||||
options={{
|
||||
tabBarBadge: localNotification?.latestTime
|
||||
? !localNotification.readTime ||
|
||||
|
@ -3,7 +3,7 @@ import { HeaderCenter, HeaderRight } from '@components/Header'
|
||||
import Timeline from '@components/Timeline'
|
||||
import { BottomTabScreenProps } from '@react-navigation/bottom-tabs'
|
||||
import { ScreenTabsParamList } from '@screens/Tabs'
|
||||
import { getLocalActiveIndex } from '@utils/slices/instancesSlice'
|
||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Platform } from 'react-native'
|
||||
@ -21,7 +21,7 @@ const Stack = createNativeStackNavigator<Nav.TabLocalStackParamList>()
|
||||
const TabLocal = React.memo(
|
||||
({ navigation }: TabLocalProp) => {
|
||||
const { t } = useTranslation('local')
|
||||
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
|
||||
const screenOptions = useMemo(
|
||||
() => ({
|
||||
@ -49,7 +49,7 @@ const TabLocal = React.memo(
|
||||
[]
|
||||
)
|
||||
const children = useCallback(
|
||||
() => (localActiveIndex !== null ? <Timeline page='Following' /> : null),
|
||||
() => (instanceActive !== -1 ? <Timeline page='Following' /> : null),
|
||||
[]
|
||||
)
|
||||
|
||||
|
@ -12,6 +12,7 @@ import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Platform } from 'react-native'
|
||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
import ScreenMeSettingsNotification from './Me/Notification'
|
||||
|
||||
const Stack = createNativeStackNavigator<Nav.TabMeStackParamList>()
|
||||
|
||||
@ -114,6 +115,19 @@ const TabMe = React.memo(
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Settings-Notification'
|
||||
component={ScreenMeSettingsNotification}
|
||||
options={({ navigation }: any) => ({
|
||||
headerTitle: t('meSettingsNotification:heading'),
|
||||
...(Platform.OS === 'android' && {
|
||||
headerCenter: () => (
|
||||
<HeaderCenter content={t('meSettingsNotification:heading')} />
|
||||
)
|
||||
}),
|
||||
headerLeft: () => <HeaderLeft onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Switch'
|
||||
component={ScreenMeSwitch}
|
||||
|
37
src/screens/Tabs/Me/Notification.tsx
Normal file
37
src/screens/Tabs/Me/Notification.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { usePushQuery } from '@utils/queryHooks/push'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
||||
const ScreenMeSettingsNotification: React.FC = () => {
|
||||
const { t } = useTranslation('meSettingsNotification')
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const { data, isLoading } = usePushQuery({})
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
<MenuContainer>
|
||||
<MenuRow
|
||||
title={t('content.global.heading')}
|
||||
description={t('content.global.description')}
|
||||
// switchValue={notification.enabled}
|
||||
// switchOnValueChange={() => dispatch(updateNotification(true))}
|
||||
/>
|
||||
</MenuContainer>
|
||||
<MenuContainer>
|
||||
<MenuRow
|
||||
title={t('content.follow.heading')}
|
||||
loading={isLoading}
|
||||
// switchDisabled={!notification.enabled}
|
||||
// switchValue={notification.enabled ? data?.alerts.follow : false}
|
||||
// switchOnValueChange={() => dispatch(updateNotification(true))}
|
||||
/>
|
||||
</MenuContainer>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
||||
export default ScreenMeSettingsNotification
|
@ -8,7 +8,7 @@ import AccountNav from '@screens/Tabs/Shared/Account/Nav'
|
||||
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 { getLocalActiveIndex } from '@utils/slices/instancesSlice'
|
||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||
import React, { useReducer, useRef, useState } from 'react'
|
||||
import Animated, {
|
||||
useAnimatedScrollHandler,
|
||||
@ -17,7 +17,7 @@ import Animated, {
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const ScreenMeRoot: React.FC = () => {
|
||||
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
|
||||
const scrollRef = useRef<Animated.ScrollView>(null)
|
||||
useScrollToTop(scrollRef)
|
||||
@ -36,7 +36,7 @@ const ScreenMeRoot: React.FC = () => {
|
||||
|
||||
return (
|
||||
<AccountContext.Provider value={{ accountState, accountDispatch }}>
|
||||
{localActiveIndex !== null && data ? (
|
||||
{instanceActive !== -1 && data ? (
|
||||
<AccountNav scrollY={scrollY} account={data} />
|
||||
) : null}
|
||||
<Animated.ScrollView
|
||||
@ -45,14 +45,14 @@ const ScreenMeRoot: React.FC = () => {
|
||||
onScroll={onScroll}
|
||||
scrollEventThrottle={16}
|
||||
>
|
||||
{localActiveIndex !== null ? (
|
||||
{instanceActive !== -1 ? (
|
||||
<MyInfo setData={setData} />
|
||||
) : (
|
||||
<ComponentInstance />
|
||||
)}
|
||||
{localActiveIndex !== null ? <Collections /> : null}
|
||||
{instanceActive !== -1 ? <Collections /> : null}
|
||||
<Settings />
|
||||
{localActiveIndex !== null ? <Logout /> : null}
|
||||
{instanceActive !== -1 ? <Logout /> : null}
|
||||
</Animated.ScrollView>
|
||||
</AccountContext.Provider>
|
||||
)
|
||||
|
@ -1,17 +1,19 @@
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@root/components/haptics'
|
||||
import { localRemoveInstance } from '@utils/slices/instancesSlice'
|
||||
import removeInstance from '@utils/slices/instances/remove'
|
||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert } from 'react-native'
|
||||
import { useQueryClient } from 'react-query'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
const Logout: React.FC = () => {
|
||||
const { t } = useTranslation('meRoot')
|
||||
const dispatch = useDispatch()
|
||||
const queryClient = useQueryClient()
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
|
||||
return (
|
||||
<Button
|
||||
@ -33,7 +35,7 @@ const Logout: React.FC = () => {
|
||||
onPress: () => {
|
||||
haptics('Success')
|
||||
queryClient.clear()
|
||||
dispatch(localRemoveInstance())
|
||||
dispatch(removeInstance(instanceActive))
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1,7 +1,7 @@
|
||||
import AccountHeader from '@screens/Tabs/Shared/Account/Header'
|
||||
import AccountInformation from '@screens/Tabs/Shared/Account/Information'
|
||||
import { useAccountQuery } from '@utils/queryHooks/account'
|
||||
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
@ -10,11 +10,11 @@ export interface Props {
|
||||
}
|
||||
|
||||
const MyInfo: React.FC<Props> = ({ setData }) => {
|
||||
const localAccount = useSelector(
|
||||
getLocalAccount,
|
||||
const instanceAccount = useSelector(
|
||||
getInstanceAccount,
|
||||
(prev, next) => prev?.id === next?.id
|
||||
)
|
||||
const { data } = useAccountQuery({ id: localAccount!.id })
|
||||
const { data } = useAccountQuery({ id: instanceAccount!.id })
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
|
@ -1,17 +1,12 @@
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { getLocalUrl } from '@utils/slices/instancesSlice'
|
||||
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('meRoot')
|
||||
const navigation = useNavigation()
|
||||
|
||||
const localUrl = useSelector(getLocalUrl)
|
||||
|
||||
return (
|
||||
<MenuContainer>
|
||||
{/* <MenuRow
|
||||
|
@ -2,7 +2,9 @@ import analytics from '@components/analytics'
|
||||
import haptics from '@components/haptics'
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import i18n from '@root/i18n/i18n'
|
||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||
import {
|
||||
changeBrowser,
|
||||
changeLanguage,
|
||||
@ -17,17 +19,28 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
const SettingsApp: React.FC = () => {
|
||||
const navigation = useNavigation()
|
||||
const dispatch = useDispatch()
|
||||
const { showActionSheetWithOptions } = useActionSheet()
|
||||
const { setTheme } = useTheme()
|
||||
const { t } = useTranslation('meSettings')
|
||||
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
const settingsLanguage = useSelector(getSettingsLanguage)
|
||||
const settingsTheme = useSelector(getSettingsTheme)
|
||||
const settingsBrowser = useSelector(getSettingsBrowser)
|
||||
|
||||
return (
|
||||
<MenuContainer>
|
||||
{instanceActive !== -1 ? (
|
||||
<MenuRow
|
||||
title={t('content.notification.heading')}
|
||||
iconBack='ChevronRight'
|
||||
onPress={() => {
|
||||
navigation.navigate('Tab-Me-Settings-Notification')
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<MenuRow
|
||||
title={t('content.language.heading')}
|
||||
content={t(`content.language.options.${settingsLanguage}`)}
|
||||
|
@ -2,39 +2,36 @@ import Button from '@components/Button'
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { persistor } from '@root/store'
|
||||
import {
|
||||
getLocalActiveIndex,
|
||||
getLocalInstances
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const SettingsDev: React.FC = () => {
|
||||
const { showActionSheetWithOptions } = useActionSheet()
|
||||
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||
const localInstances = useSelector(getLocalInstances)
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
const instances = useSelector(getInstances)
|
||||
|
||||
return (
|
||||
<MenuContainer>
|
||||
<MenuRow
|
||||
title={'Local active index'}
|
||||
content={typeof localActiveIndex + ' - ' + localActiveIndex}
|
||||
content={typeof instanceActive + ' - ' + instanceActive}
|
||||
onPress={() => {}}
|
||||
/>
|
||||
<MenuRow
|
||||
title={'Saved local instances'}
|
||||
content={localInstances.length.toString()}
|
||||
content={instances.length.toString()}
|
||||
iconBack='ChevronRight'
|
||||
onPress={() =>
|
||||
showActionSheetWithOptions(
|
||||
{
|
||||
options: localInstances
|
||||
options: instances
|
||||
.map(instance => {
|
||||
return instance.url + ': ' + instance.account.id
|
||||
})
|
||||
.concat(['Cancel']),
|
||||
cancelButtonIndex: localInstances.length
|
||||
cancelButtonIndex: instances.length
|
||||
},
|
||||
buttonIndex => {}
|
||||
)
|
||||
|
@ -3,7 +3,6 @@ import Icon from '@components/Icon'
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { useSearchQuery } from '@utils/queryHooks/search'
|
||||
import { getLocalActiveIndex } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as Updates from 'expo-updates'
|
||||
@ -13,16 +12,17 @@ import * as WebBrowser from 'expo-web-browser'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||
|
||||
const SettingsTooot: React.FC = () => {
|
||||
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
const navigation = useNavigation()
|
||||
const { theme } = useTheme()
|
||||
const { t } = useTranslation('meSettings')
|
||||
|
||||
const { isLoading, data } = useSearchQuery({
|
||||
term: '@tooot@xmflsct.com',
|
||||
options: { enabled: localActiveIndex !== null }
|
||||
options: { enabled: instanceActive !== -1 }
|
||||
})
|
||||
|
||||
return (
|
||||
|
@ -4,10 +4,10 @@ import haptics from '@components/haptics'
|
||||
import ComponentInstance from '@components/Instance'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import {
|
||||
getLocalActiveIndex,
|
||||
getLocalInstances,
|
||||
InstanceLocal,
|
||||
updateLocalActiveIndex
|
||||
getInstanceActive,
|
||||
getInstances,
|
||||
Instance,
|
||||
updateInstanceActive
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
@ -25,7 +25,7 @@ import { useQueryClient } from 'react-query'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
interface Props {
|
||||
instance: InstanceLocal
|
||||
instance: Instance
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@ const AccountButton: React.FC<Props> = ({ instance, disabled = false }) => {
|
||||
onPress={() => {
|
||||
haptics('Light')
|
||||
analytics('switch_existing_press')
|
||||
dispatch(updateLocalActiveIndex(instance))
|
||||
dispatch(updateInstanceActive(instance))
|
||||
queryClient.clear()
|
||||
navigation.goBack()
|
||||
}}
|
||||
@ -56,8 +56,8 @@ const AccountButton: React.FC<Props> = ({ instance, disabled = false }) => {
|
||||
const ScreenMeSwitchRoot: React.FC = () => {
|
||||
const { t } = useTranslation('meSwitch')
|
||||
const { theme } = useTheme()
|
||||
const localInstances = useSelector(getLocalInstances)
|
||||
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||
const instances = useSelector(getInstances)
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
@ -72,8 +72,8 @@ const ScreenMeSwitchRoot: React.FC = () => {
|
||||
{t('content.existing')}
|
||||
</Text>
|
||||
<View style={styles.accountButtons}>
|
||||
{localInstances.length
|
||||
? localInstances
|
||||
{instances.length
|
||||
? instances
|
||||
.slice()
|
||||
.sort((a, b) =>
|
||||
`${a.uri}${a.account.acct}`.localeCompare(
|
||||
@ -81,7 +81,7 @@ const ScreenMeSwitchRoot: React.FC = () => {
|
||||
)
|
||||
)
|
||||
.map((instance, index) => {
|
||||
const localAccount = localInstances[localActiveIndex!]
|
||||
const localAccount = instances[instanceActive!]
|
||||
return (
|
||||
<AccountButton
|
||||
key={index}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { HeaderCenter } from '@components/Header'
|
||||
import Timeline from '@components/Timeline'
|
||||
import sharedScreens from '@screens/Tabs/Shared/sharedScreens'
|
||||
import { updateLocalNotification } from '@utils/slices/instancesSlice'
|
||||
import { updateInstanceNotification } from '@utils/slices/instancesSlice'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Platform, ViewToken } from 'react-native'
|
||||
@ -46,7 +46,7 @@ const TabNotifications = React.memo(
|
||||
viewableItems[0].index === 0
|
||||
) {
|
||||
dispatch(
|
||||
updateLocalNotification({
|
||||
updateInstanceNotification({
|
||||
readTime: viewableItems[0].item.created_at
|
||||
})
|
||||
)
|
||||
|
@ -4,7 +4,7 @@ import Timeline from '@components/Timeline'
|
||||
import SegmentedControl from '@react-native-community/segmented-control'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import sharedScreens from '@screens/Tabs/Shared/sharedScreens'
|
||||
import { getLocalActiveIndex } from '@utils/slices/instancesSlice'
|
||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -21,7 +21,7 @@ const TabPublic = React.memo(
|
||||
const { t, i18n } = useTranslation()
|
||||
const { mode } = useTheme()
|
||||
const navigation = useNavigation()
|
||||
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||
const instanceActive = useSelector(getInstanceActive)
|
||||
|
||||
const [segment, setSegment] = useState(0)
|
||||
const pages: { title: string; page: App.Pages }[] = [
|
||||
@ -74,9 +74,9 @@ const TabPublic = React.memo(
|
||||
key: App.Pages
|
||||
}
|
||||
}) => {
|
||||
return localActiveIndex !== null && <Timeline page={route.key} />
|
||||
return instanceActive !== -1 && <Timeline page={route.key} />
|
||||
},
|
||||
[localActiveIndex]
|
||||
[instanceActive]
|
||||
)
|
||||
const children = useCallback(
|
||||
() => (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback } from 'react'
|
||||
@ -23,7 +23,7 @@ export interface Props {
|
||||
const AccountInformation: React.FC<Props> = ({ account, myInfo = false }) => {
|
||||
const ownAccount =
|
||||
account?.id ===
|
||||
useSelector(getLocalAccount, (prev, next) => prev?.id === next?.id)?.id
|
||||
useSelector(getInstanceAccount, (prev, next) => prev?.id === next?.id)?.id
|
||||
const { mode, theme } = useTheme()
|
||||
|
||||
const animation = useCallback(
|
||||
|
@ -1,5 +1,8 @@
|
||||
import Icon from '@components/Icon'
|
||||
import { getLocalAccount, getLocalUri } from '@utils/slices/instancesSlice'
|
||||
import {
|
||||
getInstanceAccount,
|
||||
getInstanceUri
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useMemo } from 'react'
|
||||
@ -14,11 +17,11 @@ export interface Props {
|
||||
|
||||
const AccountInformationAccount: React.FC<Props> = ({ account, myInfo }) => {
|
||||
const { theme } = useTheme()
|
||||
const localAccount = useSelector(
|
||||
getLocalAccount,
|
||||
const instanceAccount = useSelector(
|
||||
getInstanceAccount,
|
||||
(prev, next) => prev?.acct === next?.acct
|
||||
)
|
||||
const localUri = useSelector(getLocalUri)
|
||||
const instanceUri = useSelector(getInstanceUri)
|
||||
|
||||
const movedStyle = useMemo(
|
||||
() =>
|
||||
@ -45,7 +48,7 @@ const AccountInformationAccount: React.FC<Props> = ({ account, myInfo }) => {
|
||||
}
|
||||
}, [account?.moved])
|
||||
|
||||
if (account || (myInfo && localAccount !== undefined)) {
|
||||
if (account || (myInfo && instanceAccount)) {
|
||||
return (
|
||||
<View
|
||||
style={[styles.base, { flexDirection: 'row', alignItems: 'center' }]}
|
||||
@ -60,8 +63,8 @@ const AccountInformationAccount: React.FC<Props> = ({ account, myInfo }) => {
|
||||
]}
|
||||
selectable
|
||||
>
|
||||
@{myInfo ? localAccount?.acct : account?.acct}
|
||||
{myInfo ? `@${localUri}` : null}
|
||||
@{myInfo ? instanceAccount.acct : account?.acct}
|
||||
{myInfo ? `@${instanceUri}` : null}
|
||||
</Text>
|
||||
{movedContent}
|
||||
{account?.locked ? (
|
||||
|
@ -1,9 +1,10 @@
|
||||
import client from '@api/client'
|
||||
import apiInstance from '@api/instance'
|
||||
import NetInfo from '@react-native-community/netinfo'
|
||||
import { store } from '@root/store'
|
||||
import removeInstance from '@utils/slices/instances/remove'
|
||||
import {
|
||||
localRemoveInstance,
|
||||
updateLocalAccount
|
||||
getInstanceActive,
|
||||
updateInstanceAccount
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import log from './log'
|
||||
|
||||
@ -13,29 +14,28 @@ const netInfo = async (): Promise<{
|
||||
}> => {
|
||||
log('log', 'netInfo', 'initializing')
|
||||
const netInfo = await NetInfo.fetch()
|
||||
const activeIndex = store.getState().instances.local?.activeIndex
|
||||
const activeIndex = getInstanceActive(store.getState())
|
||||
|
||||
if (netInfo.isConnected) {
|
||||
log('log', 'netInfo', 'network connected')
|
||||
if (activeIndex !== null) {
|
||||
if (activeIndex !== -1) {
|
||||
log('log', 'netInfo', 'checking locally stored credentials')
|
||||
return client<Mastodon.Account>({
|
||||
return apiInstance<Mastodon.Account>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `accounts/verify_credentials`
|
||||
})
|
||||
.then(res => {
|
||||
log('log', 'netInfo', 'local credential check passed')
|
||||
if (
|
||||
res.body.id !==
|
||||
store.getState().instances.local?.instances[activeIndex].account.id
|
||||
store.getState().instances.instances[activeIndex].account.id
|
||||
) {
|
||||
log('error', 'netInfo', 'local id does not match remote id')
|
||||
store.dispatch(localRemoveInstance(activeIndex))
|
||||
store.dispatch(removeInstance(activeIndex))
|
||||
return Promise.resolve({ connected: true, corruputed: '' })
|
||||
} else {
|
||||
store.dispatch(
|
||||
updateLocalAccount({
|
||||
updateInstanceAccount({
|
||||
acct: res.body.acct,
|
||||
avatarStatic: res.body.avatar_static
|
||||
})
|
||||
@ -50,7 +50,7 @@ const netInfo = async (): Promise<{
|
||||
typeof error.status === 'number' &&
|
||||
error.status === 401
|
||||
) {
|
||||
store.dispatch(localRemoveInstance(activeIndex))
|
||||
store.dispatch(removeInstance(activeIndex))
|
||||
}
|
||||
return Promise.resolve({
|
||||
connected: true,
|
||||
|
39
src/store.ts
39
src/store.ts
@ -6,9 +6,9 @@ import {
|
||||
getDefaultMiddleware
|
||||
} from '@reduxjs/toolkit'
|
||||
import contextsSlice from '@utils/slices/contextsSlice'
|
||||
import instancesSlice, { InstancesState } from '@utils/slices/instancesSlice'
|
||||
import instancesSlice from '@utils/slices/instancesSlice'
|
||||
import settingsSlice from '@utils/slices/settingsSlice'
|
||||
import { createMigrate, persistReducer, persistStore } from 'redux-persist'
|
||||
import { persistReducer, persistStore } from 'redux-persist'
|
||||
|
||||
const secureStorage = createSecureStore()
|
||||
|
||||
@ -20,43 +20,10 @@ const contextsPersistConfig = {
|
||||
storage: AsyncStorage
|
||||
}
|
||||
|
||||
const instancesMigration = {
|
||||
2: (state: InstancesState) => {
|
||||
return {
|
||||
...state,
|
||||
local: {
|
||||
...state.local,
|
||||
instances: state.local.instances.map(instance => {
|
||||
instance.max_toot_chars = 500
|
||||
instance.drafts = []
|
||||
return instance
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
3: (state: InstancesState) => {
|
||||
return {
|
||||
...state,
|
||||
local: {
|
||||
...state.local,
|
||||
instances: state.local.instances.map(instance => {
|
||||
if (!instance.urls) {
|
||||
instance.urls = {
|
||||
streaming_api: `wss://${instance.url}`
|
||||
}
|
||||
}
|
||||
return instance
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const instancesPersistConfig = {
|
||||
key: 'instances',
|
||||
prefix,
|
||||
version: 3,
|
||||
storage: secureStorage,
|
||||
migrate: createMigrate(instancesMigration, { debug: true })
|
||||
storage: secureStorage
|
||||
}
|
||||
|
||||
const settingsPersistConfig = {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import client from '@api/client'
|
||||
import apiInstance from '@api/instance'
|
||||
import { AxiosError } from 'axios'
|
||||
import { useQuery, UseQueryOptions } from 'react-query'
|
||||
|
||||
@ -7,9 +7,8 @@ export type QueryKey = ['Account', { id: Mastodon.Account['id'] }]
|
||||
const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
|
||||
const { id } = queryKey[1]
|
||||
|
||||
return client<Mastodon.Account>({
|
||||
return apiInstance<Mastodon.Account>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `accounts/${id}`
|
||||
}).then(res => res.body)
|
||||
}
|
||||
|
@ -1,35 +0,0 @@
|
||||
import client from '@api/client'
|
||||
import { InstancesState } from '@utils/slices/instancesSlice'
|
||||
import { AxiosError } from 'axios'
|
||||
import { useQuery, UseQueryOptions } from 'react-query'
|
||||
|
||||
export type QueryKey = [
|
||||
'AccountCheck',
|
||||
{
|
||||
id: Mastodon.Account['id']
|
||||
index: NonNullable<InstancesState['local']['activeIndex']>
|
||||
}
|
||||
]
|
||||
|
||||
const queryFunction = async ({ queryKey }: { queryKey: QueryKey }) => {
|
||||
const { id, index } = queryKey[1]
|
||||
|
||||
return client<Mastodon.Account>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
localIndex: index,
|
||||
url: `accounts/${id}`
|
||||
}).then(res => res.body)
|
||||
}
|
||||
|
||||
const useAccountCheckQuery = <TData = Mastodon.Account>({
|
||||
options,
|
||||
...queryKeyParams
|
||||
}: QueryKey[1] & {
|
||||
options?: UseQueryOptions<Mastodon.Account, AxiosError, TData>
|
||||
}) => {
|
||||
const queryKey: QueryKey = ['AccountCheck', { ...queryKeyParams }]
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
}
|
||||
|
||||
export { useAccountCheckQuery }
|
@ -1,4 +1,4 @@
|
||||
import client from '@api/client'
|
||||
import apiInstance from '@api/instance'
|
||||
import { AxiosError } from 'axios'
|
||||
import {
|
||||
useMutation,
|
||||
@ -12,9 +12,8 @@ type QueryKeyAnnouncement = ['Announcements', { showAll?: boolean }]
|
||||
const queryFunction = ({ queryKey }: { queryKey: QueryKeyAnnouncement }) => {
|
||||
const { showAll } = queryKey[1]
|
||||
|
||||
return client<Mastodon.Announcement[]>({
|
||||
return apiInstance<Mastodon.Announcement[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `announcements`,
|
||||
...(showAll && {
|
||||
params: {
|
||||
@ -52,15 +51,13 @@ const mutationFunction = async ({
|
||||
}: MutationVarsAnnouncement) => {
|
||||
switch (type) {
|
||||
case 'reaction':
|
||||
return client<{}>({
|
||||
return apiInstance<{}>({
|
||||
method: me ? 'delete' : 'put',
|
||||
instance: 'local',
|
||||
url: `announcements/${id}/reactions/${name}`
|
||||
})
|
||||
case 'dismiss':
|
||||
return client<{}>({
|
||||
return apiInstance<{}>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `announcements/${id}/dismiss`
|
||||
})
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import client from '@api/client'
|
||||
import apiGeneral from '@api/general'
|
||||
import { AxiosError } from 'axios'
|
||||
import * as AuthSession from 'expo-auth-session'
|
||||
import { useQuery, UseQueryOptions } from 'react-query'
|
||||
|
||||
export type QueryKey = ['Apps', { instanceDomain?: string }]
|
||||
export type QueryKey = ['Apps', { domain?: string }]
|
||||
|
||||
const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
|
||||
const redirectUri = AuthSession.makeRedirectUri({
|
||||
@ -11,7 +11,7 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
|
||||
useProxy: false
|
||||
})
|
||||
|
||||
const { instanceDomain } = queryKey[1]
|
||||
const { domain } = queryKey[1]
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('client_name', 'tooot')
|
||||
@ -19,11 +19,10 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
|
||||
formData.append('redirect_uris', redirectUri)
|
||||
formData.append('scopes', 'read write follow push')
|
||||
|
||||
return client<Mastodon.Apps>({
|
||||
return apiGeneral<Mastodon.Apps>({
|
||||
method: 'post',
|
||||
instance: 'remote',
|
||||
instanceDomain,
|
||||
url: `apps`,
|
||||
domain: domain || '',
|
||||
url: `api/v1/apps`,
|
||||
body: formData
|
||||
}).then(res => res.body)
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
import client from '@api/client'
|
||||
import apiInstance from '@api/instance'
|
||||
import { AxiosError } from 'axios'
|
||||
import { useQuery, UseQueryOptions } from 'react-query'
|
||||
|
||||
type QueryKey = ['Emojis']
|
||||
|
||||
const queryFunction = () => {
|
||||
return client<Mastodon.Emoji[]>({
|
||||
return apiInstance<Mastodon.Emoji[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: 'custom_emojis'
|
||||
}).then(res => res.body)
|
||||
}
|
||||
|
@ -1,18 +1,21 @@
|
||||
import client from '@api/client'
|
||||
import apiGeneral from '@api/general'
|
||||
import { AxiosError } from 'axios'
|
||||
import { useQuery, UseQueryOptions } from 'react-query'
|
||||
|
||||
export type QueryKey = ['Instance', { instanceDomain?: string }]
|
||||
export type QueryKey = ['Instance', { domain?: string }]
|
||||
|
||||
const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
|
||||
const { instanceDomain } = queryKey[1]
|
||||
const queryFunction = async ({ queryKey }: { queryKey: QueryKey }) => {
|
||||
const { domain } = queryKey[1]
|
||||
if (!domain) {
|
||||
return Promise.reject()
|
||||
}
|
||||
|
||||
return client<Mastodon.Instance>({
|
||||
const res = await apiGeneral<Mastodon.Instance>({
|
||||
method: 'get',
|
||||
instance: 'remote',
|
||||
instanceDomain,
|
||||
url: `instance`
|
||||
}).then(res => res.body)
|
||||
domain: domain,
|
||||
url: `api/v1/instance`
|
||||
})
|
||||
return res.body
|
||||
}
|
||||
|
||||
const useInstanceQuery = <
|
||||
|
@ -1,13 +1,12 @@
|
||||
import client from '@api/client'
|
||||
import apiInstance from '@api/instance'
|
||||
import { AxiosError } from 'axios'
|
||||
import { useQuery, UseQueryOptions } from 'react-query'
|
||||
|
||||
export type QueryKey = ['Lists']
|
||||
|
||||
const queryFunction = () => {
|
||||
return client<Mastodon.List[]>({
|
||||
return apiInstance<Mastodon.List[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: 'lists'
|
||||
}).then(res => res.body)
|
||||
}
|
||||
|
24
src/utils/queryHooks/push.ts
Normal file
24
src/utils/queryHooks/push.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import apiInstance from '@api/instance'
|
||||
import { AxiosError } from 'axios'
|
||||
import { useQuery, UseQueryOptions } from 'react-query'
|
||||
|
||||
export type QueryKey = ['Push']
|
||||
|
||||
const queryFunction = async () => {
|
||||
const res = await apiInstance<Mastodon.PushSubscription>({
|
||||
method: 'get',
|
||||
url: 'push/subscription'
|
||||
})
|
||||
return res.body
|
||||
}
|
||||
|
||||
const usePushQuery = <TData = Mastodon.PushSubscription>({
|
||||
options
|
||||
}: {
|
||||
options?: UseQueryOptions<Mastodon.PushSubscription, AxiosError, TData>
|
||||
}) => {
|
||||
const queryKey: QueryKey = ['Push']
|
||||
return useQuery(queryKey, queryFunction, options)
|
||||
}
|
||||
|
||||
export { usePushQuery }
|
@ -1,4 +1,4 @@
|
||||
import client from '@api/client'
|
||||
import apiInstance from '@api/instance'
|
||||
import { AxiosError } from 'axios'
|
||||
import {
|
||||
useMutation,
|
||||
@ -15,9 +15,8 @@ export type QueryKeyRelationship = [
|
||||
const queryFunction = ({ queryKey }: { queryKey: QueryKeyRelationship }) => {
|
||||
const { id } = queryKey[1]
|
||||
|
||||
return client<Mastodon.Relationship[]>({
|
||||
return apiInstance<Mastodon.Relationship[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `accounts/relationships`,
|
||||
params: {
|
||||
'id[]': id
|
||||
@ -57,15 +56,13 @@ type MutationVarsRelationship =
|
||||
const mutationFunction = async (params: MutationVarsRelationship) => {
|
||||
switch (params.type) {
|
||||
case 'incoming':
|
||||
return client<Mastodon.Relationship>({
|
||||
return apiInstance<Mastodon.Relationship>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `follow_requests/${params.id}/${params.payload.action}`
|
||||
}).then(res => res.body)
|
||||
case 'outgoing':
|
||||
return client<Mastodon.Relationship>({
|
||||
return apiInstance<Mastodon.Relationship>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `accounts/${params.id}/${params.payload.state ? 'un' : ''}${
|
||||
params.payload.action
|
||||
}`
|
||||
|
@ -1,4 +1,4 @@
|
||||
import client from '@api/client'
|
||||
import apiInstance from '@api/instance'
|
||||
import { AxiosError } from 'axios'
|
||||
import { useInfiniteQuery, UseInfiniteQueryOptions } from 'react-query'
|
||||
|
||||
@ -17,9 +17,8 @@ const queryFunction = ({
|
||||
const { type, id } = queryKey[1]
|
||||
let params: { [key: string]: string } = { ...pageParam }
|
||||
|
||||
return client<Mastodon.Account[]>({
|
||||
return apiInstance<Mastodon.Account[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `accounts/${id}/${type}`,
|
||||
params
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
import client from '@api/client'
|
||||
import apiInstance from '@api/instance'
|
||||
import { AxiosError } from 'axios'
|
||||
import { useQuery, UseQueryOptions } from 'react-query'
|
||||
|
||||
@ -19,10 +19,9 @@ type SearchResult = {
|
||||
|
||||
const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
|
||||
const { type, term, limit = 20 } = queryKey[1]
|
||||
return client<SearchResult>({
|
||||
return apiInstance<SearchResult>({
|
||||
version: 'v2',
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: 'search',
|
||||
params: { ...(type && { type }), ...(term && { q: term }), limit }
|
||||
}).then(res => res.body)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import client from '@api/client'
|
||||
import apiInstance from '@api/instance'
|
||||
import haptics from '@components/haptics'
|
||||
import { AxiosError } from 'axios'
|
||||
import { uniqBy } from 'lodash'
|
||||
@ -35,17 +35,15 @@ const queryFunction = ({
|
||||
|
||||
switch (page) {
|
||||
case 'Following':
|
||||
return client<Mastodon.Status[]>({
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: 'timelines/home',
|
||||
params
|
||||
})
|
||||
|
||||
case 'Local':
|
||||
return client<Mastodon.Status[]>({
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: 'timelines/public',
|
||||
params: {
|
||||
...params,
|
||||
@ -54,26 +52,23 @@ const queryFunction = ({
|
||||
})
|
||||
|
||||
case 'LocalPublic':
|
||||
return client<Mastodon.Status[]>({
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: 'timelines/public',
|
||||
params
|
||||
})
|
||||
|
||||
case 'Notifications':
|
||||
return client<Mastodon.Notification[]>({
|
||||
return apiInstance<Mastodon.Notification[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: 'notifications',
|
||||
params
|
||||
})
|
||||
|
||||
case 'Account_Default':
|
||||
if (pageParam && pageParam.hasOwnProperty('max_id')) {
|
||||
return client<Mastodon.Status[]>({
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `accounts/${account}/statuses`,
|
||||
params: {
|
||||
exclude_replies: 'true',
|
||||
@ -81,9 +76,8 @@ const queryFunction = ({
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return client<(Mastodon.Status & { isPinned: boolean })[]>({
|
||||
return apiInstance<(Mastodon.Status & { isPinned: boolean })[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `accounts/${account}/statuses`,
|
||||
params: {
|
||||
pinned: 'true'
|
||||
@ -91,9 +85,8 @@ const queryFunction = ({
|
||||
}).then(async res1 => {
|
||||
let pinned: Mastodon.Status['id'][] = []
|
||||
res1.body.forEach(status => pinned.push(status.id))
|
||||
const res2 = await client<Mastodon.Status[]>({
|
||||
const res2 = await apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `accounts/${account}/statuses`,
|
||||
params: {
|
||||
exclude_replies: 'true'
|
||||
@ -108,17 +101,15 @@ const queryFunction = ({
|
||||
}
|
||||
|
||||
case 'Account_All':
|
||||
return client<Mastodon.Status[]>({
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `accounts/${account}/statuses`,
|
||||
params
|
||||
})
|
||||
|
||||
case 'Account_Attachments':
|
||||
return client<Mastodon.Status[]>({
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `accounts/${account}/statuses`,
|
||||
params: {
|
||||
only_media: 'true',
|
||||
@ -127,57 +118,50 @@ const queryFunction = ({
|
||||
})
|
||||
|
||||
case 'Hashtag':
|
||||
return client<Mastodon.Status[]>({
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `timelines/tag/${hashtag}`,
|
||||
params
|
||||
})
|
||||
|
||||
case 'Conversations':
|
||||
return client<Mastodon.Conversation[]>({
|
||||
return apiInstance<Mastodon.Conversation[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `conversations`,
|
||||
params
|
||||
})
|
||||
|
||||
case 'Bookmarks':
|
||||
return client<Mastodon.Status[]>({
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `bookmarks`,
|
||||
params
|
||||
})
|
||||
|
||||
case 'Favourites':
|
||||
return client<Mastodon.Status[]>({
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `favourites`,
|
||||
params
|
||||
})
|
||||
|
||||
case 'List':
|
||||
return client<Mastodon.Status[]>({
|
||||
return apiInstance<Mastodon.Status[]>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `timelines/list/${list}`,
|
||||
params
|
||||
})
|
||||
|
||||
case 'Toot':
|
||||
return client<Mastodon.Status>({
|
||||
return apiInstance<Mastodon.Status>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `statuses/${toot}`
|
||||
}).then(async res1 => {
|
||||
const res2 = await client<{
|
||||
const res2 = await apiInstance<{
|
||||
ancestors: Mastodon.Status[]
|
||||
descendants: Mastodon.Status[]
|
||||
}>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `statuses/${toot}/context`
|
||||
})
|
||||
return {
|
||||
@ -296,9 +280,8 @@ const mutationFunction = async (params: MutationVarsTimeline) => {
|
||||
}
|
||||
})
|
||||
|
||||
return client<Mastodon.Poll>({
|
||||
return apiInstance<Mastodon.Poll>({
|
||||
method: params.payload.type === 'vote' ? 'post' : 'get',
|
||||
instance: 'local',
|
||||
url:
|
||||
params.payload.type === 'vote'
|
||||
? `polls/${params.payload.id}/votes`
|
||||
@ -306,9 +289,8 @@ const mutationFunction = async (params: MutationVarsTimeline) => {
|
||||
...(params.payload.type === 'vote' && { body: formData })
|
||||
})
|
||||
default:
|
||||
return client<Mastodon.Status>({
|
||||
return apiInstance<Mastodon.Status>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `statuses/${params.id}/${
|
||||
params.payload.currentValue ? 'un' : ''
|
||||
}${MapPropertyToUrl[params.payload.property]}`
|
||||
@ -318,15 +300,13 @@ const mutationFunction = async (params: MutationVarsTimeline) => {
|
||||
switch (params.payload.property) {
|
||||
case 'block':
|
||||
case 'mute':
|
||||
return client<Mastodon.Account>({
|
||||
return apiInstance<Mastodon.Account>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `accounts/${params.id}/${params.payload.property}`
|
||||
})
|
||||
case 'reports':
|
||||
return client<Mastodon.Account>({
|
||||
return apiInstance<Mastodon.Account>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `reports`,
|
||||
params: {
|
||||
account_id: params.id
|
||||
@ -334,15 +314,13 @@ const mutationFunction = async (params: MutationVarsTimeline) => {
|
||||
})
|
||||
}
|
||||
case 'deleteItem':
|
||||
return client<Mastodon.Conversation>({
|
||||
return apiInstance<Mastodon.Conversation>({
|
||||
method: 'delete',
|
||||
instance: 'local',
|
||||
url: `${params.source}/${params.id}`
|
||||
})
|
||||
case 'domainBlock':
|
||||
return client<any>({
|
||||
return apiInstance<any>({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
url: `domain_blocks`,
|
||||
params: {
|
||||
domain: params.domain
|
||||
|
@ -32,11 +32,11 @@ export const contextsInitialState = {
|
||||
current: 0,
|
||||
hidden: false
|
||||
},
|
||||
previousTab: 'Tab-Local'
|
||||
previousTab: 'Tab-Me'
|
||||
}
|
||||
|
||||
const contextsSlice = createSlice({
|
||||
name: 'settings',
|
||||
name: 'contexts',
|
||||
initialState: contextsInitialState as ContextsState,
|
||||
reducers: {
|
||||
updateStoreReview: (state, action: PayloadAction<1>) => {
|
||||
|
87
src/utils/slices/instances/add.ts
Normal file
87
src/utils/slices/instances/add.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import apiGeneral from '@api/general'
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit'
|
||||
import { RootState } from '@root/store'
|
||||
import { Instance } from '../instancesSlice'
|
||||
|
||||
const addInstance = createAsyncThunk(
|
||||
'instances/add',
|
||||
async ({
|
||||
domain,
|
||||
token,
|
||||
instance,
|
||||
max_toot_chars = 500,
|
||||
appData
|
||||
}: {
|
||||
domain: Instance['url']
|
||||
token: Instance['token']
|
||||
instance: Mastodon.Instance
|
||||
max_toot_chars?: number
|
||||
appData: Instance['appData']
|
||||
}): Promise<{ type: 'add' | 'overwrite'; data: Instance }> => {
|
||||
const { store } = require('@root/store')
|
||||
const instances = (store.getState() as RootState).instances.instances
|
||||
|
||||
const {
|
||||
body: { id, acct, avatar_static }
|
||||
} = await apiGeneral<Mastodon.Account>({
|
||||
method: 'get',
|
||||
domain,
|
||||
url: `api/v1/accounts/verify_credentials`,
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
})
|
||||
|
||||
let type: 'add' | 'overwrite'
|
||||
type = 'add'
|
||||
if (
|
||||
instances.length &&
|
||||
instances.filter(instance => {
|
||||
if (instance.url === domain && instance.account.id === id) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}).length
|
||||
) {
|
||||
type = 'overwrite'
|
||||
} else {
|
||||
type = 'add'
|
||||
}
|
||||
|
||||
const { body: preferences } = await apiGeneral<Mastodon.Preferences>({
|
||||
method: 'get',
|
||||
domain,
|
||||
url: `api/v1/preferences`,
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
})
|
||||
|
||||
return Promise.resolve({
|
||||
type,
|
||||
data: {
|
||||
active: true,
|
||||
appData,
|
||||
url: domain,
|
||||
token,
|
||||
uri: instance.uri,
|
||||
urls: instance.urls,
|
||||
max_toot_chars,
|
||||
account: {
|
||||
id,
|
||||
acct,
|
||||
avatarStatic: avatar_static,
|
||||
preferences
|
||||
},
|
||||
notification: {
|
||||
readTime: undefined,
|
||||
latestTime: undefined
|
||||
},
|
||||
push: {
|
||||
loading: false,
|
||||
enabled: false
|
||||
},
|
||||
drafts: []
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
export default addInstance
|
21
src/utils/slices/instances/push/disable.ts
Normal file
21
src/utils/slices/instances/push/disable.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import apiGeneral from '@api/general'
|
||||
import * as Notifications from 'expo-notifications'
|
||||
|
||||
const serverUnregister = async () => {
|
||||
const deviceToken = (await Notifications.getDevicePushTokenAsync()).data
|
||||
|
||||
return apiGeneral<{ endpoint: string; publicKey: string; auth: string }>({
|
||||
method: 'post',
|
||||
domain: 'testpush.home.xmflsct.com',
|
||||
url: 'unregister',
|
||||
body: { deviceToken }
|
||||
})
|
||||
}
|
||||
|
||||
const pushDisable = async () => {
|
||||
await serverUnregister()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export default pushDisable
|
61
src/utils/slices/instances/push/enable.ts
Normal file
61
src/utils/slices/instances/push/enable.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import apiGeneral from '@api/general'
|
||||
import apiInstance from '@api/instance'
|
||||
import * as Notifications from 'expo-notifications'
|
||||
import { Platform } from 'react-native'
|
||||
|
||||
const serverRegister = async () => {
|
||||
const deviceToken = (await Notifications.getDevicePushTokenAsync()).data
|
||||
const expoToken = (
|
||||
await Notifications.getExpoPushTokenAsync({
|
||||
experienceId: '@xmflsct/tooot'
|
||||
})
|
||||
).data
|
||||
|
||||
return apiGeneral<{ endpoint: string; publicKey: string; auth: string }>({
|
||||
method: 'post',
|
||||
domain: 'testpush.home.xmflsct.com',
|
||||
url: 'register',
|
||||
body: { deviceToken, expoToken }
|
||||
})
|
||||
}
|
||||
|
||||
const pushEnable = async (): Promise<Mastodon.PushSubscription> => {
|
||||
const { status: existingStatus } = await Notifications.getPermissionsAsync()
|
||||
let finalStatus = existingStatus
|
||||
if (existingStatus !== 'granted') {
|
||||
const { status } = await Notifications.requestPermissionsAsync()
|
||||
finalStatus = status
|
||||
}
|
||||
if (finalStatus !== 'granted') {
|
||||
alert('Failed to get push token for push notification!')
|
||||
return Promise.reject()
|
||||
}
|
||||
|
||||
const serverRes = (await serverRegister()).body
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append(
|
||||
'subscription[endpoint]',
|
||||
'https://testpush.home.xmflsct.com/test1'
|
||||
)
|
||||
formData.append('subscription[keys][p256dh]', serverRes.publicKey)
|
||||
formData.append('subscription[keys][auth]', serverRes.auth)
|
||||
|
||||
const res = await apiInstance<Mastodon.PushSubscription>({
|
||||
method: 'post',
|
||||
url: 'push/subscription',
|
||||
body: formData
|
||||
})
|
||||
return res.body
|
||||
|
||||
// if (Platform.OS === 'android') {
|
||||
// Notifications.setNotificationChannelAsync('default', {
|
||||
// name: 'default',
|
||||
// importance: Notifications.AndroidImportance.MAX,
|
||||
// vibrationPattern: [0, 250, 250, 250],
|
||||
// lightColor: '#FF231F7C'
|
||||
// })
|
||||
// }
|
||||
}
|
||||
|
||||
export default pushEnable
|
40
src/utils/slices/instances/remove.ts
Normal file
40
src/utils/slices/instances/remove.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit'
|
||||
import { RootState } from '@root/store'
|
||||
import * as AuthSession from 'expo-auth-session'
|
||||
|
||||
const removeInstance = createAsyncThunk(
|
||||
'instances/remove',
|
||||
async (index: number): Promise<number> => {
|
||||
const { store } = require('@root/store')
|
||||
const instances = (store.getState() as RootState).instances.instances
|
||||
|
||||
if (index !== -1) {
|
||||
const currentInstance = instances[index]
|
||||
|
||||
let revoked = undefined
|
||||
try {
|
||||
revoked = await AuthSession.revokeAsync(
|
||||
{
|
||||
clientId: currentInstance.appData.clientId,
|
||||
clientSecret: currentInstance.appData.clientSecret,
|
||||
token: currentInstance.token,
|
||||
scopes: ['read', 'write', 'follow', 'push']
|
||||
},
|
||||
{
|
||||
revocationEndpoint: `https://${currentInstance.url}/oauth/revoke`
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
console.warn('Revoking error')
|
||||
}
|
||||
|
||||
if (!revoked) {
|
||||
console.warn('Revoking error')
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(index)
|
||||
}
|
||||
)
|
||||
|
||||
export default removeInstance
|
12
src/utils/slices/instances/updateAccountPreferences.ts
Normal file
12
src/utils/slices/instances/updateAccountPreferences.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import apiInstance from '@api/instance'
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit'
|
||||
|
||||
export const updateAccountPreferences = createAsyncThunk(
|
||||
'instances/updateAccountPreferences',
|
||||
async (): Promise<Mastodon.Preferences> => {
|
||||
return apiInstance<Mastodon.Preferences>({
|
||||
method: 'get',
|
||||
url: `preferences`
|
||||
}).then(res => res.body)
|
||||
}
|
||||
)
|
17
src/utils/slices/instances/updatePush.ts
Normal file
17
src/utils/slices/instances/updatePush.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit'
|
||||
import { Instance } from '../instancesSlice'
|
||||
import pushDisable from './push/disable'
|
||||
import pushEnable from './push/enable'
|
||||
|
||||
export const updatePush = createAsyncThunk(
|
||||
'instances/updatePush',
|
||||
async (
|
||||
enable: boolean
|
||||
): Promise<Instance['push']['subscription'] | boolean> => {
|
||||
if (enable) {
|
||||
return pushEnable()
|
||||
} else {
|
||||
return pushDisable()
|
||||
}
|
||||
}
|
||||
)
|
@ -1,13 +1,15 @@
|
||||
import client from '@api/client'
|
||||
import analytics from '@components/analytics'
|
||||
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
import { RootState } from '@root/store'
|
||||
import { ComposeStateDraft } from '@screens/Compose/utils/types'
|
||||
import * as AuthSession from 'expo-auth-session'
|
||||
import * as Localization from 'expo-localization'
|
||||
import { findIndex } from 'lodash'
|
||||
import addInstance from './instances/add'
|
||||
import removeInstance from './instances/remove'
|
||||
import { updateAccountPreferences } from './instances/updateAccountPreferences'
|
||||
import { updatePush } from './instances/updatePush'
|
||||
|
||||
export type InstanceLocal = {
|
||||
export type Instance = {
|
||||
active: boolean
|
||||
appData: {
|
||||
clientId: string
|
||||
clientSecret: string
|
||||
@ -27,245 +29,105 @@ export type InstanceLocal = {
|
||||
readTime?: Mastodon.Notification['created_at']
|
||||
latestTime?: Mastodon.Notification['created_at']
|
||||
}
|
||||
push: {
|
||||
loading: boolean
|
||||
enabled: boolean
|
||||
subscription?: Mastodon.PushSubscription
|
||||
}
|
||||
drafts: ComposeStateDraft[]
|
||||
}
|
||||
|
||||
export type InstancesState = {
|
||||
local: {
|
||||
activeIndex: number | null
|
||||
instances: InstanceLocal[]
|
||||
}
|
||||
|
||||
remote: {
|
||||
url: string
|
||||
}
|
||||
instances: Instance[]
|
||||
}
|
||||
|
||||
export const updateLocalAccountPreferences = createAsyncThunk(
|
||||
'instances/updateLocalAccountPreferences',
|
||||
async (): Promise<Mastodon.Preferences> => {
|
||||
const res = await client<Mastodon.Preferences>({
|
||||
method: 'get',
|
||||
instance: 'local',
|
||||
url: `preferences`
|
||||
})
|
||||
|
||||
return Promise.resolve(res.body)
|
||||
}
|
||||
)
|
||||
|
||||
export const localAddInstance = createAsyncThunk(
|
||||
'instances/localAddInstance',
|
||||
async ({
|
||||
url,
|
||||
token,
|
||||
instance,
|
||||
max_toot_chars = 500,
|
||||
appData
|
||||
}: {
|
||||
url: InstanceLocal['url']
|
||||
token: InstanceLocal['token']
|
||||
instance: Mastodon.Instance
|
||||
max_toot_chars?: number
|
||||
appData: InstanceLocal['appData']
|
||||
}): Promise<{ type: 'add' | 'overwrite'; data: InstanceLocal }> => {
|
||||
const { store } = require('@root/store')
|
||||
const instanceLocal: InstancesState['local'] = store.getState().instances
|
||||
.local
|
||||
|
||||
const {
|
||||
body: { id, acct, avatar_static }
|
||||
} = await client<Mastodon.Account>({
|
||||
method: 'get',
|
||||
instance: 'remote',
|
||||
instanceDomain: url,
|
||||
url: `accounts/verify_credentials`,
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
})
|
||||
|
||||
let type: 'add' | 'overwrite'
|
||||
if (
|
||||
instanceLocal.instances.filter(instance => {
|
||||
if (instance) {
|
||||
if (instance.url === url && instance.account.id === id) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}).length
|
||||
) {
|
||||
type = 'overwrite'
|
||||
} else {
|
||||
type = 'add'
|
||||
}
|
||||
|
||||
const { body: preferences } = await client<Mastodon.Preferences>({
|
||||
method: 'get',
|
||||
instance: 'remote',
|
||||
instanceDomain: url,
|
||||
url: `preferences`,
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
})
|
||||
|
||||
return Promise.resolve({
|
||||
type,
|
||||
data: {
|
||||
appData,
|
||||
url,
|
||||
token,
|
||||
uri: instance.uri,
|
||||
urls: instance.urls,
|
||||
max_toot_chars,
|
||||
account: {
|
||||
id,
|
||||
acct,
|
||||
avatarStatic: avatar_static,
|
||||
preferences
|
||||
},
|
||||
notification: {
|
||||
readTime: undefined,
|
||||
latestTime: undefined
|
||||
},
|
||||
drafts: []
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
export const localRemoveInstance = createAsyncThunk(
|
||||
'instances/localRemoveInstance',
|
||||
async (index?: InstancesState['local']['activeIndex']): Promise<number> => {
|
||||
const { store } = require('@root/store')
|
||||
const instanceLocal: InstancesState['local'] = store.getState().instances
|
||||
.local
|
||||
|
||||
if (index) {
|
||||
return Promise.resolve(index)
|
||||
} else {
|
||||
if (instanceLocal.activeIndex !== null) {
|
||||
const currentInstance =
|
||||
instanceLocal.instances[instanceLocal.activeIndex]
|
||||
|
||||
let revoked = undefined
|
||||
try {
|
||||
revoked = await AuthSession.revokeAsync(
|
||||
{
|
||||
clientId: currentInstance.appData.clientId,
|
||||
clientSecret: currentInstance.appData.clientSecret,
|
||||
token: currentInstance.token,
|
||||
scopes: ['read', 'write', 'follow', 'push']
|
||||
},
|
||||
{
|
||||
revocationEndpoint: `https://${currentInstance.url}/oauth/revoke`
|
||||
}
|
||||
)
|
||||
} catch {}
|
||||
|
||||
if (!revoked) {
|
||||
console.warn('Revoking error')
|
||||
}
|
||||
|
||||
return Promise.resolve(instanceLocal.activeIndex)
|
||||
} else {
|
||||
throw new Error('Active index invalid, cannot remove instance')
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export const instancesInitialState: InstancesState = {
|
||||
local: {
|
||||
activeIndex: null,
|
||||
instances: []
|
||||
},
|
||||
remote: {
|
||||
url: Localization.locale.includes('zh') ? 'm.cmx.im' : 'mastodon.social'
|
||||
}
|
||||
instances: []
|
||||
}
|
||||
|
||||
const findInstanceActive = (state: Instance[]) =>
|
||||
state.findIndex(instance => instance.active)
|
||||
|
||||
const instancesSlice = createSlice({
|
||||
name: 'instances',
|
||||
initialState: instancesInitialState,
|
||||
reducers: {
|
||||
updateLocalActiveIndex: (state, action: PayloadAction<InstanceLocal>) => {
|
||||
state.local.activeIndex = state.local.instances.findIndex(
|
||||
instance =>
|
||||
updateInstanceActive: ({ instances }, action: PayloadAction<Instance>) => {
|
||||
instances = instances.map(instance => {
|
||||
instance.active =
|
||||
instance.url === action.payload.url &&
|
||||
instance.token === action.payload.token &&
|
||||
instance.account.id === action.payload.account.id
|
||||
)
|
||||
return instance
|
||||
})
|
||||
},
|
||||
updateLocalAccount: (
|
||||
state,
|
||||
action: PayloadAction<
|
||||
Pick<InstanceLocal['account'], 'acct' & 'avatarStatic'>
|
||||
>
|
||||
updateInstanceAccount: (
|
||||
{ instances },
|
||||
action: PayloadAction<Pick<Instance['account'], 'acct' & 'avatarStatic'>>
|
||||
) => {
|
||||
if (state.local.activeIndex !== null) {
|
||||
state.local.instances[state.local.activeIndex].account = {
|
||||
...state.local.instances[state.local.activeIndex].account,
|
||||
...action.payload
|
||||
}
|
||||
const activeIndex = findInstanceActive(instances)
|
||||
instances[activeIndex].account = {
|
||||
...instances[activeIndex].account,
|
||||
...action.payload
|
||||
}
|
||||
},
|
||||
updateLocalNotification: (
|
||||
state,
|
||||
action: PayloadAction<Partial<InstanceLocal['notification']>>
|
||||
updateInstanceNotification: (
|
||||
{ instances },
|
||||
action: PayloadAction<Partial<Instance['notification']>>
|
||||
) => {
|
||||
if (state.local.activeIndex !== null) {
|
||||
state.local.instances[state.local.activeIndex].notification = {
|
||||
...state.local.instances[state.local.activeIndex].notification,
|
||||
...action.payload
|
||||
}
|
||||
const activeIndex = findInstanceActive(instances)
|
||||
instances[activeIndex].notification = {
|
||||
...instances[activeIndex].notification,
|
||||
...action.payload
|
||||
}
|
||||
},
|
||||
updateLocalDraft: (state, action: PayloadAction<ComposeStateDraft>) => {
|
||||
if (state.local.activeIndex !== null) {
|
||||
const draftIndex = findIndex(
|
||||
state.local.instances[state.local.activeIndex].drafts,
|
||||
['timestamp', action.payload.timestamp]
|
||||
)
|
||||
if (draftIndex === -1) {
|
||||
state.local.instances[state.local.activeIndex].drafts.unshift(
|
||||
action.payload
|
||||
)
|
||||
} else {
|
||||
state.local.instances[state.local.activeIndex].drafts[draftIndex] =
|
||||
action.payload
|
||||
}
|
||||
updateInstanceDraft: (
|
||||
{ instances },
|
||||
action: PayloadAction<ComposeStateDraft>
|
||||
) => {
|
||||
const activeIndex = findInstanceActive(instances)
|
||||
const draftIndex = findIndex(instances[activeIndex].drafts, [
|
||||
'timestamp',
|
||||
action.payload.timestamp
|
||||
])
|
||||
if (draftIndex === -1) {
|
||||
instances[activeIndex].drafts.unshift(action.payload)
|
||||
} else {
|
||||
instances[activeIndex].drafts[draftIndex] = action.payload
|
||||
}
|
||||
},
|
||||
removeLocalDraft: (
|
||||
state,
|
||||
removeInstanceDraft: (
|
||||
{ instances },
|
||||
action: PayloadAction<ComposeStateDraft['timestamp']>
|
||||
) => {
|
||||
if (state.local.activeIndex !== null) {
|
||||
state.local.instances[
|
||||
state.local.activeIndex
|
||||
].drafts = state.local.instances[
|
||||
state.local.activeIndex
|
||||
].drafts?.filter(draft => draft.timestamp !== action.payload)
|
||||
}
|
||||
const activeIndex = findInstanceActive(instances)
|
||||
instances[activeIndex].drafts = instances[activeIndex].drafts?.filter(
|
||||
draft => draft.timestamp !== action.payload
|
||||
)
|
||||
}
|
||||
},
|
||||
extraReducers: builder => {
|
||||
builder
|
||||
.addCase(localAddInstance.fulfilled, (state, action) => {
|
||||
.addCase(addInstance.fulfilled, (state, action) => {
|
||||
switch (action.payload.type) {
|
||||
case 'add':
|
||||
state.local.instances.push(action.payload.data)
|
||||
state.local.activeIndex = state.local.instances.length - 1
|
||||
state.instances.length &&
|
||||
(state.instances = state.instances.map(instance => {
|
||||
instance.active = false
|
||||
return instance
|
||||
}))
|
||||
state.instances.push(action.payload.data)
|
||||
break
|
||||
case 'overwrite':
|
||||
state.local.instances = state.local.instances.map(instance => {
|
||||
console.log('overwriting')
|
||||
state.instances = state.instances.map(instance => {
|
||||
if (
|
||||
instance.url === action.payload.data.url &&
|
||||
instance.account.id === action.payload.data.account.id
|
||||
) {
|
||||
return action.payload.data
|
||||
} else {
|
||||
instance.active = false
|
||||
return instance
|
||||
}
|
||||
})
|
||||
@ -273,76 +135,99 @@ const instancesSlice = createSlice({
|
||||
|
||||
analytics('login')
|
||||
})
|
||||
.addCase(localAddInstance.rejected, (state, action) => {
|
||||
console.error(state.local)
|
||||
.addCase(addInstance.rejected, (state, action) => {
|
||||
console.error(state.instances)
|
||||
console.error(action.error)
|
||||
})
|
||||
|
||||
.addCase(localRemoveInstance.fulfilled, (state, action) => {
|
||||
state.local.instances.splice(action.payload, 1)
|
||||
state.local.activeIndex = state.local.instances.length
|
||||
? state.local.instances.length - 1
|
||||
: null
|
||||
.addCase(removeInstance.fulfilled, (state, action) => {
|
||||
state.instances.splice(action.payload, 1)
|
||||
state.instances.length &&
|
||||
(state.instances[state.instances.length - 1].active = true)
|
||||
|
||||
analytics('logout')
|
||||
})
|
||||
.addCase(localRemoveInstance.rejected, (state, action) => {
|
||||
console.error(state.local)
|
||||
.addCase(removeInstance.rejected, (state, action) => {
|
||||
console.error(state)
|
||||
console.error(action.error)
|
||||
})
|
||||
|
||||
.addCase(updateLocalAccountPreferences.fulfilled, (state, action) => {
|
||||
state.local.instances[state.local.activeIndex!].account.preferences =
|
||||
action.payload
|
||||
.addCase(updateAccountPreferences.fulfilled, (state, action) => {
|
||||
const activeIndex = findInstanceActive(state.instances)
|
||||
state.instances[activeIndex].account.preferences = action.payload
|
||||
})
|
||||
.addCase(updateLocalAccountPreferences.rejected, (_, action) => {
|
||||
.addCase(updateAccountPreferences.rejected, (_, action) => {
|
||||
console.error(action.error)
|
||||
})
|
||||
|
||||
.addCase(updatePush.fulfilled, (state, action) => {
|
||||
const activeIndex = findInstanceActive(state.instances)
|
||||
if (typeof action.payload === 'boolean') {
|
||||
state.instances[activeIndex].push.enabled = action.payload
|
||||
} else {
|
||||
state.instances[activeIndex].push.enabled = true
|
||||
state.instances[activeIndex].push.subscription = action.payload
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export const getLocalActiveIndex = ({ instances: { local } }: RootState) =>
|
||||
local.activeIndex
|
||||
export const getLocalInstances = ({ instances: { local } }: RootState) =>
|
||||
local.instances
|
||||
export const getLocalInstance = ({ instances: { local } }: RootState) =>
|
||||
local.activeIndex !== null ? local.instances[local.activeIndex] : undefined
|
||||
export const getLocalUrl = ({ instances: { local } }: RootState) =>
|
||||
local.activeIndex !== null
|
||||
? local.instances[local.activeIndex].url
|
||||
: undefined
|
||||
export const getLocalUri = ({ instances: { local } }: RootState) =>
|
||||
local.activeIndex !== null
|
||||
? local.instances[local.activeIndex].uri
|
||||
: undefined
|
||||
export const getLocalUrls = ({ instances: { local } }: RootState) =>
|
||||
local.activeIndex !== null
|
||||
? local.instances[local.activeIndex].urls
|
||||
: undefined
|
||||
export const getLocalMaxTootChar = ({ instances: { local } }: RootState) =>
|
||||
local.activeIndex !== null
|
||||
? local.instances[local.activeIndex].max_toot_chars
|
||||
: 500
|
||||
export const getLocalAccount = ({ instances: { local } }: RootState) =>
|
||||
local.activeIndex !== null
|
||||
? local.instances[local.activeIndex].account
|
||||
: undefined
|
||||
export const getLocalNotification = ({ instances: { local } }: RootState) =>
|
||||
local.activeIndex !== null
|
||||
? local.instances[local.activeIndex].notification
|
||||
: undefined
|
||||
export const getLocalDrafts = ({ instances: { local } }: RootState) =>
|
||||
local.activeIndex !== null
|
||||
? local.instances[local.activeIndex].drafts
|
||||
: undefined
|
||||
export const getRemoteUrl = ({ instances: { remote } }: RootState) => remote.url
|
||||
export const getInstanceActive = ({ instances: { instances } }: RootState) =>
|
||||
findInstanceActive(instances)
|
||||
|
||||
export const getInstances = ({ instances: { instances } }: RootState) =>
|
||||
instances
|
||||
|
||||
export const getInstance = ({ instances: { instances } }: RootState) => {
|
||||
const instanceActive = findInstanceActive(instances)
|
||||
return instanceActive !== -1 ? instances[instanceActive] : null
|
||||
}
|
||||
|
||||
export const getInstanceUrl = ({ instances: { instances } }: RootState) => {
|
||||
const instanceActive = findInstanceActive(instances)
|
||||
return instanceActive !== -1 ? instances[instanceActive].url : null
|
||||
}
|
||||
|
||||
export const getInstanceUri = ({ instances: { instances } }: RootState) => {
|
||||
const instanceActive = findInstanceActive(instances)
|
||||
return instanceActive !== -1 ? instances[instanceActive].uri : null
|
||||
}
|
||||
|
||||
export const getInstanceUrls = ({ instances: { instances } }: RootState) => {
|
||||
const instanceActive = findInstanceActive(instances)
|
||||
return instanceActive !== -1 ? instances[instanceActive].urls : null
|
||||
}
|
||||
|
||||
export const getInstanceMaxTootChar = ({
|
||||
instances: { instances }
|
||||
}: RootState) => {
|
||||
const instanceActive = findInstanceActive(instances)
|
||||
return instanceActive !== -1 ? instances[instanceActive].max_toot_chars : null
|
||||
}
|
||||
|
||||
export const getInstanceAccount = ({ instances: { instances } }: RootState) => {
|
||||
const instanceActive = findInstanceActive(instances)
|
||||
return instanceActive !== -1 ? instances[instanceActive].account : null
|
||||
}
|
||||
|
||||
export const getInstanceNotification = ({
|
||||
instances: { instances }
|
||||
}: RootState) => {
|
||||
const instanceActive = findInstanceActive(instances)
|
||||
return instanceActive !== -1 ? instances[instanceActive].notification : null
|
||||
}
|
||||
|
||||
export const getInstanceDrafts = ({ instances: { instances } }: RootState) => {
|
||||
const instanceActive = findInstanceActive(instances)
|
||||
return instanceActive !== -1 ? instances[instanceActive].drafts : null
|
||||
}
|
||||
|
||||
export const {
|
||||
updateLocalActiveIndex,
|
||||
updateLocalAccount,
|
||||
updateLocalNotification,
|
||||
updateLocalDraft,
|
||||
removeLocalDraft
|
||||
updateInstanceActive,
|
||||
updateInstanceAccount,
|
||||
updateInstanceNotification,
|
||||
updateInstanceDraft,
|
||||
removeInstanceDraft
|
||||
} = instancesSlice.actions
|
||||
|
||||
export default instancesSlice.reducer
|
||||
|
@ -9,6 +9,14 @@ enum availableLanguages {
|
||||
'en'
|
||||
}
|
||||
|
||||
export const changeAnalytics = createAsyncThunk(
|
||||
'settings/changeAnalytics',
|
||||
async (newValue: SettingsState['analytics']) => {
|
||||
await Analytics.setAnalyticsCollectionEnabled(newValue)
|
||||
return newValue
|
||||
}
|
||||
)
|
||||
|
||||
export type SettingsState = {
|
||||
language: keyof availableLanguages
|
||||
theme: 'light' | 'dark' | 'auto'
|
||||
@ -17,6 +25,9 @@ export type SettingsState = {
|
||||
}
|
||||
|
||||
export const settingsInitialState = {
|
||||
notification: {
|
||||
enabled: false
|
||||
},
|
||||
language: Object.keys(
|
||||
pickBy(availableLanguages, (_, key) => Localization.locale.includes(key))
|
||||
)
|
||||
@ -31,14 +42,6 @@ export const settingsInitialState = {
|
||||
analytics: true
|
||||
}
|
||||
|
||||
export const changeAnalytics = createAsyncThunk(
|
||||
'settings/changeAnalytics',
|
||||
async (newValue: SettingsState['analytics']) => {
|
||||
await Analytics.setAnalyticsCollectionEnabled(newValue)
|
||||
return newValue
|
||||
}
|
||||
)
|
||||
|
||||
const settingsSlice = createSlice({
|
||||
name: 'settings',
|
||||
initialState: settingsInitialState as SettingsState,
|
||||
|
70
yarn.lock
70
yarn.lock
@ -1224,6 +1224,11 @@
|
||||
dependencies:
|
||||
"@hapi/hoek" "^8.3.0"
|
||||
|
||||
"@ide/backoff@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@ide/backoff/-/backoff-1.0.0.tgz#466842c25bd4a4833e0642fab41ccff064010176"
|
||||
integrity sha512-F0YfUDjvT+Mtt/R4xdl2X0EYCHMMiJqNLdxHD++jDT5ydEFIyqbCHh51Qx2E211dgZprPKhV7sHmnXKpLuvc5g==
|
||||
|
||||
"@istanbuljs/load-nyc-config@^1.0.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
|
||||
@ -2936,6 +2941,16 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
|
||||
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
|
||||
|
||||
assert@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/assert/-/assert-2.0.0.tgz#95fc1c616d48713510680f2eaf2d10dd22e02d32"
|
||||
integrity sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==
|
||||
dependencies:
|
||||
es6-object-assign "^1.1.0"
|
||||
is-nan "^1.2.1"
|
||||
object-is "^1.0.1"
|
||||
util "^0.12.0"
|
||||
|
||||
assign-symbols@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
|
||||
@ -3197,7 +3212,7 @@ babel-preset-jest@^26.6.2:
|
||||
babel-plugin-jest-hoist "^26.6.2"
|
||||
babel-preset-current-node-syntax "^1.0.0"
|
||||
|
||||
badgin@^1.1.2:
|
||||
badgin@^1.1.2, badgin@^1.1.5:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/badgin/-/badgin-1.2.2.tgz#cbb0b71b047230c681a68911eb24136f0632adc6"
|
||||
integrity sha512-XtoSjNhy2D09qGiLhFWBJmBwBlmleQuwyYyjddWNCJ3gqGRBOBR25VGcd8CAOSghpEUmghB3LD4NpHrUG89zCg==
|
||||
@ -4236,6 +4251,11 @@ es-to-primitive@^1.2.1:
|
||||
is-date-object "^1.0.1"
|
||||
is-symbol "^1.0.2"
|
||||
|
||||
es6-object-assign@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c"
|
||||
integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=
|
||||
|
||||
escalade@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||
@ -4455,6 +4475,14 @@ expo-constants@*:
|
||||
"@expo/config" "^3.3.18"
|
||||
uuid "^3.3.2"
|
||||
|
||||
expo-constants@9.3.1:
|
||||
version "9.3.1"
|
||||
resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-9.3.1.tgz#1cab4896ea5e626fc7f19f49893526c94b481443"
|
||||
integrity sha512-58ENdEeVxZ29INv87IqZf5ZD4+NjvxzrHOCFha8iW3TbTL8GXY/6QjlIZ/yGHu4TwBoatozeJQ+9WCCz/hXM0A==
|
||||
dependencies:
|
||||
fbjs "1.0.0"
|
||||
uuid "^3.3.2"
|
||||
|
||||
expo-constants@^9.3.3, expo-constants@~9.3.0, expo-constants@~9.3.3:
|
||||
version "9.3.5"
|
||||
resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-9.3.5.tgz#78085763e8ed100a5f2df7c682fd99631aa03d5e"
|
||||
@ -4560,6 +4588,19 @@ expo-location@~10.0.0:
|
||||
resolved "https://registry.yarnpkg.com/expo-location/-/expo-location-10.0.0.tgz#2923411649434f2f079343b163b13c5c9eee8b2d"
|
||||
integrity sha512-QLEb0iaBv4/blLxxfKRj2/HPisY+1t+g6MgegqZ1j1U/0qih4dvzUQrxie9ZOZyQB9gnXFCRnZv3QzHEb52dcA==
|
||||
|
||||
expo-notifications@~0.8.2:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/expo-notifications/-/expo-notifications-0.8.2.tgz#69e04a4e48ec6bafaeb354d284fbc23c26f2d62d"
|
||||
integrity sha512-eX/HB96FqXzSMAwtA/fhxB1tGYELQywPm7oBTfnALHH3MFHy1bW7NZYGcU82sDF+DF09uLZ4Fn4p5ValMWA5TA==
|
||||
dependencies:
|
||||
"@ide/backoff" "^1.0.0"
|
||||
abort-controller "^3.0.0"
|
||||
assert "^2.0.0"
|
||||
badgin "^1.1.5"
|
||||
expo-application "~2.4.1"
|
||||
expo-constants "9.3.1"
|
||||
uuid "^3.4.0"
|
||||
|
||||
expo-permissions@~10.0.0:
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/expo-permissions/-/expo-permissions-10.0.0.tgz#5b31c54d561d00c7e46cd02321bc3704c51c584b"
|
||||
@ -5612,11 +5653,24 @@ is-generator-fn@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
|
||||
integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==
|
||||
|
||||
is-generator-function@^1.0.7:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.8.tgz#dfb5c2b120e02b0a8d9d2c6806cd5621aa922f7b"
|
||||
integrity sha512-2Omr/twNtufVZFr1GhxjOMFPAj2sjc/dKaIqBhvo4qciXfJmITGH6ZGd8eZYNHza8t1y0e01AuqRhJwfWp26WQ==
|
||||
|
||||
is-map@^2.0.1, is-map@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127"
|
||||
integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==
|
||||
|
||||
is-nan@^1.2.1:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d"
|
||||
integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==
|
||||
dependencies:
|
||||
call-bind "^1.0.0"
|
||||
define-properties "^1.1.3"
|
||||
|
||||
is-negative-zero@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
|
||||
@ -7899,7 +7953,7 @@ object-inspect@^1.9.0:
|
||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a"
|
||||
integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==
|
||||
|
||||
object-is@^1.1.4:
|
||||
object-is@^1.0.1, object-is@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.4.tgz#63d6c83c00a43f4cbc9434eb9757c8a5b8565068"
|
||||
integrity sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg==
|
||||
@ -10218,6 +10272,18 @@ util-deprecate@~1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||
|
||||
util@^0.12.0:
|
||||
version "0.12.3"
|
||||
resolved "https://registry.yarnpkg.com/util/-/util-0.12.3.tgz#971bb0292d2cc0c892dab7c6a5d37c2bec707888"
|
||||
integrity sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog==
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
is-arguments "^1.0.4"
|
||||
is-generator-function "^1.0.7"
|
||||
is-typed-array "^1.1.3"
|
||||
safe-buffer "^5.1.2"
|
||||
which-typed-array "^1.1.2"
|
||||
|
||||
utils-merge@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||
|
Loading…
x
Reference in New Issue
Block a user