mirror of
https://github.com/tooot-app/app
synced 2024-12-12 09:10:19 +01:00
commit
751de9ff30
@ -197,7 +197,7 @@ private_lane :build_android do
|
|||||||
end
|
end
|
||||||
|
|
||||||
lane :build do
|
lane :build do
|
||||||
releaseExists = get_github_release(url: GITHUB_REPO, version: GITHUB_RELEASE, api_token: ENV['GH_PAT_GET_RELEASE'])
|
releaseExists = get_github_release(url: GITHUB_REPO, version: "v#{VERSION}", api_token: ENV['GH_PAT_GET_RELEASE'])
|
||||||
if releaseExists
|
if releaseExists
|
||||||
puts("Release #{GITHUB_RELEASE} exists. Continue with building React Native only.")
|
puts("Release #{GITHUB_RELEASE} exists. Continue with building React Native only.")
|
||||||
else
|
else
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"native": "210511",
|
"native": "210511",
|
||||||
"major": 2,
|
"major": 2,
|
||||||
"minor": 0,
|
"minor": 0,
|
||||||
"patch": 1,
|
"patch": 2,
|
||||||
"expo": "41.0.0"
|
"expo": "41.0.0"
|
||||||
},
|
},
|
||||||
"description": "tooot app for Mastodon",
|
"description": "tooot app for Mastodon",
|
||||||
|
9
src/@types/mastodon.d.ts
vendored
9
src/@types/mastodon.d.ts
vendored
@ -261,6 +261,15 @@ declare namespace Mastodon {
|
|||||||
verified_at: string | null
|
verified_at: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Filter = {
|
||||||
|
id: string
|
||||||
|
phrase: string
|
||||||
|
context: ('home' | 'notifications' | 'public' | 'thread' | 'account')[]
|
||||||
|
expires_at?: string
|
||||||
|
irreversible: boolean
|
||||||
|
whole_word: boolean
|
||||||
|
}
|
||||||
|
|
||||||
type List = {
|
type List = {
|
||||||
id: string
|
id: string
|
||||||
title: string
|
title: string
|
||||||
|
@ -13,6 +13,7 @@ import pushUseReceive from '@utils/push/useReceive'
|
|||||||
import pushUseRespond from '@utils/push/useRespond'
|
import pushUseRespond from '@utils/push/useRespond'
|
||||||
import { updatePreviousTab } from '@utils/slices/contextsSlice'
|
import { updatePreviousTab } from '@utils/slices/contextsSlice'
|
||||||
import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences'
|
import { updateAccountPreferences } from '@utils/slices/instances/updateAccountPreferences'
|
||||||
|
import { updateFilters } from '@utils/slices/instances/updateFilters'
|
||||||
import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice'
|
import { getInstanceActive, getInstances } from '@utils/slices/instancesSlice'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import { themes } from '@utils/styles/themes'
|
import { themes } from '@utils/styles/themes'
|
||||||
@ -106,6 +107,7 @@ const Screens: React.FC<Props> = ({ localCorrupt }) => {
|
|||||||
// Lazily update users's preferences, for e.g. composing default visibility
|
// Lazily update users's preferences, for e.g. composing default visibility
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (instanceActive !== -1) {
|
if (instanceActive !== -1) {
|
||||||
|
dispatch(updateFilters())
|
||||||
dispatch(updateAccountPreferences())
|
dispatch(updateAccountPreferences())
|
||||||
}
|
}
|
||||||
}, [instanceActive])
|
}, [instanceActive])
|
||||||
|
@ -69,7 +69,10 @@ const apiGeneral = async <T = unknown>({
|
|||||||
error.response.status,
|
error.response.status,
|
||||||
error.response.data.error
|
error.response.data.error
|
||||||
)
|
)
|
||||||
return Promise.reject(error.response.data.error)
|
return Promise.reject({
|
||||||
|
status: error.response.status,
|
||||||
|
message: error.response.data.error
|
||||||
|
})
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
// The request was made but no response was received
|
// The request was made but no response was received
|
||||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||||
|
@ -98,7 +98,10 @@ const apiInstance = async <T = unknown>({
|
|||||||
error.response.status,
|
error.response.status,
|
||||||
error.response.data.error
|
error.response.data.error
|
||||||
)
|
)
|
||||||
return Promise.reject(error.response.data.error)
|
return Promise.reject({
|
||||||
|
status: error.response.status,
|
||||||
|
message: error.response.data.error
|
||||||
|
})
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
// The request was made but no response was received
|
// The request was made but no response was received
|
||||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
import React, { useCallback, useMemo, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
AccessibilityProps,
|
AccessibilityProps,
|
||||||
Image,
|
Image,
|
||||||
@ -52,32 +52,30 @@ const GracefullyImage = React.memo(
|
|||||||
setImageDimensions
|
setImageDimensions
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const originalFailed = useRef(false)
|
const [originalFailed, setOriginalFailed] = useState(false)
|
||||||
const [imageLoaded, setImageLoaded] = useState(false)
|
const [imageLoaded, setImageLoaded] = useState(false)
|
||||||
|
|
||||||
const source = useMemo(() => {
|
const source = useMemo(() => {
|
||||||
if (originalFailed.current) {
|
if (originalFailed) {
|
||||||
return { uri: uri.remote || undefined }
|
return { uri: uri.remote || undefined }
|
||||||
} else {
|
} else {
|
||||||
return { uri: uri.original }
|
return { uri: uri.original }
|
||||||
}
|
}
|
||||||
}, [originalFailed.current])
|
}, [originalFailed])
|
||||||
const onLoad = useCallback(
|
|
||||||
({ nativeEvent }) => {
|
const onLoad = useCallback(() => {
|
||||||
setImageLoaded(true)
|
setImageLoaded(true)
|
||||||
setImageDimensions &&
|
if (setImageDimensions && source.uri) {
|
||||||
setImageDimensions({
|
Image.getSize(source.uri, (width, height) =>
|
||||||
width: nativeEvent.source.width,
|
setImageDimensions({ width, height })
|
||||||
height: nativeEvent.source.height
|
)
|
||||||
})
|
|
||||||
},
|
|
||||||
[source.uri]
|
|
||||||
)
|
|
||||||
const onError = useCallback(() => {
|
|
||||||
if (!originalFailed.current) {
|
|
||||||
originalFailed.current = true
|
|
||||||
}
|
}
|
||||||
}, [originalFailed.current])
|
}, [source.uri])
|
||||||
|
const onError = useCallback(() => {
|
||||||
|
if (!originalFailed) {
|
||||||
|
setOriginalFailed(true)
|
||||||
|
}
|
||||||
|
}, [originalFailed])
|
||||||
|
|
||||||
const previewView = useMemo(
|
const previewView = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
@ -76,11 +76,10 @@ const ParseEmojis = React.memo(
|
|||||||
: emojis[emojiIndex].url
|
: emojis[emojiIndex].url
|
||||||
if (validUrl.isHttpsUri(uri)) {
|
if (validUrl.isHttpsUri(uri)) {
|
||||||
return (
|
return (
|
||||||
<FastImage
|
<Text key={emojiShortcode + i}>
|
||||||
key={emojiShortcode + i}
|
{i === 0 ? ' ' : undefined}
|
||||||
source={{ uri }}
|
<FastImage source={{ uri }} style={styles.image} />
|
||||||
style={styles.image}
|
</Text>
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
|
@ -10,14 +10,16 @@ import TimelinePoll from '@components/Timeline/Shared/Poll'
|
|||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import { StackNavigationProp } from '@react-navigation/stack'
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
import { getInstance, getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
import htmlparser2 from 'htmlparser2-without-node-native'
|
||||||
import { uniqBy } from 'lodash'
|
import { uniqBy } from 'lodash'
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { Pressable, StyleSheet, View } from 'react-native'
|
import { Pressable, StyleSheet, View } from 'react-native'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import TimelineActionsUsers from './Shared/ActionsUsers'
|
import TimelineActionsUsers from './Shared/ActionsUsers'
|
||||||
|
import TimelineFiltered, { shouldFilter } from './Shared/Filtered'
|
||||||
import TimelineFullConversation from './Shared/FullConversation'
|
import TimelineFullConversation from './Shared/FullConversation'
|
||||||
import TimelineTranslate from './Shared/Translate'
|
import TimelineTranslate from './Shared/Translate'
|
||||||
|
|
||||||
@ -49,6 +51,16 @@ const TimelineDefault: React.FC<Props> = ({
|
|||||||
|
|
||||||
let actualStatus = item.reblog ? item.reblog : item
|
let actualStatus = item.reblog ? item.reblog : item
|
||||||
|
|
||||||
|
const ownAccount = actualStatus.account.id === instanceAccount?.id
|
||||||
|
|
||||||
|
if (
|
||||||
|
!highlighted &&
|
||||||
|
queryKey &&
|
||||||
|
shouldFilter({ status: actualStatus, queryKey })
|
||||||
|
) {
|
||||||
|
return <TimelineFiltered />
|
||||||
|
}
|
||||||
|
|
||||||
const onPress = useCallback(() => {
|
const onPress = useCallback(() => {
|
||||||
analytics('timeline_default_press', {
|
analytics('timeline_default_press', {
|
||||||
page: queryKey ? queryKey[1].page : origin
|
page: queryKey ? queryKey[1].page : origin
|
||||||
@ -118,7 +130,7 @@ const TimelineDefault: React.FC<Props> = ({
|
|||||||
statusId={actualStatus.id}
|
statusId={actualStatus.id}
|
||||||
poll={actualStatus.poll}
|
poll={actualStatus.poll}
|
||||||
reblog={item.reblog ? true : false}
|
reblog={item.reblog ? true : false}
|
||||||
sameAccount={actualStatus.account.id === instanceAccount?.id}
|
sameAccount={ownAccount}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{!disableDetails &&
|
{!disableDetails &&
|
||||||
|
@ -17,6 +17,7 @@ import { uniqBy } from 'lodash'
|
|||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { Pressable, StyleSheet, View } from 'react-native'
|
import { Pressable, StyleSheet, View } from 'react-native'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
import TimelineFiltered, { shouldFilter } from './Shared/Filtered'
|
||||||
import TimelineFullConversation from './Shared/FullConversation'
|
import TimelineFullConversation from './Shared/FullConversation'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -30,6 +31,13 @@ const TimelineNotifications: React.FC<Props> = ({
|
|||||||
queryKey,
|
queryKey,
|
||||||
highlighted = false
|
highlighted = false
|
||||||
}) => {
|
}) => {
|
||||||
|
if (
|
||||||
|
notification.status &&
|
||||||
|
shouldFilter({ status: notification.status, queryKey })
|
||||||
|
) {
|
||||||
|
return <TimelineFiltered />
|
||||||
|
}
|
||||||
|
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const instanceAccount = useSelector(
|
const instanceAccount = useSelector(
|
||||||
getInstanceAccount,
|
getInstanceAccount,
|
||||||
@ -38,6 +46,7 @@ const TimelineNotifications: React.FC<Props> = ({
|
|||||||
const navigation = useNavigation<
|
const navigation = useNavigation<
|
||||||
StackNavigationProp<Nav.TabLocalStackParamList>
|
StackNavigationProp<Nav.TabLocalStackParamList>
|
||||||
>()
|
>()
|
||||||
|
|
||||||
const actualAccount = notification.status
|
const actualAccount = notification.status
|
||||||
? notification.status.account
|
? notification.status.account
|
||||||
: notification.account
|
: notification.account
|
||||||
|
@ -8,7 +8,7 @@ import AttachmentVideo from '@components/Timeline/Shared/Attachment/Video'
|
|||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||||
import React, { useCallback, useMemo, useState } from 'react'
|
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Pressable, StyleSheet, View } from 'react-native'
|
import { Pressable, StyleSheet, View } from 'react-native'
|
||||||
|
|
||||||
@ -33,11 +33,13 @@ const TimelineAttachment = React.memo(
|
|||||||
haptics('Light')
|
haptics('Light')
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
let imageUrls: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'] = []
|
const imageUrls = useRef<
|
||||||
|
Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls']
|
||||||
|
>([])
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
const navigateToImagesViewer = (id: string) =>
|
const navigateToImagesViewer = (id: string) =>
|
||||||
navigation.navigate('Screen-ImagesViewer', {
|
navigation.navigate('Screen-ImagesViewer', {
|
||||||
imageUrls,
|
imageUrls: imageUrls.current,
|
||||||
id
|
id
|
||||||
})
|
})
|
||||||
const attachments = useMemo(
|
const attachments = useMemo(
|
||||||
@ -45,7 +47,7 @@ const TimelineAttachment = React.memo(
|
|||||||
status.media_attachments.map((attachment, index) => {
|
status.media_attachments.map((attachment, index) => {
|
||||||
switch (attachment.type) {
|
switch (attachment.type) {
|
||||||
case 'image':
|
case 'image':
|
||||||
imageUrls.push({
|
imageUrls.current.push({
|
||||||
id: attachment.id,
|
id: attachment.id,
|
||||||
preview_url: attachment.preview_url,
|
preview_url: attachment.preview_url,
|
||||||
url: attachment.url,
|
url: attachment.url,
|
||||||
@ -106,7 +108,7 @@ const TimelineAttachment = React.memo(
|
|||||||
attachment.remote_url?.endsWith('.png') ||
|
attachment.remote_url?.endsWith('.png') ||
|
||||||
attachment.remote_url?.endsWith('.gif')
|
attachment.remote_url?.endsWith('.gif')
|
||||||
) {
|
) {
|
||||||
imageUrls.push({
|
imageUrls.current.push({
|
||||||
id: attachment.id,
|
id: attachment.id,
|
||||||
preview_url: attachment.preview_url,
|
preview_url: attachment.preview_url,
|
||||||
url: attachment.url,
|
url: attachment.url,
|
||||||
|
105
src/components/Timeline/Shared/Filtered.tsx
Normal file
105
src/components/Timeline/Shared/Filtered.tsx
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { store } from '@root/store'
|
||||||
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
|
import { getInstance, getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||||
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
import htmlparser2 from 'htmlparser2-without-node-native'
|
||||||
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Text, View } from 'react-native'
|
||||||
|
|
||||||
|
const TimelineFiltered = React.memo(
|
||||||
|
() => {
|
||||||
|
const { theme } = useTheme()
|
||||||
|
const { t } = useTranslation('componentTimeline')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{ backgroundColor: theme.backgroundDefault }}>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
...StyleConstants.FontStyle.S,
|
||||||
|
color: theme.secondary,
|
||||||
|
textAlign: 'center',
|
||||||
|
paddingVertical: StyleConstants.Spacing.S,
|
||||||
|
paddingLeft: StyleConstants.Avatar.M + StyleConstants.Spacing.S
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('shared.filtered')}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
() => true
|
||||||
|
)
|
||||||
|
|
||||||
|
export const shouldFilter = ({
|
||||||
|
status,
|
||||||
|
queryKey
|
||||||
|
}: {
|
||||||
|
status: Mastodon.Status
|
||||||
|
queryKey: QueryKeyTimeline
|
||||||
|
}) => {
|
||||||
|
const instance = getInstance(store.getState())
|
||||||
|
const ownAccount =
|
||||||
|
getInstanceAccount(store.getState())?.id === status.account.id
|
||||||
|
|
||||||
|
let shouldFilter = false
|
||||||
|
if (queryKey && !ownAccount) {
|
||||||
|
const parser = new htmlparser2.Parser({
|
||||||
|
ontext (text: string) {
|
||||||
|
const checkFilter = (filter: Mastodon.Filter) => {
|
||||||
|
switch (filter.whole_word) {
|
||||||
|
case true:
|
||||||
|
if (new RegExp('\\b' + filter.phrase + '\\b').test(text)) {
|
||||||
|
shouldFilter = true
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case false:
|
||||||
|
if (new RegExp(filter.phrase).test(text)) {
|
||||||
|
shouldFilter = true
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
instance?.filters.forEach(filter => {
|
||||||
|
if (filter.expires_at) {
|
||||||
|
if (new Date().getTime() > new Date(filter.expires_at).getTime()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (queryKey[1].page) {
|
||||||
|
case 'Following':
|
||||||
|
case 'Local':
|
||||||
|
case 'List':
|
||||||
|
case 'Account_Default':
|
||||||
|
if (filter.context.includes('home')) {
|
||||||
|
checkFilter(filter)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'Notifications':
|
||||||
|
if (filter.context.includes('notifications')) {
|
||||||
|
checkFilter(filter)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'LocalPublic':
|
||||||
|
if (filter.context.includes('public')) {
|
||||||
|
checkFilter(filter)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'Toot':
|
||||||
|
if (filter.context.includes('thread')) {
|
||||||
|
checkFilter(filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
parser.write(status.content)
|
||||||
|
parser.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
return shouldFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TimelineFiltered
|
@ -31,7 +31,7 @@ const TimelineTranslate = React.memo(
|
|||||||
|
|
||||||
const settingsLanguage = useSelector(getSettingsLanguage)
|
const settingsLanguage = useSelector(getSettingsLanguage)
|
||||||
|
|
||||||
if (settingsLanguage.includes(tootLanguage)) {
|
if (settingsLanguage?.includes(tootLanguage)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +73,7 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"expandHint": "hidden content"
|
"expandHint": "hidden content"
|
||||||
},
|
},
|
||||||
|
"filtered": "Filtered",
|
||||||
"fullConversation": "Read conversations",
|
"fullConversation": "Read conversations",
|
||||||
"translate": {
|
"translate": {
|
||||||
"default": "Translate",
|
"default": "Translate",
|
||||||
|
@ -73,6 +73,7 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"expandHint": "隐藏内容"
|
"expandHint": "隐藏内容"
|
||||||
},
|
},
|
||||||
|
"filtered": "已过滤",
|
||||||
"fullConversation": "阅读全部对话",
|
"fullConversation": "阅读全部对话",
|
||||||
"translate": {
|
"translate": {
|
||||||
"default": "翻译",
|
"default": "翻译",
|
||||||
|
@ -21,8 +21,6 @@ const ComposeTextInput: React.FC = () => {
|
|||||||
borderBottomColor: theme.border
|
borderBottomColor: theme.border
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
autoCapitalize='none'
|
|
||||||
autoCorrect={false}
|
|
||||||
autoFocus
|
autoFocus
|
||||||
enablesReturnKeyAutomatically
|
enablesReturnKeyAutomatically
|
||||||
multiline
|
multiline
|
||||||
|
@ -52,8 +52,8 @@ const ImageItem = ({
|
|||||||
const scrollViewRef = useRef<ScrollView>(null)
|
const scrollViewRef = useRef<ScrollView>(null)
|
||||||
const [scaled, setScaled] = useState(false)
|
const [scaled, setScaled] = useState(false)
|
||||||
const [imageDimensions, setImageDimensions] = useState({
|
const [imageDimensions, setImageDimensions] = useState({
|
||||||
width: imageSrc.width || 0,
|
width: imageSrc.width || 1,
|
||||||
height: imageSrc.height || 0
|
height: imageSrc.height || 1
|
||||||
})
|
})
|
||||||
const handleDoubleTap = useDoubleTapToZoom(scrollViewRef, scaled, SCREEN)
|
const handleDoubleTap = useDoubleTapToZoom(scrollViewRef, scaled, SCREEN)
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ const saveIos = async ({ messageRef, mode, image }: CommonProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const saveAndroid = async ({ messageRef, mode, image }: CommonProps) => {
|
const saveAndroid = async ({ messageRef, mode, image }: CommonProps) => {
|
||||||
const fileUri: string = `${FileSystem.documentDirectory}test.jpg`
|
const fileUri: string = `${FileSystem.documentDirectory}${image.id}.jpg`
|
||||||
const downloadedFile: FileSystem.FileSystemDownloadResult = await FileSystem.downloadAsync(
|
const downloadedFile: FileSystem.FileSystemDownloadResult = await FileSystem.downloadAsync(
|
||||||
image.url,
|
image.url,
|
||||||
fileUri
|
fileUri
|
||||||
@ -80,7 +80,7 @@ const saveAndroid = async ({ messageRef, mode, image }: CommonProps) => {
|
|||||||
ref: messageRef,
|
ref: messageRef,
|
||||||
mode,
|
mode,
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: 'test'
|
message: i18next.t('screenImageViewer:content.save.succeed')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
@ -43,11 +43,7 @@ const netInfo = async (): Promise<{
|
|||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
log('error', 'netInfo', 'local credential check failed')
|
log('error', 'netInfo', 'local credential check failed')
|
||||||
if (
|
if (error.status && error.status == 401) {
|
||||||
error.status &&
|
|
||||||
typeof error.status === 'number' &&
|
|
||||||
error.status === 401
|
|
||||||
) {
|
|
||||||
store.dispatch(removeInstance(instance))
|
store.dispatch(removeInstance(instance))
|
||||||
}
|
}
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
|
12
src/utils/slices/instances/updateFilters.ts
Normal file
12
src/utils/slices/instances/updateFilters.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import apiInstance from '@api/instance'
|
||||||
|
import { createAsyncThunk } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
|
export const updateFilters = createAsyncThunk(
|
||||||
|
'instances/updateFilters',
|
||||||
|
async (): Promise<Mastodon.Filter[]> => {
|
||||||
|
return apiInstance<Mastodon.Filter[]>({
|
||||||
|
method: 'get',
|
||||||
|
url: `filters`
|
||||||
|
}).then(res => res.body)
|
||||||
|
}
|
||||||
|
)
|
@ -6,6 +6,7 @@ import { findIndex } from 'lodash'
|
|||||||
import addInstance from './instances/add'
|
import addInstance from './instances/add'
|
||||||
import removeInstance from './instances/remove'
|
import removeInstance from './instances/remove'
|
||||||
import { updateAccountPreferences } from './instances/updateAccountPreferences'
|
import { updateAccountPreferences } from './instances/updateAccountPreferences'
|
||||||
|
import { updateFilters } from './instances/updateFilters'
|
||||||
import { updateInstancePush } from './instances/updatePush'
|
import { updateInstancePush } from './instances/updatePush'
|
||||||
import { updateInstancePushAlert } from './instances/updatePushAlert'
|
import { updateInstancePushAlert } from './instances/updatePushAlert'
|
||||||
import { updateInstancePushDecode } from './instances/updatePushDecode'
|
import { updateInstancePushDecode } from './instances/updatePushDecode'
|
||||||
@ -29,6 +30,7 @@ export type Instance = {
|
|||||||
avatarStatic: Mastodon.Account['avatar_static']
|
avatarStatic: Mastodon.Account['avatar_static']
|
||||||
preferences: Mastodon.Preferences
|
preferences: Mastodon.Preferences
|
||||||
}
|
}
|
||||||
|
filters: Mastodon.Filter[]
|
||||||
notifications_filter: {
|
notifications_filter: {
|
||||||
follow: boolean
|
follow: boolean
|
||||||
favourite: boolean
|
favourite: boolean
|
||||||
@ -236,6 +238,15 @@ const instancesSlice = createSlice({
|
|||||||
console.error(action.error)
|
console.error(action.error)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Update Instance Account Filters
|
||||||
|
.addCase(updateFilters.fulfilled, (state, action) => {
|
||||||
|
const activeIndex = findInstanceActive(state.instances)
|
||||||
|
state.instances[activeIndex].filters = action.payload
|
||||||
|
})
|
||||||
|
.addCase(updateFilters.rejected, (_, action) => {
|
||||||
|
console.error(action.error)
|
||||||
|
})
|
||||||
|
|
||||||
// Update Instance Account Preferences
|
// Update Instance Account Preferences
|
||||||
.addCase(updateAccountPreferences.fulfilled, (state, action) => {
|
.addCase(updateAccountPreferences.fulfilled, (state, action) => {
|
||||||
const activeIndex = findInstanceActive(state.instances)
|
const activeIndex = findInstanceActive(state.instances)
|
||||||
|
@ -4,7 +4,7 @@ import * as Analytics from 'expo-firebase-analytics'
|
|||||||
import * as Localization from 'expo-localization'
|
import * as Localization from 'expo-localization'
|
||||||
import { pickBy } from 'lodash'
|
import { pickBy } from 'lodash'
|
||||||
|
|
||||||
enum availableLanguages {
|
enum AvailableLanguages {
|
||||||
'zh-Hans',
|
'zh-Hans',
|
||||||
'en'
|
'en'
|
||||||
}
|
}
|
||||||
@ -19,7 +19,7 @@ export const changeAnalytics = createAsyncThunk(
|
|||||||
|
|
||||||
export type SettingsState = {
|
export type SettingsState = {
|
||||||
fontsize: -1 | 0 | 1 | 2 | 3
|
fontsize: -1 | 0 | 1 | 2 | 3
|
||||||
language: keyof availableLanguages
|
language: string
|
||||||
theme: 'light' | 'dark' | 'auto'
|
theme: 'light' | 'dark' | 'auto'
|
||||||
browser: 'internal' | 'external'
|
browser: 'internal' | 'external'
|
||||||
analytics: boolean
|
analytics: boolean
|
||||||
@ -31,10 +31,10 @@ export const settingsInitialState = {
|
|||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
language: Object.keys(
|
language: Object.keys(
|
||||||
pickBy(availableLanguages, (_, key) => Localization.locale.includes(key))
|
pickBy(AvailableLanguages, (_, key) => Localization.locale.includes(key))
|
||||||
)
|
)
|
||||||
? Object.keys(
|
? Object.keys(
|
||||||
pickBy(availableLanguages, (_, key) =>
|
pickBy(AvailableLanguages, (_, key) =>
|
||||||
Localization.locale.includes(key)
|
Localization.locale.includes(key)
|
||||||
)
|
)
|
||||||
)[0]
|
)[0]
|
||||||
|
Loading…
Reference in New Issue
Block a user