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:
@ -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
|
||||
|
@ -1,5 +1,3 @@
|
||||
toooting愉快!此版本包括以下改进和修复:
|
||||
- 改进过滤体验,与v4.0以上版本一致
|
||||
- 支持查看用户的头像和横幅图片
|
||||
- 修复iPad部分尺寸问题(非优化)
|
||||
- 试验性(!)支持Pleroma
|
||||
- 修复错误的升级通知
|
||||
- 修复部分应用崩溃
|
||||
|
@ -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",
|
||||
|
@ -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: {
|
||||
|
@ -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 => {
|
||||
|
@ -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({
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -218,7 +218,7 @@ const ParseHTML = React.memo(
|
||||
if (children) {
|
||||
return (
|
||||
<ParseEmojis
|
||||
content={children.toString()}
|
||||
content={children?.toString()}
|
||||
emojis={emojis}
|
||||
size={size}
|
||||
adaptiveSize={adaptiveSize}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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}}?"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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"
|
||||
|
@ -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": {
|
||||
|
@ -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ó"
|
||||
|
@ -254,9 +254,6 @@
|
||||
"content_true": "",
|
||||
"content_false": ""
|
||||
},
|
||||
"update": {
|
||||
"title": ""
|
||||
},
|
||||
"logout": {
|
||||
"button": "",
|
||||
"alert": {
|
||||
|
@ -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?"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -254,9 +254,6 @@
|
||||
"content_true": "Aktiviert",
|
||||
"content_false": "Deaktiviert"
|
||||
},
|
||||
"update": {
|
||||
"title": "Auf neueste Version aktualisiert"
|
||||
},
|
||||
"logout": {
|
||||
"button": "Abmelden",
|
||||
"alert": {
|
||||
|
@ -254,9 +254,6 @@
|
||||
"content_true": "Enabled",
|
||||
"content_false": "Disabled"
|
||||
},
|
||||
"update": {
|
||||
"title": "Update to latest version"
|
||||
},
|
||||
"logout": {
|
||||
"button": "Log out",
|
||||
"alert": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"buttons": {
|
||||
"OK": "OK",
|
||||
"OK": "De acuerdo",
|
||||
"apply": "Aplicar",
|
||||
"cancel": "Cancelar",
|
||||
"discard": "Descartar",
|
||||
|
@ -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."
|
||||
}
|
||||
},
|
||||
|
@ -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"
|
||||
|
@ -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",
|
||||
|
@ -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": {
|
||||
|
@ -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": {
|
||||
|
@ -254,9 +254,6 @@
|
||||
"content_true": "Abilitato",
|
||||
"content_false": "Disabilitato"
|
||||
},
|
||||
"update": {
|
||||
"title": "Aggiorna all'ultima versione"
|
||||
},
|
||||
"logout": {
|
||||
"button": "Esci dall'account",
|
||||
"alert": {
|
||||
|
@ -254,9 +254,6 @@
|
||||
"content_true": "有効",
|
||||
"content_false": "無効"
|
||||
},
|
||||
"update": {
|
||||
"title": "最新バージョンへのアップデート"
|
||||
},
|
||||
"logout": {
|
||||
"button": "ログアウト",
|
||||
"alert": {
|
||||
|
@ -254,9 +254,6 @@
|
||||
"content_true": "활성화됨",
|
||||
"content_false": "비활성화됨"
|
||||
},
|
||||
"update": {
|
||||
"title": "최신 버전으로 업데이트"
|
||||
},
|
||||
"logout": {
|
||||
"button": "로그아웃",
|
||||
"alert": {
|
||||
|
@ -254,9 +254,6 @@
|
||||
"content_true": "Ingeschakeld",
|
||||
"content_false": "Uitgeschakeld"
|
||||
},
|
||||
"update": {
|
||||
"title": "Bijwerken naar de laatste versie"
|
||||
},
|
||||
"logout": {
|
||||
"button": "Uitloggen",
|
||||
"alert": {
|
||||
|
@ -254,9 +254,6 @@
|
||||
"content_true": "",
|
||||
"content_false": ""
|
||||
},
|
||||
"update": {
|
||||
"title": ""
|
||||
},
|
||||
"logout": {
|
||||
"button": "",
|
||||
"alert": {
|
||||
|
@ -254,9 +254,6 @@
|
||||
"content_true": "Habilitado",
|
||||
"content_false": "Desabilitado"
|
||||
},
|
||||
"update": {
|
||||
"title": "Atualize para a versão mais recente"
|
||||
},
|
||||
"logout": {
|
||||
"button": "Sair",
|
||||
"alert": {
|
||||
|
@ -1 +1,3 @@
|
||||
{}
|
||||
{
|
||||
"frequentUsed": "Часто используемые"
|
||||
}
|
@ -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": "Вы можете войти в другую учетную запись, сохранив существующую учетную запись"
|
||||
}
|
||||
}
|
||||
}
|
@ -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": ""
|
||||
|
@ -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}} не поддерживается"
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"heading": "",
|
||||
"heading": "Поделиться...",
|
||||
"content": {
|
||||
"select_account": ""
|
||||
"select_account": "Выберите аккаунт"
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"content": {
|
||||
"altText": {
|
||||
"heading": ""
|
||||
"heading": "Альтернативный текст"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
{
|
||||
"heading": "",
|
||||
"heading": "Объявления",
|
||||
"content": {
|
||||
"published": "",
|
||||
"published": "Опубликовано <0 />",
|
||||
"button": {
|
||||
"read": "",
|
||||
"unread": ""
|
||||
"read": "Прочитано",
|
||||
"unread": "Отметить как прочитанное"
|
||||
}
|
||||
}
|
||||
}
|
@ -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": "",
|
||||
|
@ -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": {
|
||||
|
@ -254,9 +254,6 @@
|
||||
"content_true": "Aktiverad",
|
||||
"content_false": "Inaktiverad"
|
||||
},
|
||||
"update": {
|
||||
"title": "Uppdatera till senaste versionen"
|
||||
},
|
||||
"logout": {
|
||||
"button": "Logga ut",
|
||||
"alert": {
|
||||
|
@ -254,9 +254,6 @@
|
||||
"content_true": "Увімкнено",
|
||||
"content_false": "Вимкнено"
|
||||
},
|
||||
"update": {
|
||||
"title": "Оновити до останньої версії"
|
||||
},
|
||||
"logout": {
|
||||
"button": "Вийти",
|
||||
"alert": {
|
||||
|
@ -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": {
|
||||
|
@ -254,9 +254,6 @@
|
||||
"content_true": "已启用",
|
||||
"content_false": "未启用"
|
||||
},
|
||||
"update": {
|
||||
"title": "更新至最新版本"
|
||||
},
|
||||
"logout": {
|
||||
"button": "退出当前账号",
|
||||
"alert": {
|
||||
|
@ -254,9 +254,6 @@
|
||||
"content_true": "啟用",
|
||||
"content_false": "關閉"
|
||||
},
|
||||
"update": {
|
||||
"title": "更新到最新版本"
|
||||
},
|
||||
"logout": {
|
||||
"button": "登出",
|
||||
"alert": {
|
||||
|
@ -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
|
||||
|
@ -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!))
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
)
|
||||
},
|
||||
|
@ -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) {
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
if (Platform.OS === 'android') {
|
||||
instances.forEach(setChannels)
|
||||
instances.forEach(instance => setChannels(instance, true))
|
||||
}
|
||||
|
||||
navigation.pop(1)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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') {
|
||||
|
@ -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 })
|
||||
}
|
||||
|
@ -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 })
|
||||
|
Reference in New Issue
Block a user