1
0
mirror of https://github.com/tooot-app/app synced 2025-06-05 22:19:13 +02:00

Merge branch 'main' into release

This commit is contained in:
xmflsct
2022-12-21 15:34:33 +01:00
57 changed files with 202 additions and 315 deletions

View File

@ -1,5 +1,3 @@
Enjoy toooting! This version includes following improvements and fixes:
- Align filter experience with v4.0 and above
- Supports enlarging user's avatar and banner
- Fix iPad weird sizing (not optimisation)
- Experiment (!) support of Pleroma
- Fixed wrongly update notification
- Fix some random crashes

View File

@ -1,5 +1,3 @@
toooting愉快此版本包括以下改进和修复
- 改进过滤体验与v4.0以上版本一致
- 支持查看用户的头像和横幅图片
- 修复iPad部分尺寸问题非优化
- 试验性支持Pleroma
- 修复错误的升级通知
- 修复部分应用崩溃

View File

@ -1,6 +1,6 @@
{
"name": "tooot",
"version": "4.7.1",
"version": "4.7.2",
"description": "tooot for Mastodon",
"author": "xmflsct <me@xmflsct.com>",
"license": "GPL-3.0-or-later",

View File

@ -44,7 +44,10 @@ const apiGeneral = async <T = unknown>({
...userAgent,
...headers
},
...(body && { data: body })
...(body &&
(body instanceof FormData
? (body as (FormData & { _parts: [][] }) | undefined)?._parts?.length
: Object.keys(body).length) && { data: body })
})
.then(response => {
let links: {

View File

@ -64,7 +64,7 @@ const apiInstance = async <T = unknown>({
Authorization: `Bearer ${token}`
})
},
...(body && { data: body }),
...((body as (FormData & { _parts: [][] }) | undefined)?._parts.length && { data: body }),
...extras
})
.then(response => {

View File

@ -47,7 +47,10 @@ const apiTooot = async <T = unknown>({
...userAgent,
...headers
},
...(body && { data: body })
...(body &&
(body instanceof FormData
? (body as (FormData & { _parts: [][] }) | undefined)?._parts?.length
: Object.keys(body).length) && { data: body })
})
.then(response => {
return Promise.resolve({

View File

@ -52,22 +52,24 @@ const ComponentHashtag: React.FC<PropsWithChildren & Props> = ({
>
#{hashtag.name}
</CustomText>
<View
style={{ flexDirection: 'row', alignItems: 'center', alignSelf: 'stretch' }}
onLayout={({
nativeEvent: {
layout: { height }
}
}) => setHeight(height)}
>
<Sparkline
data={hashtag.history?.map(h => parseInt(h.uses)).reverse()}
width={width}
height={height}
margin={children ? StyleConstants.Spacing.S : undefined}
/>
{children}
</View>
{hashtag.history?.length ? (
<View
style={{ flexDirection: 'row', alignItems: 'center', alignSelf: 'stretch' }}
onLayout={({
nativeEvent: {
layout: { height }
}
}) => setHeight(height)}
>
<Sparkline
data={hashtag.history.map(h => parseInt(h.uses)).reverse()}
width={width}
height={height}
margin={children ? StyleConstants.Spacing.S : undefined}
/>
{children}
</View>
) : null}
</Pressable>
)
}

View File

@ -218,7 +218,7 @@ const ParseHTML = React.memo(
if (children) {
return (
<ParseEmojis
content={children.toString()}
content={children?.toString()}
emojis={emojis}
size={size}
adaptiveSize={adaptiveSize}

View File

@ -54,7 +54,7 @@ const TimelineTranslate = () => {
<CustomText fontStyle='S' style={{ color: colors.secondary }}>{` Source: ${
detected?.language
}; Confidence: ${
detected?.confidence.toString().slice(0, 5) || 'null'
detected?.confidence?.toString().slice(0, 5) || 'null'
}; Target: ${targetLanguage}`}</CustomText>
) : null
}

View File

@ -7,6 +7,7 @@ import { SearchResult } from '@utils/queryHooks/search'
import { getSettingsBrowser } from '@utils/slices/settingsSlice'
import * as Linking from 'expo-linking'
import * as WebBrowser from 'expo-web-browser'
import validUrl from 'valid-url'
export let loadingLink = false
@ -86,18 +87,21 @@ const openLink = async (url: string, navigation?: any) => {
}
loadingLink = false
switch (getSettingsBrowser(store.getState())) {
// Some links might end with an empty space at the end that triggers an error
case 'internal':
await WebBrowser.openBrowserAsync(encodeURI(url), {
dismissButtonStyle: 'close',
enableBarCollapsing: true,
...(await browserPackage())
})
break
case 'external':
await Linking.openURL(encodeURI(url))
break
const validatedUrl = validUrl.isWebUri(url)
if (validatedUrl) {
switch (getSettingsBrowser(store.getState())) {
// Some links might end with an empty space at the end that triggers an error
case 'internal':
await WebBrowser.openBrowserAsync(validatedUrl, {
dismissButtonStyle: 'close',
enableBarCollapsing: true,
...(await browserPackage())
})
break
case 'external':
await Linking.openURL(validatedUrl)
break
}
}
}

View File

@ -15,13 +15,13 @@
"action_false": "Bloqueja l'usuari",
"action_true": "Deixa de bloquejar l'usuari",
"alert": {
"title": ""
"title": "Vols bloquejar a @{{username}}?"
}
},
"reports": {
"action": "Denuncia i bloqueja l'usuari",
"alert": {
"title": ""
"title": "Vols denunciar i bloquejar a @{{username}}?"
}
}
},

View File

@ -32,7 +32,7 @@
},
"update": "L'impuls ha sigut editat",
"admin.sign_up": "{{name}} s'ha unit a la instància",
"admin.report": ""
"admin.report": "{{name}} ha reportat:"
},
"actions": {
"reply": {
@ -100,7 +100,7 @@
"fullConversation": "Llegeix conversacions",
"translate": {
"default": "Tradueix",
"succeed": "Traduït per {{provider}} amb {{source}}",
"succeed": "Traduït per {{provider}} des de l'idioma {{source}}",
"failed": "Error al traduir",
"source_not_supported": "L'idioma de la publicació no està suportada",
"target_not_supported": "Aquest idioma no està suportat"

View File

@ -2,7 +2,7 @@
"heading": {
"left": {
"alert": {
"title": "Voleu cancel·lar l'edició?",
"title": "Vols cancel·lar l'edició?",
"buttons": {
"save": "Desa l'esborrany",
"delete": "Esborra l'esborrany"
@ -13,9 +13,9 @@
"button": {
"default": "Publica",
"conversation": "Envia un missatge directe",
"reply": "Publica la resposta",
"deleteEdit": "Publicació",
"edit": "Publica l'edició",
"reply": "Respon",
"deleteEdit": "Torna-ho a publicar",
"edit": "Edita",
"share": "Publicació"
},
"alert": {

View File

@ -133,7 +133,7 @@
"listDelete": {
"heading": "Esborra la llista",
"confirm": {
"title": "Vol esborrar la llista \"{{list}}\"?",
"title": "Vols esborrar la llista \"{{list}}\"?",
"message": "Aquesta acció no es pot desfer."
}
},
@ -254,13 +254,10 @@
"content_true": "Habilitat",
"content_false": "Deshabilitat"
},
"update": {
"title": "Actualitza a la última versió"
},
"logout": {
"button": "Tanca la sessió",
"alert": {
"title": "Vol tancar la sessió?",
"title": "Vols tancar la sessió?",
"message": "Després de tancar la sessió, hauràs de tornar a iniciar la sessió",
"buttons": {
"logout": "Tanca la sessió"

View File

@ -254,9 +254,6 @@
"content_true": "",
"content_false": ""
},
"update": {
"title": ""
},
"logout": {
"button": "",
"alert": {

View File

@ -15,13 +15,13 @@
"action_false": "Nutzer blockieren",
"action_true": "User entblocken",
"alert": {
"title": ""
"title": "{{username}} wirklich blockieren?"
}
},
"reports": {
"action": "Nutzer melden und blockieren",
"alert": {
"title": ""
"title": "{{username}} wirklich blockieren und melden?"
}
}
},

View File

@ -254,9 +254,6 @@
"content_true": "Aktiviert",
"content_false": "Deaktiviert"
},
"update": {
"title": "Auf neueste Version aktualisiert"
},
"logout": {
"button": "Abmelden",
"alert": {

View File

@ -254,9 +254,6 @@
"content_true": "Enabled",
"content_false": "Disabled"
},
"update": {
"title": "Update to latest version"
},
"logout": {
"button": "Log out",
"alert": {

View File

@ -1,6 +1,6 @@
{
"buttons": {
"OK": "OK",
"OK": "De acuerdo",
"apply": "Aplicar",
"cancel": "Cancelar",
"discard": "Descartar",

View File

@ -15,13 +15,13 @@
"action_false": "Bloquear usuario",
"action_true": "Desbloquear usuario",
"alert": {
"title": ""
"title": "¿Quieres bloquear a @{{username}}?"
}
},
"reports": {
"action": "Reportar y bloquear usuario",
"alert": {
"title": ""
"title": "¿Quieres denunciar y bloquear a @{{username}}?"
}
}
},
@ -38,7 +38,7 @@
"block": {
"action": "Bloquear instancia {{instance}}",
"alert": {
"title": "¿Confirmar bloqueo de la instancia {{instance}}?",
"title": "¿Quieres bloquear la instancia {{instance}}?",
"message": "Puedes silenciar o bloquear a un usuario.\n\nTras bloquear una instancia, todo su contenido junto con sus seguidores se eliminarán."
}
}
@ -59,14 +59,14 @@
"delete": {
"action": "Eliminar toot",
"alert": {
"title": "¿Confirmar eliminación?",
"title": "¿Quieres eliminar?",
"message": "Todos los boosts y favoritos se eliminarán, incluidas todas las respuestas."
}
},
"deleteEdit": {
"action": "Eliminar toot y volver a publicar",
"alert": {
"title": "¿Confirmar eliminación y volver a publicar?",
"title": "¿Quieres eliminar y volver a publicar?",
"message": "Todos los boosts y favoritos se eliminarán, incluidas todas las respuestas."
}
},

View File

@ -32,7 +32,7 @@
},
"update": "El impulso ha sido editado",
"admin.sign_up": "{{name}} se unió a la instancia",
"admin.report": ""
"admin.report": "{{name}} reportó:"
},
"actions": {
"reply": {
@ -145,7 +145,7 @@
"refresh": "Actualizar"
},
"count": {
"voters_one": "{{count}} usuarios ha votado",
"voters_one": "{{count}} usuario ha votado",
"voters_other": "{{count}} usuarios han votado",
"votes_one": "{{count}} voto",
"votes_other": "{{count}} votos"

View File

@ -2,7 +2,7 @@
"heading": {
"left": {
"alert": {
"title": "¿Cancelar edición?",
"title": "¿Quieres cancelar la edición?",
"buttons": {
"save": "Guardar borrador",
"delete": "Eliminar borrador"
@ -13,9 +13,9 @@
"button": {
"default": "Toot",
"conversation": "Mensaje privado",
"reply": "Respuesta al toot",
"deleteEdit": "Toot",
"edit": "Edita el toot",
"reply": "Responde",
"deleteEdit": "Vuelve a publicar",
"edit": "Edita",
"share": "Toot"
},
"alert": {
@ -39,7 +39,7 @@
"placeholder": "Mensaje de aviso de spoiler"
},
"textInput": {
"placeholder": "Qué está pasando",
"placeholder": "¿Qué está pasando?",
"keyboardImage": {
"exceedMaximum": {
"title": "Número máximo de adjuntos alcanzado",

View File

@ -11,7 +11,7 @@
"segments": {
"federated": "Federado",
"local": "Local",
"trending": "En tendencia"
"trending": "Tendencia"
}
},
"notifications": {
@ -133,7 +133,7 @@
"listDelete": {
"heading": "Borrar lista",
"confirm": {
"title": "¿Eliminar lista \"{{list}}\"?",
"title": "¿Quieres eliminar la lista \"{{list}}\"?",
"message": "Esta acción no se podrá deshacer."
}
},
@ -254,9 +254,6 @@
"content_true": "Habilitado",
"content_false": "Deshabilitado"
},
"update": {
"title": "Actualizar a la última versión"
},
"logout": {
"button": "Cerrar sesión",
"alert": {

View File

@ -254,9 +254,6 @@
"content_true": "Activé",
"content_false": "Désactivé"
},
"update": {
"title": "Mettre à jour vers la dernière version"
},
"logout": {
"button": "Se déconnecter",
"alert": {

View File

@ -254,9 +254,6 @@
"content_true": "Abilitato",
"content_false": "Disabilitato"
},
"update": {
"title": "Aggiorna all'ultima versione"
},
"logout": {
"button": "Esci dall'account",
"alert": {

View File

@ -254,9 +254,6 @@
"content_true": "有効",
"content_false": "無効"
},
"update": {
"title": "最新バージョンへのアップデート"
},
"logout": {
"button": "ログアウト",
"alert": {

View File

@ -254,9 +254,6 @@
"content_true": "활성화됨",
"content_false": "비활성화됨"
},
"update": {
"title": "최신 버전으로 업데이트"
},
"logout": {
"button": "로그아웃",
"alert": {

View File

@ -254,9 +254,6 @@
"content_true": "Ingeschakeld",
"content_false": "Uitgeschakeld"
},
"update": {
"title": "Bijwerken naar de laatste versie"
},
"logout": {
"button": "Uitloggen",
"alert": {

View File

@ -254,9 +254,6 @@
"content_true": "",
"content_false": ""
},
"update": {
"title": ""
},
"logout": {
"button": "",
"alert": {

View File

@ -254,9 +254,6 @@
"content_true": "Habilitado",
"content_false": "Desabilitado"
},
"update": {
"title": "Atualize para a versão mais recente"
},
"logout": {
"button": "Sair",
"alert": {

View File

@ -1 +1,3 @@
{}
{
"frequentUsed": "Часто используемые"
}

View File

@ -1,27 +1,27 @@
{
"server": {
"textInput": {
"placeholder": ""
"placeholder": "Домен инстанса"
},
"whitelisted": "",
"button": "",
"whitelisted": "Это сервер может быть из белого списка, из которого tooot не может получить данные перед входом.",
"button": "Войти",
"information": {
"name": "",
"accounts": "",
"statuses": "",
"name": "Название",
"accounts": "Пользователей",
"statuses": "Постов",
"domains": ""
},
"disclaimer": {
"base": ""
"base": "Вход в систему использует системный браузер, информация о вашем аккаунте не будет видна приложению."
},
"terms": {
"base": ""
"base": "Продолжая, вы соглашаетесь с <0>политикой конфиденциальности</0> и <1>условиями использования</1>."
}
},
"update": {
"alert": {
"title": "",
"message": ""
"title": "Вход выполнен",
"message": "Вы можете войти в другую учетную запись, сохранив существующую учетную запись"
}
}
}

View File

@ -20,11 +20,11 @@
},
"shared": {
"actioned": {
"pinned": "",
"pinned": "Закрепить",
"favourite": "",
"status": "",
"follow": "",
"follow_request": "",
"status": "{{name}} только что запостил",
"follow": "{{name}} подписался (-лась) на вас",
"follow_request": "{{name}} хочет подписаться на вас",
"poll": "",
"reblog": {
"default": "",
@ -92,7 +92,7 @@
"expandHint": ""
},
"filtered": {
"reveal": "",
"reveal": "Все равно показать",
"match_v1": "",
"match_v2_one": "",
"match_v2_other": ""

View File

@ -1,17 +1,17 @@
{
"screenshot": {
"title": "",
"message": ""
"title": "Защита Персональных Данных",
"message": "Пожалуйста, не раскрывайте личность других пользователей, таких как имя пользователя, аватар и т.д. Спасибо!"
},
"localCorrupt": {
"message": ""
"message": "Время входа истекло, повторите вход"
},
"pushError": {
"message": "",
"description": ""
"message": "Ошибка службы Push",
"description": "Пожалуйста, повторно включите push-уведомления в настройках"
},
"shareError": {
"imageNotSupported": "",
"videoNotSupported": ""
"imageNotSupported": "Формат изображения {{type}} не поддерживается",
"videoNotSupported": "Формат видео {{type}} не поддерживается"
}
}

View File

@ -1,6 +1,6 @@
{
"heading": "",
"heading": "Поделиться...",
"content": {
"select_account": ""
"select_account": "Выберите аккаунт"
}
}

View File

@ -1,7 +1,7 @@
{
"content": {
"altText": {
"heading": ""
"heading": "Альтернативный текст"
}
}
}

View File

@ -1,10 +1,10 @@
{
"heading": "",
"heading": "Объявления",
"content": {
"published": "",
"published": "Опубликовано <0 />",
"button": {
"read": "",
"unread": ""
"read": "Прочитано",
"unread": "Отметить как прочитанное"
}
}
}

View File

@ -2,31 +2,31 @@
"heading": {
"left": {
"alert": {
"title": "",
"title": "Отменить изменения?",
"buttons": {
"save": "",
"delete": ""
"save": "Сохранить черновик",
"delete": "Удалить черновик"
}
}
},
"right": {
"button": {
"default": "",
"conversation": "",
"reply": "",
"deleteEdit": "",
"edit": "",
"share": ""
"default": "Пост",
"conversation": "Пост DM",
"reply": "Ответить",
"deleteEdit": "Пост",
"edit": "Пост",
"share": "Пост"
},
"alert": {
"default": {
"title": "",
"button": ""
"title": "Не удалось опубликовать пост",
"button": "Попробовать снова"
},
"removeReply": {
"title": "",
"title": "Пост для ответа не найдет",
"description": "",
"confirm": ""
"confirm": "Удалить ссылку"
}
}
}
@ -34,7 +34,7 @@
"content": {
"root": {
"header": {
"postingAs": "",
"postingAs": "Запостить как @{{acct}}@{{domain}}",
"spoilerInput": {
"placeholder": ""
},
@ -50,7 +50,7 @@
},
"footer": {
"attachments": {
"sensitive": "",
"sensitive": "Пометить вложения как чувствительные",
"remove": {
"accessibilityLabel": ""
},
@ -58,32 +58,32 @@
"accessibilityLabel": ""
},
"upload": {
"accessibilityLabel": ""
"accessibilityLabel": "Загрузить больше вложений"
}
},
"emojis": {
"accessibilityHint": ""
"accessibilityHint": "Нажмите, чтобы добавить эмодзи к посту"
},
"poll": {
"option": {
"placeholder": {
"accessibilityLabel": "",
"single": "",
"multiple": ""
"accessibilityLabel": "Вариант опроса {{index}}",
"single": "Единичный выбор",
"multiple": "Множественный выбор"
}
},
"quantity": {
"reduce": {
"accessibilityLabel": "",
"accessibilityLabel": "Уменьшить количество вариантов ответа до {{amount}}",
"accessibilityHint": ""
},
"increase": {
"accessibilityLabel": "",
"accessibilityLabel": "Уменьшить количество вариантов ответа до {{amount}}",
"accessibilityHint": ""
}
},
"multiple": {
"heading": "",
"heading": "Тип выбора",
"options": {
"single": "",
"multiple": ""
@ -92,25 +92,25 @@
"expiration": {
"heading": "",
"options": {
"300": "",
"1800": "",
"3600": "",
"21600": "",
"86400": "",
"259200": "",
"604800": ""
"300": "5 минут",
"1800": "30 минут",
"3600": "1 час",
"21600": "6 часов",
"86400": "1 день",
"259200": "3 дня",
"604800": "7 дней"
}
}
}
},
"actions": {
"attachment": {
"accessibilityLabel": "",
"accessibilityLabel": "Загрузить вложение",
"accessibilityHint": "",
"failed": {
"alert": {
"title": "",
"button": ""
"button": "Попробовать снова"
}
}
},
@ -120,7 +120,7 @@
},
"visibility": {
"accessibilityLabel": "",
"title": "",
"title": "Видимость поста",
"options": {
"public": "",
"unlisted": "",

View File

@ -4,7 +4,7 @@
"name": "",
"options": {
"showBoosts": "",
"showReplies": ""
"showReplies": "Показать ответы"
}
},
"public": {
@ -26,8 +26,8 @@
},
"notifications": {
"filters": {
"accessibilityLabel": "",
"accessibilityHint": "",
"accessibilityLabel": "Фильтр",
"accessibilityHint": "Фильтр отображаемых типов уведомлений",
"title": "",
"options": {
"follow": "",
@ -254,9 +254,6 @@
"content_true": "",
"content_false": ""
},
"update": {
"title": ""
},
"logout": {
"button": "",
"alert": {

View File

@ -254,9 +254,6 @@
"content_true": "Aktiverad",
"content_false": "Inaktiverad"
},
"update": {
"title": "Uppdatera till senaste versionen"
},
"logout": {
"button": "Logga ut",
"alert": {

View File

@ -254,9 +254,6 @@
"content_true": "Увімкнено",
"content_false": "Вимкнено"
},
"update": {
"title": "Оновити до останньої версії"
},
"logout": {
"button": "Вийти",
"alert": {

View File

@ -254,9 +254,6 @@
"content_true": "Bật",
"content_false": "Tắt"
},
"update": {
"title": "Cập nhật phiên bản mới"
},
"logout": {
"button": "Đăng xuất",
"alert": {

View File

@ -254,9 +254,6 @@
"content_true": "已启用",
"content_false": "未启用"
},
"update": {
"title": "更新至最新版本"
},
"logout": {
"button": "退出当前账号",
"alert": {

View File

@ -254,9 +254,6 @@
"content_true": "啟用",
"content_false": "關閉"
},
"update": {
"title": "更新到最新版本"
},
"logout": {
"button": "登出",
"alert": {

View File

@ -196,7 +196,7 @@ const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-
rightOpenValue={-actionWidth}
previewOpenValue={-actionWidth / 2}
ItemSeparatorComponent={ComponentSeparator}
keyExtractor={item => item.timestamp.toString()}
keyExtractor={item => item.timestamp?.toString()}
/>
</PanGestureHandler>
<Modal

View File

@ -43,11 +43,11 @@ const composePost = async (
e => e && e.length && formData.append('poll[options][]', e)
)
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) {
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!))
}

View File

@ -2,13 +2,11 @@ import GracefullyImage from '@components/GracefullyImage'
import haptics from '@components/haptics'
import Icon from '@components/Icon'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import { useAppDispatch } from '@root/store'
import { RootStackScreenProps, ScreenTabsStackParamList } from '@utils/navigation/navigators'
import { getVersionUpdate, retrieveVersionLatest } from '@utils/slices/appSlice'
import { getPreviousTab } from '@utils/slices/contextsSlice'
import { getInstanceAccount, getInstanceActive } from '@utils/slices/instancesSlice'
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 { useSelector } from 'react-redux'
import TabLocal from './Tabs/Local'
@ -55,17 +53,6 @@ const ScreenTabs = React.memo(
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 (
<Tab.Navigator
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-Compose' component={composeComponent} listeners={composeListeners} />
<Tab.Screen name='Tab-Notifications' component={TabNotifications} />
<Tab.Screen
name='Tab-Me'
component={TabMe}
options={tabMeOptions}
listeners={meListeners}
/>
<Tab.Screen name='Tab-Me' component={TabMe} listeners={meListeners} />
</Tab.Navigator>
)
},

View File

@ -8,12 +8,7 @@ import { isDevelopment } from '@utils/checkEnvironment'
import { useAppsQuery } from '@utils/queryHooks/apps'
import { useProfileQuery } from '@utils/queryHooks/profile'
import { getExpoToken, retrieveExpoToken } from '@utils/slices/appSlice'
import {
PUSH_ADMIN,
PUSH_DEFAULT,
setChannels,
usePushFeatures
} from '@utils/slices/instances/push/utils'
import { PUSH_ADMIN, PUSH_DEFAULT, usePushFeatures } from '@utils/slices/instances/push/utils'
import { updateInstancePush } from '@utils/slices/instances/updatePush'
import { updateInstancePushAlert } from '@utils/slices/instances/updatePushAlert'
import { updateInstancePushDecode } from '@utils/slices/instances/updatePushDecode'
@ -25,7 +20,7 @@ import * as Notifications from 'expo-notifications'
import * as WebBrowser from 'expo-web-browser'
import React, { useState, useEffect } from 'react'
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'
const TabMePush: React.FC = () => {
@ -50,11 +45,7 @@ const TabMePush: React.FC = () => {
setPushEnabled(permissions.granted)
setPushCanAskAgain(permissions.canAskAgain)
layoutAnimation()
if (Platform.OS === 'android') {
await setChannels(instance)
dispatch(retrieveExpoToken())
}
dispatch(retrieveExpoToken())
}
if (appsQuery.data?.vapid_key) {

View File

@ -12,12 +12,8 @@ import accountReducer from '@screens/Tabs/Shared/Account/utils/reducer'
import { useProfileQuery } from '@utils/queryHooks/profile'
import { getInstanceActive } from '@utils/slices/instancesSlice'
import React, { useReducer, useRef } from 'react'
import Animated, {
useAnimatedScrollHandler,
useSharedValue
} from 'react-native-reanimated'
import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated'
import { useSelector } from 'react-redux'
import Update from './Root/Update'
const TabMeRoot: React.FC = () => {
const instanceActive = useSelector(getInstanceActive)
@ -29,10 +25,7 @@ const TabMeRoot: React.FC = () => {
const scrollRef = useRef<Animated.ScrollView>(null)
useScrollToTop(scrollRef)
const [accountState, accountDispatch] = useReducer(
accountReducer,
accountInitialState
)
const [accountState, accountDispatch] = useReducer(accountReducer, accountInitialState)
const scrollY = useSharedValue(0)
const onScroll = useAnimatedScrollHandler(event => {
@ -41,22 +34,15 @@ const TabMeRoot: React.FC = () => {
return (
<AccountContext.Provider value={{ accountState, accountDispatch }}>
{instanceActive !== -1 && data ? (
<AccountNav scrollY={scrollY} account={data} />
) : null}
{instanceActive !== -1 && data ? <AccountNav scrollY={scrollY} account={data} /> : null}
<Animated.ScrollView
ref={scrollRef}
keyboardShouldPersistTaps='handled'
onScroll={onScroll}
scrollEventThrottle={16}
>
{instanceActive !== -1 ? (
<MyInfo account={data} />
) : (
<ComponentInstance />
)}
{instanceActive !== -1 ? <MyInfo account={data} /> : <ComponentInstance />}
{instanceActive !== -1 ? <Collections /> : null}
<Update />
<Settings />
{instanceActive !== -1 ? <AccountInformationSwitch /> : null}
{instanceActive !== -1 ? <Logout /> : null}

View File

@ -115,7 +115,11 @@ const Collections: React.FC = () => {
iconFront={instancePush ? 'Bell' : 'BellOff'}
iconBack='ChevronRight'
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')}
/>
</MenuContainer>

View File

@ -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

View File

@ -27,7 +27,7 @@ const TabMeSettingsLanguage: React.FC<TabMeStackScreenProps<'Tab-Me-Settings-Lan
// Update Android notification channel language
if (Platform.OS === 'android') {
instances.forEach(setChannels)
instances.forEach(instance => setChannels(instance, true))
}
navigation.pop(1)

View File

@ -1,38 +1,43 @@
import apiTooot from '@api/tooot'
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { RootState } from '@root/store'
import { isDevelopment } from '@utils/checkEnvironment'
import Constants from 'expo-constants'
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> => {
if (isDevelopment) {
return 'ExponentPushToken[DEVELOPMENT_1]'
}
export const retrieveExpoToken = createAsyncThunk(
'app/expoToken',
async (_, { getState }): Promise<string> => {
const instance = getInstance(getState() as RootState)
const expoToken = getExpoToken(getState() as RootState)
const res = await Notifications.getExpoPushTokenAsync({
experienceId: '@xmflsct/tooot',
applicationId: 'com.xmflsct.app.tooot'
})
return res.data
})
if (Platform.OS === 'android') {
await setChannels(instance)
}
export const retrieveVersionLatest = createAsyncThunk(
'app/versionUpdate',
async (): Promise<string> => {
const res = await apiTooot<{ latest: string }>({ method: 'get', url: 'version.json' })
return res.body.latest
if (expoToken?.length) {
return expoToken
} else {
if (isDevelopment) {
return 'ExponentPushToken[DEVELOPMENT_1]'
}
const res = await Notifications.getExpoPushTokenAsync({
experienceId: '@xmflsct/tooot',
applicationId: 'com.xmflsct.app.tooot'
})
return res.data
}
}
)
export type AppState = {
expoToken?: string
versionUpdate: boolean
}
export const appInitialState: AppState = {
expoToken: undefined,
versionUpdate: false
expoToken: undefined
}
const appSlice = createSlice({
@ -40,22 +45,14 @@ const appSlice = createSlice({
initialState: appInitialState,
reducers: {},
extraReducers: builder => {
builder
.addCase(retrieveExpoToken.fulfilled, (state, action) => {
if (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)
}
})
builder.addCase(retrieveExpoToken.fulfilled, (state, action) => {
if (action.payload) {
state.expoToken = action.payload
}
})
}
})
export const getExpoToken = (state: RootState) => state.app.expoToken
export const getVersionUpdate = (state: RootState) => state.app.versionUpdate
export default appSlice.reducer

View File

@ -28,7 +28,7 @@ const subscribe = async ({
}) => {
return apiTooot({
method: 'post',
url: `/push/subscribe/${expoToken}/${instanceUrl}/${accountId}`,
url: `push/subscribe/${expoToken}/${instanceUrl}/${accountId}`,
body: { accountFull, serverKey, auth }
})
}
@ -97,7 +97,7 @@ const pushRegister = async (
})
if (Platform.OS === 'android') {
setChannels(instance)
setChannels(instance, true)
}
return Promise.resolve(auth)

View File

@ -21,7 +21,7 @@ const pushUnregister = async (state: RootState, expoToken: string) => {
await apiTooot<{ endpoint: string; publicKey: string; auth: string }>({
method: 'delete',
url: `/push/unsubscribe/${expoToken}/${instance.url}/${instance.account.id}`
url: `push/unsubscribe/${expoToken}/${instance.url}/${instance.account.id}`
})
if (Platform.OS === 'android') {

View File

@ -62,7 +62,7 @@ export const PUSH_ADMIN = (
}
}) 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 deleteChannel = async (type: string) =>
@ -82,6 +82,9 @@ export const setChannels = async (instance: InstanceLatest) => {
const profileQuery = await queryClient.fetchQuery(queryKey, queryFunctionProfile)
const channelGroup = await Notifications.getNotificationChannelGroupAsync(account)
if (channelGroup && !reset) {
return
}
if (!channelGroup) {
await Notifications.setNotificationChannelGroupAsync(account, { name: account })
}

View File

@ -25,14 +25,14 @@ export const updateInstancePushDecode = createAsyncThunk(
await apiTooot({
method: 'put',
url: `/push/update-decode/${expoToken}/${instance.url}/${instance.account.id}`,
url: `push/update-decode/${expoToken}/${instance.url}/${instance.account.id}`,
body: {
auth: !disable ? null : instance.push.keys.auth
}
})
if (Platform.OS === 'android') {
setChannels(instance)
setChannels(instance, true)
}
return Promise.resolve({ disable })