mirror of https://github.com/tooot-app/app
Merge branch 'main' into candidate
This commit is contained in:
commit
fbf282c78a
|
@ -1,4 +1,3 @@
|
||||||
Enjoy toooting! This version includes following improvements and fixes:
|
Enjoy toooting! This version includes following improvements and fixes:
|
||||||
- Align filter experience with v4.0 and above
|
- Fixed wrongly update notification
|
||||||
- Supports enlarging user's avatar and banner
|
- Fix some random crashes
|
||||||
- Fix iPad weird sizing (not optimisation)
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Client open source per Mastodon
|
App open source per Mastodon
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
toooting愉快!此版本包括以下改进和修复:
|
toooting愉快!此版本包括以下改进和修复:
|
||||||
- 改进过滤体验,与v4.0以上版本一致
|
- 修复错误的升级通知
|
||||||
- 支持查看用户的头像和横幅图片
|
- 修复部分应用崩溃
|
||||||
- 修复iPad部分尺寸问题(非优化)
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "tooot",
|
"name": "tooot",
|
||||||
"version": "4.7.1",
|
"version": "4.7.2",
|
||||||
"description": "tooot for Mastodon",
|
"description": "tooot for Mastodon",
|
||||||
"author": "xmflsct <me@xmflsct.com>",
|
"author": "xmflsct <me@xmflsct.com>",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
|
|
|
@ -44,7 +44,10 @@ const apiGeneral = async <T = unknown>({
|
||||||
...userAgent,
|
...userAgent,
|
||||||
...headers
|
...headers
|
||||||
},
|
},
|
||||||
...(body && { data: body })
|
...(body &&
|
||||||
|
(body instanceof FormData
|
||||||
|
? (body as (FormData & { _parts: [][] }) | undefined)?._parts?.length
|
||||||
|
: Object.keys(body).length) && { data: body })
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
let links: {
|
let links: {
|
||||||
|
|
|
@ -64,7 +64,7 @@ const apiInstance = async <T = unknown>({
|
||||||
Authorization: `Bearer ${token}`
|
Authorization: `Bearer ${token}`
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
...(body && { data: body }),
|
...((body as (FormData & { _parts: [][] }) | undefined)?._parts.length && { data: body }),
|
||||||
...extras
|
...extras
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
|
|
@ -47,7 +47,10 @@ const apiTooot = async <T = unknown>({
|
||||||
...userAgent,
|
...userAgent,
|
||||||
...headers
|
...headers
|
||||||
},
|
},
|
||||||
...(body && { data: body })
|
...(body &&
|
||||||
|
(body instanceof FormData
|
||||||
|
? (body as (FormData & { _parts: [][] }) | undefined)?._parts?.length
|
||||||
|
: Object.keys(body).length) && { data: body })
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
|
|
|
@ -53,26 +53,17 @@ const GracefullyImage = ({
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { reduceMotionEnabled } = useAccessibility()
|
const { reduceMotionEnabled } = useAccessibility()
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
const [originalFailed, setOriginalFailed] = useState(false)
|
|
||||||
const [imageLoaded, setImageLoaded] = useState(false)
|
const [imageLoaded, setImageLoaded] = useState(false)
|
||||||
|
|
||||||
const source = originalFailed
|
const source = {
|
||||||
? { uri: uri.remote || undefined }
|
uri: reduceMotionEnabled && uri.static ? uri.static : uri.original
|
||||||
: {
|
}
|
||||||
uri: reduceMotionEnabled && uri.static ? uri.static : uri.original
|
|
||||||
}
|
|
||||||
|
|
||||||
const onLoad = () => {
|
const onLoad = () => {
|
||||||
setImageLoaded(true)
|
setImageLoaded(true)
|
||||||
if (setImageDimensions && source.uri) {
|
if (setImageDimensions && source.uri) {
|
||||||
Image.getSize(source.uri, (width, height) => setImageDimensions({ width, height }))
|
Image.getSize(source.uri, (width, height) => setImageDimensions({ width, height }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const onError = () => {
|
|
||||||
if (!originalFailed) {
|
|
||||||
setOriginalFailed(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const blurhashView = useMemo(() => {
|
const blurhashView = useMemo(() => {
|
||||||
if (hidden || !imageLoaded) {
|
if (hidden || !imageLoaded) {
|
||||||
|
@ -101,10 +92,11 @@ const GracefullyImage = ({
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<FastImage
|
<FastImage
|
||||||
source={source}
|
source={{
|
||||||
|
uri: reduceMotionEnabled && uri.static ? uri.static : uri.original
|
||||||
|
}}
|
||||||
style={[{ flex: 1 }, imageStyle]}
|
style={[{ flex: 1 }, imageStyle]}
|
||||||
onLoad={onLoad}
|
onLoad={onLoad}
|
||||||
onError={onError}
|
|
||||||
/>
|
/>
|
||||||
{blurhashView}
|
{blurhashView}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
|
|
@ -52,22 +52,24 @@ const ComponentHashtag: React.FC<PropsWithChildren & Props> = ({
|
||||||
>
|
>
|
||||||
#{hashtag.name}
|
#{hashtag.name}
|
||||||
</CustomText>
|
</CustomText>
|
||||||
<View
|
{hashtag.history?.length ? (
|
||||||
style={{ flexDirection: 'row', alignItems: 'center', alignSelf: 'stretch' }}
|
<View
|
||||||
onLayout={({
|
style={{ flexDirection: 'row', alignItems: 'center', alignSelf: 'stretch' }}
|
||||||
nativeEvent: {
|
onLayout={({
|
||||||
layout: { height }
|
nativeEvent: {
|
||||||
}
|
layout: { height }
|
||||||
}) => setHeight(height)}
|
}
|
||||||
>
|
}) => setHeight(height)}
|
||||||
<Sparkline
|
>
|
||||||
data={hashtag.history.map(h => parseInt(h.uses)).reverse()}
|
<Sparkline
|
||||||
width={width}
|
data={hashtag.history.map(h => parseInt(h.uses)).reverse()}
|
||||||
height={height}
|
width={width}
|
||||||
margin={children ? StyleConstants.Spacing.S : undefined}
|
height={height}
|
||||||
/>
|
margin={children ? StyleConstants.Spacing.S : undefined}
|
||||||
{children}
|
/>
|
||||||
</View>
|
{children}
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ const renderNode = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const domain = href?.split(new RegExp(/:\/\/(.[^\/]+)/))
|
const domain = href?.split(new RegExp(/:\/\/(.[^\/]+\/.{3})/))
|
||||||
// Need example here
|
// Need example here
|
||||||
const content = node.children && node.children[0] && node.children[0].data
|
const content = node.children && node.children[0] && node.children[0].data
|
||||||
const shouldBeTag = tags && tags.filter(tag => `#${tag.name}` === content).length > 0
|
const shouldBeTag = tags && tags.filter(tag => `#${tag.name}` === content).length > 0
|
||||||
|
@ -128,17 +128,7 @@ const renderNode = ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{content && content !== href ? content : showFullLink ? href : domain?.[1]}
|
{content && content !== href ? content : showFullLink ? href : domain?.[1]}
|
||||||
{!shouldBeTag ? (
|
{!shouldBeTag ? '...' : null}
|
||||||
<Icon
|
|
||||||
color={colors.blue}
|
|
||||||
name='ExternalLink'
|
|
||||||
size={adaptedFontsize}
|
|
||||||
style={{
|
|
||||||
marginLeft: StyleConstants.Spacing.XS,
|
|
||||||
transform: [{ translateY: Platform.OS === 'ios' ? -1 : 2 }]
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</CustomText>
|
</CustomText>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -228,7 +218,7 @@ const ParseHTML = React.memo(
|
||||||
if (children) {
|
if (children) {
|
||||||
return (
|
return (
|
||||||
<ParseEmojis
|
<ParseEmojis
|
||||||
content={children.toString()}
|
content={children?.toString()}
|
||||||
emojis={emojis}
|
emojis={emojis}
|
||||||
size={size}
|
size={size}
|
||||||
adaptiveSize={adaptiveSize}
|
adaptiveSize={adaptiveSize}
|
||||||
|
|
|
@ -13,6 +13,8 @@ import { useQueryClient } from '@tanstack/react-query'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { checkInstanceFeature } from '@utils/slices/instancesSlice'
|
import { checkInstanceFeature } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import { View } from 'react-native'
|
||||||
|
import { useRoute } from '@react-navigation/native'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
id: Mastodon.Account['id']
|
id: Mastodon.Account['id']
|
||||||
|
@ -122,9 +124,12 @@ const RelationshipOutgoing: React.FC<Props> = ({ id }: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { name } = useRoute()
|
||||||
|
const isPageNotifications = name === 'Tab-Notifications-Root'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||||
{canFollowNotify && query.data?.following ? (
|
{!isPageNotifications && canFollowNotify && query.data?.following ? (
|
||||||
<Button
|
<Button
|
||||||
type='icon'
|
type='icon'
|
||||||
content={query.data.notifying ? 'BellOff' : 'Bell'}
|
content={query.data.notifying ? 'BellOff' : 'Bell'}
|
||||||
|
@ -151,7 +156,7 @@ const RelationshipOutgoing: React.FC<Props> = ({ id }: Props) => {
|
||||||
loading={query.isLoading || mutation.isLoading}
|
loading={query.isLoading || mutation.isLoading}
|
||||||
disabled={query.isError || query.data?.blocked_by}
|
disabled={query.isError || query.data?.blocked_by}
|
||||||
/>
|
/>
|
||||||
</>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ const TimelineTranslate = () => {
|
||||||
<CustomText fontStyle='S' style={{ color: colors.secondary }}>{` Source: ${
|
<CustomText fontStyle='S' style={{ color: colors.secondary }}>{` Source: ${
|
||||||
detected?.language
|
detected?.language
|
||||||
}; Confidence: ${
|
}; Confidence: ${
|
||||||
detected?.confidence.toString().slice(0, 5) || 'null'
|
detected?.confidence?.toString().slice(0, 5) || 'null'
|
||||||
}; Target: ${targetLanguage}`}</CustomText>
|
}; Target: ${targetLanguage}`}</CustomText>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { SearchResult } from '@utils/queryHooks/search'
|
||||||
import { getSettingsBrowser } from '@utils/slices/settingsSlice'
|
import { getSettingsBrowser } from '@utils/slices/settingsSlice'
|
||||||
import * as Linking from 'expo-linking'
|
import * as Linking from 'expo-linking'
|
||||||
import * as WebBrowser from 'expo-web-browser'
|
import * as WebBrowser from 'expo-web-browser'
|
||||||
|
import validUrl from 'valid-url'
|
||||||
|
|
||||||
export let loadingLink = false
|
export let loadingLink = false
|
||||||
|
|
||||||
|
@ -86,18 +87,21 @@ const openLink = async (url: string, navigation?: any) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
loadingLink = false
|
loadingLink = false
|
||||||
switch (getSettingsBrowser(store.getState())) {
|
const validatedUrl = validUrl.isWebUri(url)
|
||||||
// Some links might end with an empty space at the end that triggers an error
|
if (validatedUrl) {
|
||||||
case 'internal':
|
switch (getSettingsBrowser(store.getState())) {
|
||||||
await WebBrowser.openBrowserAsync(encodeURI(url), {
|
// Some links might end with an empty space at the end that triggers an error
|
||||||
dismissButtonStyle: 'close',
|
case 'internal':
|
||||||
enableBarCollapsing: true,
|
await WebBrowser.openBrowserAsync(validatedUrl, {
|
||||||
...(await browserPackage())
|
dismissButtonStyle: 'close',
|
||||||
})
|
enableBarCollapsing: true,
|
||||||
break
|
...(await browserPackage())
|
||||||
case 'external':
|
})
|
||||||
await Linking.openURL(encodeURI(url))
|
break
|
||||||
break
|
case 'external':
|
||||||
|
await Linking.openURL(validatedUrl)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,21 @@
|
||||||
import { QueryClient } from '@tanstack/react-query'
|
import { QueryClient } from '@tanstack/react-query'
|
||||||
|
|
||||||
const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5 } } })
|
const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
staleTime: 1000 * 60 * 5,
|
||||||
|
retry: (failureCount, error: any) => {
|
||||||
|
if (error?.status === 404) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (failureCount <= 3) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default queryClient
|
export default queryClient
|
||||||
|
|
|
@ -254,9 +254,6 @@
|
||||||
"content_true": "Enabled",
|
"content_true": "Enabled",
|
||||||
"content_false": "Disabled"
|
"content_false": "Disabled"
|
||||||
},
|
},
|
||||||
"update": {
|
|
||||||
"title": "Update to latest version"
|
|
||||||
},
|
|
||||||
"logout": {
|
"logout": {
|
||||||
"button": "Log out",
|
"button": "Log out",
|
||||||
"alert": {
|
"alert": {
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
"reports": {
|
"reports": {
|
||||||
"action": "Rapporteren en blokkeren",
|
"action": "Rapporteren en blokkeren",
|
||||||
"alert": {
|
"alert": {
|
||||||
"title": ""
|
"title": "Bevestig rapporteren en blokkeren van @{{username}} ?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"textInput": {
|
"textInput": {
|
||||||
"placeholder": "Domeinnaam van instantie"
|
"placeholder": "Domeinnaam van instantie"
|
||||||
},
|
},
|
||||||
"whitelisted": "",
|
"whitelisted": "Dit kan een gewhiteliste instantie zijn waarvan tooot geen gegevens kan ophalen voordat er ingelogd is.",
|
||||||
"button": "Inloggen",
|
"button": "Inloggen",
|
||||||
"information": {
|
"information": {
|
||||||
"name": "Naam",
|
"name": "Naam",
|
||||||
|
|
|
@ -1,32 +1,32 @@
|
||||||
{
|
{
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"OK": "",
|
"OK": "ОК",
|
||||||
"apply": "",
|
"apply": "Применить",
|
||||||
"cancel": "",
|
"cancel": "Отменить",
|
||||||
"discard": "",
|
"discard": "Отклонить",
|
||||||
"continue": "",
|
"continue": "Продолжить",
|
||||||
"create": "",
|
"create": "Создать",
|
||||||
"delete": "",
|
"delete": "Удалить",
|
||||||
"done": "",
|
"done": "Готово",
|
||||||
"confirm": ""
|
"confirm": "Подтвердить"
|
||||||
},
|
},
|
||||||
"customEmoji": {
|
"customEmoji": {
|
||||||
"accessibilityLabel": ""
|
"accessibilityLabel": "Пользовательские эмодзи {{emoji}}"
|
||||||
},
|
},
|
||||||
"message": {
|
"message": {
|
||||||
"success": {
|
"success": {
|
||||||
"message": ""
|
"message": "Успешно {{function}}"
|
||||||
},
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
"message": ""
|
"message": ""
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"message": ""
|
"message": "Ошибка {{function}}, попробуйте ещё раз"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"separator": "",
|
"separator": ", ",
|
||||||
"discard": {
|
"discard": {
|
||||||
"title": "",
|
"title": "Изменения не сохранены",
|
||||||
"message": ""
|
"message": "Ваши изменения не были сохранены. Вы хотите отменить изменения?"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,13 +15,13 @@
|
||||||
"action_false": "Blockera användare",
|
"action_false": "Blockera användare",
|
||||||
"action_true": "Avblockera användare",
|
"action_true": "Avblockera användare",
|
||||||
"alert": {
|
"alert": {
|
||||||
"title": ""
|
"title": "Bekräfta blockering av användaren {{username}}?"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"reports": {
|
"reports": {
|
||||||
"action": "Rapportera och blockera användare",
|
"action": "Rapportera och blockera användare",
|
||||||
"alert": {
|
"alert": {
|
||||||
"title": ""
|
"title": "Bekräfta rapportera och blockera användaren @{{username}}?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,13 +15,13 @@
|
||||||
"action_false": "Chặn người này",
|
"action_false": "Chặn người này",
|
||||||
"action_true": "Bỏ chặn người dùng",
|
"action_true": "Bỏ chặn người dùng",
|
||||||
"alert": {
|
"alert": {
|
||||||
"title": ""
|
"title": "Bạn có chắc muốn chặn {{username}}?"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"reports": {
|
"reports": {
|
||||||
"action": "Báo cáo và chặn",
|
"action": "Báo cáo và chặn",
|
||||||
"alert": {
|
"alert": {
|
||||||
"title": ""
|
"title": "Bạn có chắc muốn báo cáo {{username}}?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,13 +15,13 @@
|
||||||
"action_false": "封鎖使用者",
|
"action_false": "封鎖使用者",
|
||||||
"action_true": "解除封鎖使用者",
|
"action_true": "解除封鎖使用者",
|
||||||
"alert": {
|
"alert": {
|
||||||
"title": ""
|
"title": "確認封鎖使用者 @{{username}}?"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"reports": {
|
"reports": {
|
||||||
"action": "檢舉並封鎖使用者",
|
"action": "檢舉並封鎖使用者",
|
||||||
"alert": {
|
"alert": {
|
||||||
"title": ""
|
"title": "確認檢舉並封鎖使用者 @{{username}}?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -196,7 +196,7 @@ const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-
|
||||||
rightOpenValue={-actionWidth}
|
rightOpenValue={-actionWidth}
|
||||||
previewOpenValue={-actionWidth / 2}
|
previewOpenValue={-actionWidth / 2}
|
||||||
ItemSeparatorComponent={ComponentSeparator}
|
ItemSeparatorComponent={ComponentSeparator}
|
||||||
keyExtractor={item => item.timestamp.toString()}
|
keyExtractor={item => item.timestamp?.toString()}
|
||||||
/>
|
/>
|
||||||
</PanGestureHandler>
|
</PanGestureHandler>
|
||||||
<Modal
|
<Modal
|
||||||
|
|
|
@ -167,7 +167,7 @@ const ComposeActions: React.FC = () => {
|
||||||
}}
|
}}
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
onPress={attachmentOnPress}
|
onPress={attachmentOnPress}
|
||||||
children={<Icon name='Aperture' size={24} color={attachmentColor} />}
|
children={<Icon name='Camera' size={24} color={attachmentColor} />}
|
||||||
/>
|
/>
|
||||||
<Pressable
|
<Pressable
|
||||||
accessibilityRole='button'
|
accessibilityRole='button'
|
||||||
|
|
|
@ -43,11 +43,11 @@ const composePost = async (
|
||||||
e => e && e.length && formData.append('poll[options][]', e)
|
e => e && e.length && formData.append('poll[options][]', e)
|
||||||
)
|
)
|
||||||
formData.append('poll[expires_in]', composeState.poll.expire)
|
formData.append('poll[expires_in]', composeState.poll.expire)
|
||||||
formData.append('poll[multiple]', composeState.poll.multiple.toString())
|
formData.append('poll[multiple]', composeState.poll.multiple?.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (composeState.attachments.uploads.filter(upload => upload.remote && upload.remote.id).length) {
|
if (composeState.attachments.uploads.filter(upload => upload.remote && upload.remote.id).length) {
|
||||||
formData.append('sensitive', composeState.attachments.sensitive.toString())
|
formData.append('sensitive', composeState.attachments.sensitive?.toString())
|
||||||
composeState.attachments.uploads.forEach(e => formData.append('media_ids[]', e.remote!.id!))
|
composeState.attachments.uploads.forEach(e => formData.append('media_ids[]', e.remote!.id!))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,11 @@ import GracefullyImage from '@components/GracefullyImage'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
|
||||||
import { useAppDispatch } from '@root/store'
|
|
||||||
import { RootStackScreenProps, ScreenTabsStackParamList } from '@utils/navigation/navigators'
|
import { RootStackScreenProps, ScreenTabsStackParamList } from '@utils/navigation/navigators'
|
||||||
import { getVersionUpdate, retrieveVersionLatest } from '@utils/slices/appSlice'
|
|
||||||
import { getPreviousTab } from '@utils/slices/contextsSlice'
|
import { getPreviousTab } from '@utils/slices/contextsSlice'
|
||||||
import { getInstanceAccount, getInstanceActive } from '@utils/slices/instancesSlice'
|
import { getInstanceAccount, getInstanceActive } from '@utils/slices/instancesSlice'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useEffect, useMemo } from 'react'
|
import React, { useCallback, useMemo } from 'react'
|
||||||
import { Platform } from 'react-native'
|
import { Platform } from 'react-native'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import TabLocal from './Tabs/Local'
|
import TabLocal from './Tabs/Local'
|
||||||
|
@ -55,17 +53,6 @@ const ScreenTabs = React.memo(
|
||||||
|
|
||||||
const previousTab = useSelector(getPreviousTab, () => true)
|
const previousTab = useSelector(getPreviousTab, () => true)
|
||||||
|
|
||||||
const versionUpdate = useSelector(getVersionUpdate)
|
|
||||||
const dispatch = useAppDispatch()
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(retrieveVersionLatest())
|
|
||||||
}, [])
|
|
||||||
const tabMeOptions = useMemo(() => {
|
|
||||||
if (versionUpdate) {
|
|
||||||
return { tabBarBadge: 1 }
|
|
||||||
}
|
|
||||||
}, [versionUpdate])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tab.Navigator
|
<Tab.Navigator
|
||||||
initialRouteName={instanceActive !== -1 ? previousTab : 'Tab-Me'}
|
initialRouteName={instanceActive !== -1 ? previousTab : 'Tab-Me'}
|
||||||
|
@ -121,12 +108,7 @@ const ScreenTabs = React.memo(
|
||||||
<Tab.Screen name='Tab-Public' component={TabPublic} />
|
<Tab.Screen name='Tab-Public' component={TabPublic} />
|
||||||
<Tab.Screen name='Tab-Compose' component={composeComponent} listeners={composeListeners} />
|
<Tab.Screen name='Tab-Compose' component={composeComponent} listeners={composeListeners} />
|
||||||
<Tab.Screen name='Tab-Notifications' component={TabNotifications} />
|
<Tab.Screen name='Tab-Notifications' component={TabNotifications} />
|
||||||
<Tab.Screen
|
<Tab.Screen name='Tab-Me' component={TabMe} listeners={meListeners} />
|
||||||
name='Tab-Me'
|
|
||||||
component={TabMe}
|
|
||||||
options={tabMeOptions}
|
|
||||||
listeners={meListeners}
|
|
||||||
/>
|
|
||||||
</Tab.Navigator>
|
</Tab.Navigator>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,12 +8,7 @@ import { isDevelopment } from '@utils/checkEnvironment'
|
||||||
import { useAppsQuery } from '@utils/queryHooks/apps'
|
import { useAppsQuery } from '@utils/queryHooks/apps'
|
||||||
import { useProfileQuery } from '@utils/queryHooks/profile'
|
import { useProfileQuery } from '@utils/queryHooks/profile'
|
||||||
import { getExpoToken, retrieveExpoToken } from '@utils/slices/appSlice'
|
import { getExpoToken, retrieveExpoToken } from '@utils/slices/appSlice'
|
||||||
import {
|
import { PUSH_ADMIN, PUSH_DEFAULT, usePushFeatures } from '@utils/slices/instances/push/utils'
|
||||||
PUSH_ADMIN,
|
|
||||||
PUSH_DEFAULT,
|
|
||||||
setChannels,
|
|
||||||
usePushFeatures
|
|
||||||
} from '@utils/slices/instances/push/utils'
|
|
||||||
import { updateInstancePush } from '@utils/slices/instances/updatePush'
|
import { updateInstancePush } from '@utils/slices/instances/updatePush'
|
||||||
import { updateInstancePushAlert } from '@utils/slices/instances/updatePushAlert'
|
import { updateInstancePushAlert } from '@utils/slices/instances/updatePushAlert'
|
||||||
import { updateInstancePushDecode } from '@utils/slices/instances/updatePushDecode'
|
import { updateInstancePushDecode } from '@utils/slices/instances/updatePushDecode'
|
||||||
|
@ -25,7 +20,7 @@ import * as Notifications from 'expo-notifications'
|
||||||
import * as WebBrowser from 'expo-web-browser'
|
import * as WebBrowser from 'expo-web-browser'
|
||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { AppState, Linking, Platform, ScrollView, View } from 'react-native'
|
import { AppState, Linking, ScrollView, View } from 'react-native'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
const TabMePush: React.FC = () => {
|
const TabMePush: React.FC = () => {
|
||||||
|
@ -50,11 +45,7 @@ const TabMePush: React.FC = () => {
|
||||||
setPushEnabled(permissions.granted)
|
setPushEnabled(permissions.granted)
|
||||||
setPushCanAskAgain(permissions.canAskAgain)
|
setPushCanAskAgain(permissions.canAskAgain)
|
||||||
layoutAnimation()
|
layoutAnimation()
|
||||||
|
dispatch(retrieveExpoToken())
|
||||||
if (Platform.OS === 'android') {
|
|
||||||
await setChannels(instance)
|
|
||||||
dispatch(retrieveExpoToken())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appsQuery.data?.vapid_key) {
|
if (appsQuery.data?.vapid_key) {
|
||||||
|
|
|
@ -12,12 +12,8 @@ import accountReducer from '@screens/Tabs/Shared/Account/utils/reducer'
|
||||||
import { useProfileQuery } from '@utils/queryHooks/profile'
|
import { useProfileQuery } from '@utils/queryHooks/profile'
|
||||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||||
import React, { useReducer, useRef } from 'react'
|
import React, { useReducer, useRef } from 'react'
|
||||||
import Animated, {
|
import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated'
|
||||||
useAnimatedScrollHandler,
|
|
||||||
useSharedValue
|
|
||||||
} from 'react-native-reanimated'
|
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import Update from './Root/Update'
|
|
||||||
|
|
||||||
const TabMeRoot: React.FC = () => {
|
const TabMeRoot: React.FC = () => {
|
||||||
const instanceActive = useSelector(getInstanceActive)
|
const instanceActive = useSelector(getInstanceActive)
|
||||||
|
@ -29,10 +25,7 @@ const TabMeRoot: React.FC = () => {
|
||||||
const scrollRef = useRef<Animated.ScrollView>(null)
|
const scrollRef = useRef<Animated.ScrollView>(null)
|
||||||
useScrollToTop(scrollRef)
|
useScrollToTop(scrollRef)
|
||||||
|
|
||||||
const [accountState, accountDispatch] = useReducer(
|
const [accountState, accountDispatch] = useReducer(accountReducer, accountInitialState)
|
||||||
accountReducer,
|
|
||||||
accountInitialState
|
|
||||||
)
|
|
||||||
|
|
||||||
const scrollY = useSharedValue(0)
|
const scrollY = useSharedValue(0)
|
||||||
const onScroll = useAnimatedScrollHandler(event => {
|
const onScroll = useAnimatedScrollHandler(event => {
|
||||||
|
@ -41,22 +34,15 @@ const TabMeRoot: React.FC = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccountContext.Provider value={{ accountState, accountDispatch }}>
|
<AccountContext.Provider value={{ accountState, accountDispatch }}>
|
||||||
{instanceActive !== -1 && data ? (
|
{instanceActive !== -1 && data ? <AccountNav scrollY={scrollY} account={data} /> : null}
|
||||||
<AccountNav scrollY={scrollY} account={data} />
|
|
||||||
) : null}
|
|
||||||
<Animated.ScrollView
|
<Animated.ScrollView
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
keyboardShouldPersistTaps='handled'
|
keyboardShouldPersistTaps='handled'
|
||||||
onScroll={onScroll}
|
onScroll={onScroll}
|
||||||
scrollEventThrottle={16}
|
scrollEventThrottle={16}
|
||||||
>
|
>
|
||||||
{instanceActive !== -1 ? (
|
{instanceActive !== -1 ? <MyInfo account={data} /> : <ComponentInstance />}
|
||||||
<MyInfo account={data} />
|
|
||||||
) : (
|
|
||||||
<ComponentInstance />
|
|
||||||
)}
|
|
||||||
{instanceActive !== -1 ? <Collections /> : null}
|
{instanceActive !== -1 ? <Collections /> : null}
|
||||||
<Update />
|
|
||||||
<Settings />
|
<Settings />
|
||||||
{instanceActive !== -1 ? <AccountInformationSwitch /> : null}
|
{instanceActive !== -1 ? <AccountInformationSwitch /> : null}
|
||||||
{instanceActive !== -1 ? <Logout /> : null}
|
{instanceActive !== -1 ? <Logout /> : null}
|
||||||
|
|
|
@ -115,7 +115,11 @@ const Collections: React.FC = () => {
|
||||||
iconFront={instancePush ? 'Bell' : 'BellOff'}
|
iconFront={instancePush ? 'Bell' : 'BellOff'}
|
||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
title={t('me.stacks.push.name')}
|
title={t('me.stacks.push.name')}
|
||||||
content={t('me.root.push.content', { context: instancePush.global.toString() })}
|
content={
|
||||||
|
typeof instancePush.global === 'boolean'
|
||||||
|
? t('me.root.push.content', { context: instancePush.global.toString() })
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
onPress={() => navigation.navigate('Tab-Me-Push')}
|
onPress={() => navigation.navigate('Tab-Me-Push')}
|
||||||
/>
|
/>
|
||||||
</MenuContainer>
|
</MenuContainer>
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
|
||||||
import { getVersionUpdate } from '@utils/slices/appSlice'
|
|
||||||
import React from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import { Linking, Platform } from 'react-native'
|
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
|
|
||||||
const Update: React.FC = () => {
|
|
||||||
const { t } = useTranslation('screenTabs')
|
|
||||||
|
|
||||||
const versionUpdate = useSelector(getVersionUpdate)
|
|
||||||
|
|
||||||
return versionUpdate ? (
|
|
||||||
<MenuContainer>
|
|
||||||
<MenuRow
|
|
||||||
iconFront='ChevronsUp'
|
|
||||||
iconBack='ExternalLink'
|
|
||||||
title={t('me.root.update.title')}
|
|
||||||
badge
|
|
||||||
onPress={() => {
|
|
||||||
if (Platform.OS === 'ios') {
|
|
||||||
Linking.openURL('itms-appss://itunes.apple.com/app/id1549772269')
|
|
||||||
} else {
|
|
||||||
Linking.openURL('https://tooot.app')
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</MenuContainer>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Update
|
|
|
@ -27,7 +27,7 @@ const TabMeSettingsLanguage: React.FC<TabMeStackScreenProps<'Tab-Me-Settings-Lan
|
||||||
|
|
||||||
// Update Android notification channel language
|
// Update Android notification channel language
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
instances.forEach(setChannels)
|
instances.forEach(instance => setChannels(instance, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
navigation.pop(1)
|
navigation.pop(1)
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import GracefullyImage from '@components/GracefullyImage'
|
import GracefullyImage from '@components/GracefullyImage'
|
||||||
import navigationRef from '@helpers/navigationRef'
|
import navigationRef from '@helpers/navigationRef'
|
||||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Dimensions, Image, Pressable } from 'react-native'
|
import { Dimensions, Image } from 'react-native'
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
|
@ -12,13 +11,14 @@ export interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountHeader: React.FC<Props> = ({ account }) => {
|
const AccountHeader: React.FC<Props> = ({ account }) => {
|
||||||
const { colors } = useTheme()
|
|
||||||
const topInset = useSafeAreaInsets().top
|
const topInset = useSafeAreaInsets().top
|
||||||
|
|
||||||
useSelector(getInstanceActive)
|
useSelector(getInstanceActive)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<GracefullyImage
|
||||||
|
uri={{ original: account?.header, static: account?.header_static }}
|
||||||
|
style={{ height: Dimensions.get('window').width / 3 + topInset }}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (account) {
|
if (account) {
|
||||||
Image.getSize(account.header, (width, height) =>
|
Image.getSize(account.header, (width, height) =>
|
||||||
|
@ -30,15 +30,7 @@ const AccountHeader: React.FC<Props> = ({ account }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<GracefullyImage
|
|
||||||
uri={{ original: account?.header, static: account?.header_static }}
|
|
||||||
style={{
|
|
||||||
height: Dimensions.get('window').width / 3 + topInset,
|
|
||||||
backgroundColor: colors.disabled
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Pressable>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,51 +17,39 @@ export interface Props {
|
||||||
account: Mastodon.Account | undefined
|
account: Mastodon.Account | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountInformation = React.memo(
|
const AccountInformation: React.FC<Props> = ({ account }) => {
|
||||||
({ account }: Props) => {
|
const { colors } = useTheme()
|
||||||
const { colors } = useTheme()
|
|
||||||
|
|
||||||
const { name } = useRoute()
|
const { name } = useRoute()
|
||||||
const myInfo = name !== 'Tab-Shared-Account'
|
const myInfo = name !== 'Tab-Shared-Account'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
<Placeholder
|
<Placeholder
|
||||||
Animation={props => (
|
Animation={props => (
|
||||||
<Fade {...props} style={{ backgroundColor: colors.shimmerHighlight }} />
|
<Fade {...props} style={{ backgroundColor: colors.shimmerHighlight }} />
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<View style={styles.avatarAndActions}>
|
<View style={styles.avatarAndActions}>
|
||||||
<AccountInformationAvatar account={account} myInfo={myInfo} />
|
<AccountInformationAvatar account={account} myInfo={myInfo} />
|
||||||
<AccountInformationActions account={account} myInfo={myInfo} />
|
<AccountInformationActions account={account} myInfo={myInfo} />
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<AccountInformationName account={account} />
|
<AccountInformationName account={account} />
|
||||||
|
|
||||||
<AccountInformationAccount account={account} />
|
<AccountInformationAccount account={account} />
|
||||||
|
|
||||||
<AccountInformationFields account={account} myInfo={myInfo} />
|
<AccountInformationFields account={account} myInfo={myInfo} />
|
||||||
|
|
||||||
<AccountInformationNote account={account} myInfo={myInfo} />
|
<AccountInformationNote account={account} myInfo={myInfo} />
|
||||||
|
|
||||||
<AccountInformationCreated account={account} hidden={myInfo} />
|
<AccountInformationCreated account={account} hidden={myInfo} />
|
||||||
|
|
||||||
<AccountInformationStats account={account} myInfo={myInfo} />
|
<AccountInformationStats account={account} myInfo={myInfo} />
|
||||||
</Placeholder>
|
</Placeholder>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
(prev, next) => {
|
|
||||||
let skipUpdate = true
|
|
||||||
if (prev.account?.id !== next.account?.id) {
|
|
||||||
skipUpdate = false
|
|
||||||
}
|
|
||||||
if (prev.account?.acct === next.account?.acct) {
|
|
||||||
skipUpdate = false
|
|
||||||
}
|
|
||||||
return skipUpdate
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
base: {
|
base: {
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { TabLocalStackParamList } from '@utils/navigation/navigators'
|
||||||
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
import { getInstanceActive } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Pressable } from 'react-native'
|
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
@ -18,7 +17,15 @@ const AccountInformationAvatar: React.FC<Props> = ({ account, myInfo }) => {
|
||||||
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
const navigation = useNavigation<StackNavigationProp<TabLocalStackParamList>>()
|
||||||
useSelector(getInstanceActive)
|
useSelector(getInstanceActive)
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<GracefullyImage
|
||||||
|
key={account?.avatar}
|
||||||
|
style={{
|
||||||
|
borderRadius: 8,
|
||||||
|
overflow: 'hidden',
|
||||||
|
width: StyleConstants.Avatar.L,
|
||||||
|
height: StyleConstants.Avatar.L
|
||||||
|
}}
|
||||||
|
uri={{ original: account?.avatar, static: account?.avatar_static }}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (account) {
|
if (account) {
|
||||||
if (myInfo) {
|
if (myInfo) {
|
||||||
|
@ -33,19 +40,7 @@ const AccountInformationAvatar: React.FC<Props> = ({ account, myInfo }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
style={{
|
/>
|
||||||
borderRadius: 8,
|
|
||||||
overflow: 'hidden',
|
|
||||||
width: StyleConstants.Avatar.L,
|
|
||||||
height: StyleConstants.Avatar.L
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<GracefullyImage
|
|
||||||
key={account?.avatar}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
uri={{ original: account?.avatar, static: account?.avatar_static }}
|
|
||||||
/>
|
|
||||||
</Pressable>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,38 +1,43 @@
|
||||||
import apiTooot from '@api/tooot'
|
|
||||||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
|
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
|
||||||
import { RootState } from '@root/store'
|
import { RootState } from '@root/store'
|
||||||
import { isDevelopment } from '@utils/checkEnvironment'
|
import { isDevelopment } from '@utils/checkEnvironment'
|
||||||
import Constants from 'expo-constants'
|
|
||||||
import * as Notifications from 'expo-notifications'
|
import * as Notifications from 'expo-notifications'
|
||||||
|
import { Platform } from 'react-native'
|
||||||
|
import { setChannels } from './instances/push/utils'
|
||||||
|
import { getInstance } from './instancesSlice'
|
||||||
|
|
||||||
export const retrieveExpoToken = createAsyncThunk('app/expoToken', async (): Promise<string> => {
|
export const retrieveExpoToken = createAsyncThunk(
|
||||||
if (isDevelopment) {
|
'app/expoToken',
|
||||||
return 'ExponentPushToken[DEVELOPMENT_1]'
|
async (_, { getState }): Promise<string> => {
|
||||||
}
|
const instance = getInstance(getState() as RootState)
|
||||||
|
const expoToken = getExpoToken(getState() as RootState)
|
||||||
|
|
||||||
const res = await Notifications.getExpoPushTokenAsync({
|
if (Platform.OS === 'android') {
|
||||||
experienceId: '@xmflsct/tooot',
|
await setChannels(instance)
|
||||||
applicationId: 'com.xmflsct.app.tooot'
|
}
|
||||||
})
|
|
||||||
return res.data
|
|
||||||
})
|
|
||||||
|
|
||||||
export const retrieveVersionLatest = createAsyncThunk(
|
if (expoToken?.length) {
|
||||||
'app/versionUpdate',
|
return expoToken
|
||||||
async (): Promise<string> => {
|
} else {
|
||||||
const res = await apiTooot<{ latest: string }>({ method: 'get', url: 'version.json' })
|
if (isDevelopment) {
|
||||||
return res.body.latest
|
return 'ExponentPushToken[DEVELOPMENT_1]'
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await Notifications.getExpoPushTokenAsync({
|
||||||
|
experienceId: '@xmflsct/tooot',
|
||||||
|
applicationId: 'com.xmflsct.app.tooot'
|
||||||
|
})
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
export type AppState = {
|
export type AppState = {
|
||||||
expoToken?: string
|
expoToken?: string
|
||||||
versionUpdate: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appInitialState: AppState = {
|
export const appInitialState: AppState = {
|
||||||
expoToken: undefined,
|
expoToken: undefined
|
||||||
versionUpdate: false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const appSlice = createSlice({
|
const appSlice = createSlice({
|
||||||
|
@ -40,22 +45,14 @@ const appSlice = createSlice({
|
||||||
initialState: appInitialState,
|
initialState: appInitialState,
|
||||||
reducers: {},
|
reducers: {},
|
||||||
extraReducers: builder => {
|
extraReducers: builder => {
|
||||||
builder
|
builder.addCase(retrieveExpoToken.fulfilled, (state, action) => {
|
||||||
.addCase(retrieveExpoToken.fulfilled, (state, action) => {
|
if (action.payload) {
|
||||||
if (action.payload) {
|
state.expoToken = action.payload
|
||||||
state.expoToken = action.payload
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
.addCase(retrieveVersionLatest.fulfilled, (state, action) => {
|
|
||||||
if (action.payload && Constants.expoConfig?.version) {
|
|
||||||
state.versionUpdate =
|
|
||||||
parseFloat(action.payload) > parseFloat(Constants.expoConfig.version)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getExpoToken = (state: RootState) => state.app.expoToken
|
export const getExpoToken = (state: RootState) => state.app.expoToken
|
||||||
export const getVersionUpdate = (state: RootState) => state.app.versionUpdate
|
|
||||||
|
|
||||||
export default appSlice.reducer
|
export default appSlice.reducer
|
||||||
|
|
|
@ -50,6 +50,12 @@ const addInstance = createAsyncThunk(
|
||||||
domain,
|
domain,
|
||||||
url: `api/v1/preferences`,
|
url: `api/v1/preferences`,
|
||||||
headers: { Authorization: `Bearer ${token}` }
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
|
}).catch(error => {
|
||||||
|
if (error?.status === 404) {
|
||||||
|
return Promise.resolve({ body: {} })
|
||||||
|
} else {
|
||||||
|
return Promise.reject()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const { body: filters } = await apiGeneral<Mastodon.Filter<any>[]>({
|
const { body: filters } = await apiGeneral<Mastodon.Filter<any>[]>({
|
||||||
|
@ -66,7 +72,7 @@ const addInstance = createAsyncThunk(
|
||||||
appData,
|
appData,
|
||||||
url: domain,
|
url: domain,
|
||||||
token,
|
token,
|
||||||
uri: instance.uri,
|
uri: instance.uri.replace(/^https?:\/\//, ''), // Pleroma includes schema
|
||||||
urls: instance.urls,
|
urls: instance.urls,
|
||||||
account: {
|
account: {
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -28,7 +28,7 @@ const subscribe = async ({
|
||||||
}) => {
|
}) => {
|
||||||
return apiTooot({
|
return apiTooot({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: `/push/subscribe/${expoToken}/${instanceUrl}/${accountId}`,
|
url: `push/subscribe/${expoToken}/${instanceUrl}/${accountId}`,
|
||||||
body: { accountFull, serverKey, auth }
|
body: { accountFull, serverKey, auth }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ const pushRegister = async (
|
||||||
})
|
})
|
||||||
|
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
setChannels(instance)
|
setChannels(instance, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(auth)
|
return Promise.resolve(auth)
|
||||||
|
|
|
@ -21,7 +21,7 @@ const pushUnregister = async (state: RootState, expoToken: string) => {
|
||||||
|
|
||||||
await apiTooot<{ endpoint: string; publicKey: string; auth: string }>({
|
await apiTooot<{ endpoint: string; publicKey: string; auth: string }>({
|
||||||
method: 'delete',
|
method: 'delete',
|
||||||
url: `/push/unsubscribe/${expoToken}/${instance.url}/${instance.account.id}`
|
url: `push/unsubscribe/${expoToken}/${instance.url}/${instance.account.id}`
|
||||||
})
|
})
|
||||||
|
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
|
|
|
@ -62,7 +62,7 @@ export const PUSH_ADMIN = (
|
||||||
}
|
}
|
||||||
}) as { type: 'admin.sign_up' | 'admin.report'; permission: number }[]
|
}) as { type: 'admin.sign_up' | 'admin.report'; permission: number }[]
|
||||||
|
|
||||||
export const setChannels = async (instance: InstanceLatest) => {
|
export const setChannels = async (instance: InstanceLatest, reset: boolean | undefined = false) => {
|
||||||
const account = `@${instance.account.acct}@${instance.uri}`
|
const account = `@${instance.account.acct}@${instance.uri}`
|
||||||
|
|
||||||
const deleteChannel = async (type: string) =>
|
const deleteChannel = async (type: string) =>
|
||||||
|
@ -82,6 +82,9 @@ export const setChannels = async (instance: InstanceLatest) => {
|
||||||
const profileQuery = await queryClient.fetchQuery(queryKey, queryFunctionProfile)
|
const profileQuery = await queryClient.fetchQuery(queryKey, queryFunctionProfile)
|
||||||
|
|
||||||
const channelGroup = await Notifications.getNotificationChannelGroupAsync(account)
|
const channelGroup = await Notifications.getNotificationChannelGroupAsync(account)
|
||||||
|
if (channelGroup && !reset) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!channelGroup) {
|
if (!channelGroup) {
|
||||||
await Notifications.setNotificationChannelGroupAsync(account, { name: account })
|
await Notifications.setNotificationChannelGroupAsync(account, { name: account })
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,14 @@ export const updateAccountPreferences = createAsyncThunk(
|
||||||
return apiInstance<Mastodon.Preferences>({
|
return apiInstance<Mastodon.Preferences>({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: `preferences`
|
url: `preferences`
|
||||||
}).then(res => res.body)
|
})
|
||||||
|
.then(res => res.body)
|
||||||
|
.catch(error => {
|
||||||
|
if (error?.status === 404) {
|
||||||
|
return Promise.resolve({})
|
||||||
|
} else {
|
||||||
|
return Promise.reject()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -25,14 +25,14 @@ export const updateInstancePushDecode = createAsyncThunk(
|
||||||
|
|
||||||
await apiTooot({
|
await apiTooot({
|
||||||
method: 'put',
|
method: 'put',
|
||||||
url: `/push/update-decode/${expoToken}/${instance.url}/${instance.account.id}`,
|
url: `push/update-decode/${expoToken}/${instance.url}/${instance.account.id}`,
|
||||||
body: {
|
body: {
|
||||||
auth: !disable ? null : instance.push.keys.auth
|
auth: !disable ? null : instance.push.keys.auth
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
setChannels(instance)
|
setChannels(instance, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve({ disable })
|
return Promise.resolve({ disable })
|
||||||
|
|
Loading…
Reference in New Issue