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

Merge branch 'main' into candidate

This commit is contained in:
xmflsct
2023-01-27 22:42:39 +01:00
19 changed files with 404 additions and 422 deletions

View File

@@ -2,7 +2,7 @@ import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import { Fragment } from 'react' import { Fragment } from 'react'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import { View } from 'react-native' import { View, ViewStyle } from 'react-native'
import { TouchableNativeFeedback } from 'react-native-gesture-handler' import { TouchableNativeFeedback } from 'react-native-gesture-handler'
import Icon from './Icon' import Icon from './Icon'
import CustomText from './Text' import CustomText from './Text'
@@ -11,9 +11,10 @@ export type Props = {
onPress: () => void onPress: () => void
filter: Mastodon.Filter<'v2'> filter: Mastodon.Filter<'v2'>
button?: React.ReactNode button?: React.ReactNode
style?: ViewStyle
} }
export const Filter: React.FC<Props> = ({ onPress, filter, button }) => { export const Filter: React.FC<Props> = ({ onPress, filter, button, style }) => {
const { t } = useTranslation(['common', 'screenTabs']) const { t } = useTranslation(['common', 'screenTabs'])
const { colors } = useTheme() const { colors } = useTheme()
@@ -24,7 +25,8 @@ export const Filter: React.FC<Props> = ({ onPress, filter, button }) => {
paddingVertical: StyleConstants.Spacing.S, paddingVertical: StyleConstants.Spacing.S,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.backgroundDefault backgroundColor: colors.backgroundDefault,
...style
}} }}
> >
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
@@ -83,12 +85,7 @@ export const Filter: React.FC<Props> = ({ onPress, filter, button }) => {
{filter.context.map((c, index) => ( {filter.context.map((c, index) => (
<Fragment key={index}> <Fragment key={index}>
<CustomText <CustomText
style={{ style={{ color: colors.secondary }}
color: colors.secondary,
textDecorationColor: colors.disabled,
textDecorationLine: 'underline',
textDecorationStyle: 'solid'
}}
children={t(`screenTabs:me.preferencesFilters.contexts.${c}`)} children={t(`screenTabs:me.preferencesFilters.contexts.${c}`)}
/> />
<CustomText children={t('common:separator')} /> <CustomText children={t('common:separator')} />

View File

@@ -12,7 +12,7 @@ const Hr: React.FC<{ style?: ViewStyle }> = ({ style }) => {
borderTopColor: colors.border, borderTopColor: colors.border,
borderTopWidth: 1, borderTopWidth: 1,
height: 1, height: 1,
marginVertical: StyleConstants.Spacing.S paddingVertical: StyleConstants.Spacing.S
}, },
style style
]} ]}

View File

@@ -0,0 +1,57 @@
import { StyleConstants } from '@utils/styles/constants'
import { ColorValue, TouchableNativeFeedback, View } from 'react-native'
import { SwipeListView } from 'react-native-swipe-list-view'
import haptics from './haptics'
import Icon, { IconName } from './Icon'
import ComponentSeparator from './Separator'
export type Props = {
actions: {
onPress: (item: any) => void
color: ColorValue
icon: IconName
haptic?: Parameters<typeof haptics>['0']
}[]
}
export const SwipeToActions = <T extends unknown>({
actions,
...rest
}: Props & SwipeListView<T>['props']) => {
const perActionWidth = StyleConstants.Spacing.L * 2 + StyleConstants.Font.Size.L
return (
<SwipeListView
renderHiddenItem={({ item }) => (
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'flex-end' }}>
{actions.map((action, index) => (
<TouchableNativeFeedback
key={index}
onPress={() => {
haptics(action.haptic || 'Light')
action.onPress({ item })
}}
>
<View
style={{
paddingHorizontal: StyleConstants.Spacing.L,
flexBasis: perActionWidth,
backgroundColor: action.color,
justifyContent: 'center',
alignItems: 'center'
}}
>
<Icon name={action.icon} color='white' size={StyleConstants.Font.Size.L} />
</View>
</TouchableNativeFeedback>
))}
</View>
)}
rightOpenValue={-perActionWidth * actions.length}
disableRightSwipe
closeOnRowPress
ItemSeparatorComponent={ComponentSeparator}
{...rest}
/>
)
}

View File

@@ -6,7 +6,7 @@
"action_false": "Segueix l'usuari", "action_false": "Segueix l'usuari",
"action_true": "Deixa de seguir l'usuari" "action_true": "Deixa de seguir l'usuari"
}, },
"inLists": "", "inLists": "Llistes que hi sigui l'usuari...",
"showBoosts": { "showBoosts": {
"action_false": "Mostra els impulsos de l'usuari", "action_false": "Mostra els impulsos de l'usuari",
"action_true": "Oculta els impulsos de l'usuari" "action_true": "Oculta els impulsos de l'usuari"
@@ -21,7 +21,7 @@
"succeed_locked": "Enviada la sol·licitud de seguiment a @{{target}} com {{source}}, pendent d'aprovar-la", "succeed_locked": "Enviada la sol·licitud de seguiment a @{{target}} com {{source}}, pendent d'aprovar-la",
"failed": "Segueix com" "failed": "Segueix com"
}, },
"blockReport": "", "blockReport": "Bloqueja i denuncia",
"block": { "block": {
"action_false": "Bloqueja l'usuari", "action_false": "Bloqueja l'usuari",
"action_true": "Deixa de bloquejar l'usuari", "action_true": "Deixa de bloquejar l'usuari",
@@ -56,11 +56,11 @@
}, },
"hashtag": { "hashtag": {
"follow": { "follow": {
"action_false": "", "action_false": "Segueix",
"action_true": "" "action_true": "Deixa de seguir"
}, },
"filter": { "filter": {
"action": "" "action": "Filtra l'etiqueta..."
} }
}, },
"share": { "share": {
@@ -99,8 +99,8 @@
"action_true": "Deixa de fixar la publicació" "action_true": "Deixa de fixar la publicació"
}, },
"filter": { "filter": {
"action_false": "", "action_false": "Filtra la publicació...",
"action_true": "" "action_true": "Gestiona els filtres..."
} }
} }
} }

View File

@@ -211,7 +211,7 @@
}, },
"keywords": "Coincidències per aquestes paraules claus", "keywords": "Coincidències per aquestes paraules claus",
"keyword": "Paraula clau", "keyword": "Paraula clau",
"statuses": "" "statuses": "Coincideixen aquestes publicacions"
}, },
"profile": { "profile": {
"feedback": { "feedback": {
@@ -400,14 +400,14 @@
"name": "Multimèdia de <0 /><1></1>" "name": "Multimèdia de <0 /><1></1>"
}, },
"filter": { "filter": {
"name": "", "name": "Afegeix al filtre",
"existed": "" "existed": "Existia sota aquests filtres"
}, },
"history": { "history": {
"name": "Edita l'historial" "name": "Edita l'historial"
}, },
"report": { "report": {
"name": "Denuncia {{acct}}", "name": "Denúncia a {{acct}}",
"report": "Denúncia", "report": "Denúncia",
"forward": { "forward": {
"heading": "Envia anònimament al servidor remot {{instance}}" "heading": "Envia anònimament al servidor remot {{instance}}"

View File

@@ -6,7 +6,7 @@
"action_false": "Seguir usuario", "action_false": "Seguir usuario",
"action_true": "Dejar de seguir usuario" "action_true": "Dejar de seguir usuario"
}, },
"inLists": "", "inLists": "Listas que contienen el usuario...",
"showBoosts": { "showBoosts": {
"action_false": "Mostrar los impulsos del usuario", "action_false": "Mostrar los impulsos del usuario",
"action_true": "Ocultar los impulsos del usuario" "action_true": "Ocultar los impulsos del usuario"
@@ -21,7 +21,7 @@
"succeed_locked": "Enviado la solicitud de seguimiento a @{{target}} como {{source}}, pendiente de aprobación", "succeed_locked": "Enviado la solicitud de seguimiento a @{{target}} como {{source}}, pendiente de aprobación",
"failed": "Seguir como" "failed": "Seguir como"
}, },
"blockReport": "", "blockReport": "Bloquear y denunciar",
"block": { "block": {
"action_false": "Bloquear usuario", "action_false": "Bloquear usuario",
"action_true": "Desbloquear usuario", "action_true": "Desbloquear usuario",
@@ -56,11 +56,11 @@
}, },
"hashtag": { "hashtag": {
"follow": { "follow": {
"action_false": "", "action_false": "Seguir",
"action_true": "" "action_true": "Dejar de seguir"
}, },
"filter": { "filter": {
"action": "" "action": "Filtrar la etiqueta..."
} }
}, },
"share": { "share": {
@@ -99,8 +99,8 @@
"action_true": "Desfijar toot" "action_true": "Desfijar toot"
}, },
"filter": { "filter": {
"action_false": "", "action_false": "Filtrar la publicación...",
"action_true": "" "action_true": "Gestiona los filtros..."
} }
} }
} }

View File

@@ -70,16 +70,16 @@
"name": "Notificaciones push" "name": "Notificaciones push"
}, },
"preferences": { "preferences": {
"name": "" "name": "Preferencias"
}, },
"preferencesFilters": { "preferencesFilters": {
"name": "" "name": "Todos los filtros de contenido"
}, },
"preferencesFilterAdd": { "preferencesFilterAdd": {
"name": "" "name": "Crear filtro"
}, },
"preferencesFilterEdit": { "preferencesFilterEdit": {
"name": "" "name": "Editar filtro"
}, },
"profile": { "profile": {
"name": "Editar perfil" "name": "Editar perfil"
@@ -136,82 +136,82 @@
}, },
"preferences": { "preferences": {
"visibility": { "visibility": {
"title": "", "title": "Visibilidad de publicación predeterminada",
"options": { "options": {
"public": "", "public": "Público",
"unlisted": "", "unlisted": "No listado",
"private": "" "private": "Solo seguidores"
} }
}, },
"sensitive": { "sensitive": {
"title": "" "title": "Marcar el contenido multimedia como sensibles por defecto"
}, },
"media": { "media": {
"title": "", "title": "Mostrar el contenido multimedia",
"options": { "options": {
"default": "", "default": "Ocultar los contenidos multimedia marcados como sensibles",
"show_all": "", "show_all": "Mostrar siempre el contenido multimedia",
"hide_all": "" "hide_all": "Siempre ocultar el contenido multimedia"
} }
}, },
"spoilers": { "spoilers": {
"title": "" "title": "Siempre expandir las publicaciones marcadas con advertencias de contenido"
}, },
"autoplay_gifs": { "autoplay_gifs": {
"title": "" "title": "Reproduce automáticamente los GIF"
}, },
"filters": { "filters": {
"title": "", "title": "Filtros de contenido",
"content": "" "content": "{{count}} activo"
}, },
"web_only": { "web_only": {
"title": "", "title": "Actualizar ajustes",
"description": "" "description": "Los ajustes a continuación solo se pueden actualizar mediante la interfaz web"
} }
}, },
"preferencesFilters": { "preferencesFilters": {
"expired": "", "expired": "Expirado",
"keywords_one": "", "keywords_one": "{{count}} palabra clave",
"keywords_other": "", "keywords_other": "{{count}} palabras clave",
"statuses_one": "", "statuses_one": "{{count}} publicación",
"statuses_other": "", "statuses_other": "{{count}} publicaciones",
"context": "", "context": "Se aplica en <0 />",
"contexts": { "contexts": {
"home": "", "home": "Seguidos y listas",
"notifications": "", "notifications": "Notificación",
"public": "", "public": "Federado",
"thread": "", "thread": "Conversación",
"account": "" "account": "Perfil"
} }
}, },
"preferencesFilter": { "preferencesFilter": {
"name": "", "name": "Nombre",
"expiration": "", "expiration": "Vencimiento",
"expirationOptions": { "expirationOptions": {
"0": "", "0": "Nunca",
"1800": "", "1800": "Después de 30 minutos",
"3600": "", "3600": "Después de 1 hora",
"43200": "", "43200": "Después de 12 horas",
"86400": "", "86400": "Después de 1 día",
"604800": "", "604800": "Después de 1 semana",
"18144000": "" "18144000": "Después de 1 mes"
}, },
"context": "", "context": "Se aplica en",
"contexts": { "contexts": {
"home": "", "home": "Seguidos y listas",
"notifications": "", "notifications": "Notificación",
"public": "", "public": "Cronología federada",
"thread": "", "thread": "Vista de conversación",
"account": "" "account": "Vista de perfil"
}, },
"action": "", "action": "Al coincidir",
"actions": { "actions": {
"warn": "", "warn": "",
"hide": "" "hide": "Oculto completamente"
}, },
"keywords": "", "keywords": "Coincide con estas palabras clave",
"keyword": "", "keyword": "Palabra clave",
"statuses": "" "statuses": "Coincide con estas publicaciones"
}, },
"profile": { "profile": {
"feedback": { "feedback": {
@@ -400,8 +400,8 @@
"name": "Multimedia de <0 /><1></1>" "name": "Multimedia de <0 /><1></1>"
}, },
"filter": { "filter": {
"name": "", "name": "Añadir al filtro",
"existed": "" "existed": "Existe en estos filtros"
}, },
"history": { "history": {
"name": "Historial de ediciones" "name": "Historial de ediciones"

View File

@@ -6,7 +6,7 @@
"action_false": "", "action_false": "",
"action_true": "" "action_true": ""
}, },
"inLists": "", "inLists": "Erabiltzaile hau zerrenda hauetan dago...",
"showBoosts": { "showBoosts": {
"action_false": "", "action_false": "",
"action_true": "" "action_true": ""
@@ -16,12 +16,12 @@
"action_true": "" "action_true": ""
}, },
"followAs": { "followAs": {
"trigger": "", "trigger": "Honela jarraitu...",
"succeed_default": "", "succeed_default": "",
"succeed_locked": "", "succeed_locked": "",
"failed": "" "failed": ""
}, },
"blockReport": "", "blockReport": "Blokeatu eta salatu",
"block": { "block": {
"action_false": "", "action_false": "",
"action_true": "", "action_true": "",
@@ -56,11 +56,11 @@
}, },
"hashtag": { "hashtag": {
"follow": { "follow": {
"action_false": "", "action_false": "Jarraitu",
"action_true": "" "action_true": "Jarraitzeari utzi"
}, },
"filter": { "filter": {
"action": "" "action": "Traola iragazi..."
} }
}, },
"share": { "share": {
@@ -99,8 +99,8 @@
"action_true": "" "action_true": ""
}, },
"filter": { "filter": {
"action_false": "", "action_false": "Tuta iragazi...",
"action_true": "" "action_true": "Iragazkiak kudeatu..."
} }
} }
} }

View File

@@ -6,7 +6,7 @@
"action_false": "Volg gebruiker", "action_false": "Volg gebruiker",
"action_true": "Ontvolg" "action_true": "Ontvolg"
}, },
"inLists": "", "inLists": "Lijsten met gebruiker...",
"showBoosts": { "showBoosts": {
"action_false": "Boosts van gebruiker weergeven", "action_false": "Boosts van gebruiker weergeven",
"action_true": "Boosts van gebruiker verbergen" "action_true": "Boosts van gebruiker verbergen"
@@ -16,12 +16,12 @@
"action_true": "Dempen opheffen voor gebruiker" "action_true": "Dempen opheffen voor gebruiker"
}, },
"followAs": { "followAs": {
"trigger": "", "trigger": "Volg als ...",
"succeed_default": "Je volgt nu @{{target}} met @{{source}}", "succeed_default": "Je volgt nu @{{target}} met @{{source}}",
"succeed_locked": "Verstuurde het volgverzoek naar @{{target}} met {{source}}, in afwachting van goedkeuring", "succeed_locked": "Verstuurde het volgverzoek naar @{{target}} met {{source}}, in afwachting van goedkeuring",
"failed": "Volg als" "failed": "Volg als"
}, },
"blockReport": "", "blockReport": "Blokkeren en rapporten",
"block": { "block": {
"action_false": "Gebruiker blokkeren", "action_false": "Gebruiker blokkeren",
"action_true": "Gebruiker deblokkeren", "action_true": "Gebruiker deblokkeren",
@@ -56,11 +56,11 @@
}, },
"hashtag": { "hashtag": {
"follow": { "follow": {
"action_false": "", "action_false": "Volg",
"action_true": "" "action_true": "Ontvolg"
}, },
"filter": { "filter": {
"action": "" "action": "Filter hashtag ..."
} }
}, },
"share": { "share": {
@@ -99,8 +99,8 @@
"action_true": "Toot losmaken" "action_true": "Toot losmaken"
}, },
"filter": { "filter": {
"action_false": "", "action_false": "Filter toot ...",
"action_true": "" "action_true": "Filters beheren ..."
} }
} }
} }

View File

@@ -70,16 +70,16 @@
"name": "Push Melding" "name": "Push Melding"
}, },
"preferences": { "preferences": {
"name": "" "name": "Voorkeuren"
}, },
"preferencesFilters": { "preferencesFilters": {
"name": "" "name": "Alle inhoudsfilters"
}, },
"preferencesFilterAdd": { "preferencesFilterAdd": {
"name": "" "name": "Filter Maken"
}, },
"preferencesFilterEdit": { "preferencesFilterEdit": {
"name": "" "name": "Bewerk filter"
}, },
"profile": { "profile": {
"name": "Profiel bewerken" "name": "Profiel bewerken"
@@ -136,82 +136,82 @@
}, },
"preferences": { "preferences": {
"visibility": { "visibility": {
"title": "", "title": "Standaard zichtbaarheid van berichten",
"options": { "options": {
"public": "", "public": "Openbaar",
"unlisted": "", "unlisted": "Niet openbaar",
"private": "" "private": "Alleen volgers"
} }
}, },
"sensitive": { "sensitive": {
"title": "" "title": "Media standaard als gevoelig markeren"
}, },
"media": { "media": {
"title": "", "title": "Mediaweergave",
"options": { "options": {
"default": "", "default": "Als gevoelig gemarkeerde media verbergen",
"show_all": "", "show_all": "Media altijd tonen",
"hide_all": "" "hide_all": "Media altijd verbergen"
} }
}, },
"spoilers": { "spoilers": {
"title": "" "title": "Altijd toots met tekstwaarschuwingen uitklappen"
}, },
"autoplay_gifs": { "autoplay_gifs": {
"title": "" "title": "GIF automatisch afspelen in toots"
}, },
"filters": { "filters": {
"title": "", "title": "Inhoud Filters",
"content": "" "content": "{{count}} actief"
}, },
"web_only": { "web_only": {
"title": "", "title": "Instellingen bijwerken",
"description": "" "description": "Instellingen hieronder kunnen alleen met behulp van de webUI worden bijgewerkt"
} }
}, },
"preferencesFilters": { "preferencesFilters": {
"expired": "", "expired": "Verlopen",
"keywords_one": "", "keywords_one": "{{count}} trefwoord",
"keywords_other": "", "keywords_other": "{{count}} trefwoorden",
"statuses_one": "", "statuses_one": "{{count}} toot",
"statuses_other": "", "statuses_other": "{{count}} toots",
"context": "", "context": "Van toepassing in <0 />",
"contexts": { "contexts": {
"home": "", "home": "volgend en lijsten",
"notifications": "", "notifications": "melding",
"public": "", "public": "gefedereerd",
"thread": "", "thread": "gesprek",
"account": "" "account": "profiel"
} }
}, },
"preferencesFilter": { "preferencesFilter": {
"name": "", "name": "Naam",
"expiration": "", "expiration": "Vervaldatum",
"expirationOptions": { "expirationOptions": {
"0": "", "0": "Nooit",
"1800": "", "1800": "Na 30 minuten",
"3600": "", "3600": "Na 1 uur",
"43200": "", "43200": "Na 12 uur",
"86400": "", "86400": "Na 1 dag",
"604800": "", "604800": "Na 1 week",
"18144000": "" "18144000": "Na 1 maand"
}, },
"context": "", "context": "Van toepassing in",
"contexts": { "contexts": {
"home": "", "home": "Volgend en lijsten",
"notifications": "", "notifications": "Melding",
"public": "", "public": "Federale tijdlijn",
"thread": "", "thread": "Gesprek weergave",
"account": "" "account": "Profiel weergave"
}, },
"action": "", "action": "Bij een overeenkomst",
"actions": { "actions": {
"warn": "", "warn": "Ingeklapt maar kan worden onthuld",
"hide": "" "hide": "Volledig verborgen"
}, },
"keywords": "", "keywords": "Komt overeen met deze trefwoorden",
"keyword": "", "keyword": "Trefwoord",
"statuses": "" "statuses": "Komt overeen met deze toots"
}, },
"profile": { "profile": {
"feedback": { "feedback": {
@@ -400,8 +400,8 @@
"name": "<0 /><1>'s media</1>" "name": "<0 /><1>'s media</1>"
}, },
"filter": { "filter": {
"name": "", "name": "Toevoegen aan filter",
"existed": "" "existed": "Bestaat in deze filters"
}, },
"history": { "history": {
"name": "Geschiedenis bewerken" "name": "Geschiedenis bewerken"

View File

@@ -6,7 +6,7 @@
"action_false": "Підписатися на користувача", "action_false": "Підписатися на користувача",
"action_true": "Відписатися від користувача" "action_true": "Відписатися від користувача"
}, },
"inLists": "", "inLists": "Списки, в яких є користувач ...",
"showBoosts": { "showBoosts": {
"action_false": "Показати передмухи користувача", "action_false": "Показати передмухи користувача",
"action_true": "Приховати передмухи користувача" "action_true": "Приховати передмухи користувача"
@@ -16,12 +16,12 @@
"action_true": "Зняти заглушення з користувача" "action_true": "Зняти заглушення з користувача"
}, },
"followAs": { "followAs": {
"trigger": "", "trigger": "Стежити як ...",
"succeed_default": "Тепер ви стежите за @{{target}} з @{{source}}", "succeed_default": "Тепер ви стежите за @{{target}} з @{{source}}",
"succeed_locked": "Запит на стеження за @{{target}} з {{source}} надіслано, очікується затвердження", "succeed_locked": "Запит на стеження за @{{target}} з {{source}} надіслано, очікується затвердження",
"failed": "Стежити як" "failed": "Стежити як"
}, },
"blockReport": "", "blockReport": "Заблокувати й поскаржитися",
"block": { "block": {
"action_false": "Заблокувати користувача", "action_false": "Заблокувати користувача",
"action_true": "Розблокувати користувача", "action_true": "Розблокувати користувача",
@@ -56,11 +56,11 @@
}, },
"hashtag": { "hashtag": {
"follow": { "follow": {
"action_false": "", "action_false": "Підписатися",
"action_true": "" "action_true": "Відписатися"
}, },
"filter": { "filter": {
"action": "" "action": "Фільтрувати хештеґ ..."
} }
}, },
"share": { "share": {
@@ -99,8 +99,8 @@
"action_true": "Відкріпити дмух" "action_true": "Відкріпити дмух"
}, },
"filter": { "filter": {
"action_false": "", "action_false": "Фільтрувати дмух ...",
"action_true": "" "action_true": "Керувати фільтрами ..."
} }
} }
} }

View File

@@ -211,7 +211,7 @@
}, },
"keywords": "Збіг із цими ключовими словами", "keywords": "Збіг із цими ключовими словами",
"keyword": "Ключове слово", "keyword": "Ключове слово",
"statuses": "" "statuses": "Збігається з цими дмухами"
}, },
"profile": { "profile": {
"feedback": { "feedback": {
@@ -400,8 +400,8 @@
"name": "<0 /><1> медіа</1>" "name": "<0 /><1> медіа</1>"
}, },
"filter": { "filter": {
"name": "", "name": "Додати фільтр",
"existed": "" "existed": "Існує в цих фільтрах"
}, },
"history": { "history": {
"name": "Редагувати історію" "name": "Редагувати історію"

View File

@@ -6,7 +6,7 @@
"action_false": "跟隨使用者", "action_false": "跟隨使用者",
"action_true": "取消跟隨使用者" "action_true": "取消跟隨使用者"
}, },
"inLists": "", "inLists": "包含使用者的列表 ...",
"showBoosts": { "showBoosts": {
"action_false": "顯示使用者的轉嘟", "action_false": "顯示使用者的轉嘟",
"action_true": "隱藏使用者的轉嘟" "action_true": "隱藏使用者的轉嘟"
@@ -16,12 +16,12 @@
"action_true": "解除靜音使用者" "action_true": "解除靜音使用者"
}, },
"followAs": { "followAs": {
"trigger": "", "trigger": "用其它帳號跟隨 ...",
"succeed_default": "@{{source}} 正在跟隨 @{{target}}", "succeed_default": "@{{source}} 正在跟隨 @{{target}}",
"succeed_locked": "已從 {{source}} 發送跟隨請求至 @{{target}},等待同意", "succeed_locked": "已從 {{source}} 發送跟隨請求至 @{{target}},等待同意",
"failed": "用其它帳號跟隨" "failed": "用其它帳號跟隨"
}, },
"blockReport": "", "blockReport": "封鎖並檢舉",
"block": { "block": {
"action_false": "封鎖使用者", "action_false": "封鎖使用者",
"action_true": "解除封鎖使用者", "action_true": "解除封鎖使用者",
@@ -56,11 +56,11 @@
}, },
"hashtag": { "hashtag": {
"follow": { "follow": {
"action_false": "", "action_false": "跟隨",
"action_true": "" "action_true": "取消跟隨"
}, },
"filter": { "filter": {
"action": "" "action": "過濾主題標籤 ..."
} }
}, },
"share": { "share": {
@@ -99,8 +99,8 @@
"action_true": "取消釘選嘟文" "action_true": "取消釘選嘟文"
}, },
"filter": { "filter": {
"action_false": "", "action_false": "過濾嘟文 ...",
"action_true": "" "action_true": "管理過濾器 ..."
} }
} }
} }

View File

@@ -211,7 +211,7 @@
}, },
"keywords": "符合這些關鍵字", "keywords": "符合這些關鍵字",
"keyword": "關鍵字", "keyword": "關鍵字",
"statuses": "" "statuses": "符合這些嘟文"
}, },
"profile": { "profile": {
"feedback": { "feedback": {
@@ -400,8 +400,8 @@
"name": "<0 /><1>的媒體</1>" "name": "<0 /><1>的媒體</1>"
}, },
"filter": { "filter": {
"name": "", "name": "新增至過濾器",
"existed": "" "existed": "已被過濾"
}, },
"history": { "history": {
"name": "編輯歷史" "name": "編輯歷史"

View File

@@ -1,6 +1,6 @@
import { HeaderLeft } from '@components/Header' import { HeaderLeft } from '@components/Header'
import Icon from '@components/Icon' import Icon from '@components/Icon'
import ComponentSeparator from '@components/Separator' import { SwipeToActions } from '@components/SwipeToActions'
import CustomText from '@components/Text' import CustomText from '@components/Text'
import HeaderSharedCreated from '@components/Timeline/Shared/HeaderShared/Created' import HeaderSharedCreated from '@components/Timeline/Shared/HeaderShared/Created'
import apiInstance from '@utils/api/instance' import apiInstance from '@utils/api/instance'
@@ -10,10 +10,8 @@ import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { useContext, useEffect, useState } from 'react' import React, { useContext, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Dimensions, Modal, Platform, Pressable, View } from 'react-native' import { Dimensions, Modal, Pressable, View } from 'react-native'
import FastImage from 'react-native-fast-image' import FastImage from 'react-native-fast-image'
import { PanGestureHandler } from 'react-native-gesture-handler'
import { SwipeListView } from 'react-native-swipe-list-view'
import ComposeContext from './utils/createContext' import ComposeContext from './utils/createContext'
import { formatText } from './utils/processText' import { formatText } from './utils/processText'
import { ComposeStateDraft, ExtendedAttachment } from './utils/types' import { ComposeStateDraft, ExtendedAttachment } from './utils/types'
@@ -39,9 +37,7 @@ const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-
useEffect(() => { useEffect(() => {
navigation.setOptions({ navigation.setOptions({
title: t('content.draftsList.header.title'), title: t('content.draftsList.header.title'),
headerLeft: () => ( headerLeft: () => <HeaderLeft content='chevron-down' onPress={() => navigation.goBack()} />
<HeaderLeft content='chevron-down' onPress={() => navigation.goBack()} />
)
}) })
}, []) }, [])
@@ -49,8 +45,6 @@ const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-
const [drafts] = useAccountStorage.object('drafts') const [drafts] = useAccountStorage.object('drafts')
const [checkingAttachments, setCheckingAttachments] = useState(false) const [checkingAttachments, setCheckingAttachments] = useState(false)
const actionWidth = StyleConstants.Font.Size.L + StyleConstants.Spacing.Global.PagePadding * 4
return ( return (
<> <>
<View <View
@@ -61,7 +55,8 @@ const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-
padding: StyleConstants.Spacing.S, padding: StyleConstants.Spacing.S,
borderColor: colors.border, borderColor: colors.border,
borderWidth: 1, borderWidth: 1,
borderRadius: StyleConstants.Spacing.S borderRadius: StyleConstants.Spacing.S,
marginBottom: StyleConstants.Spacing.S
}} }}
> >
<Icon <Icon
@@ -74,8 +69,10 @@ const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-
{t('content.draftsList.warning')} {t('content.draftsList.warning')}
</CustomText> </CustomText>
</View> </View>
<PanGestureHandler enabled={Platform.OS === 'ios'}> <SwipeToActions
<SwipeListView actions={[
{ onPress: ({ item }) => removeDraft(item.timestamp), color: colors.red, icon: 'trash' }
]}
data={drafts.filter(draft => draft.timestamp !== timestamp)} data={drafts.filter(draft => draft.timestamp !== timestamp)}
renderItem={({ item }: { item: ComposeStateDraft }) => { renderItem={({ item }: { item: ComposeStateDraft }) => {
return ( return (
@@ -168,41 +165,8 @@ const ComposeDraftsList: React.FC<ScreenComposeStackScreenProps<'Screen-Compose-
</Pressable> </Pressable>
) )
}} }}
renderHiddenItem={({ item }) => (
<Pressable
style={{
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-end',
backgroundColor: colors.red
}}
onPress={() => removeDraft(item.timestamp)}
children={
<View
style={{
flexBasis:
StyleConstants.Font.Size.L + StyleConstants.Spacing.Global.PagePadding * 4,
justifyContent: 'center',
alignItems: 'center'
}}
children={
<Icon
name='trash'
size={StyleConstants.Font.Size.L}
color={colors.primaryOverlay}
/>
}
/>
}
/>
)}
disableRightSwipe={true}
rightOpenValue={-actionWidth}
previewOpenValue={-actionWidth / 2}
ItemSeparatorComponent={ComponentSeparator}
keyExtractor={item => item.timestamp?.toString()} keyExtractor={item => item.timestamp?.toString()}
/> />
</PanGestureHandler>
<Modal <Modal
transparent transparent
animationType='fade' animationType='fade'

View File

@@ -416,12 +416,12 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
<Stack.Screen <Stack.Screen
name='Screen-Compose-DraftsList' name='Screen-Compose-DraftsList'
component={ComposeDraftsList} component={ComposeDraftsList}
options={{ presentation: 'modal' }} options={{ presentation: 'modal', headerShadowVisible: false }}
/> />
<Stack.Screen <Stack.Screen
name='Screen-Compose-EditAttachment' name='Screen-Compose-EditAttachment'
component={ComposeEditAttachment} component={ComposeEditAttachment}
options={{ presentation: 'modal' }} options={{ presentation: 'modal', headerShadowVisible: false }}
/> />
</Stack.Navigator> </Stack.Navigator>
</ComposeContext.Provider> </ComposeContext.Provider>

View File

@@ -1,22 +1,17 @@
import { Filter } from '@components/Filter' import { Filter } from '@components/Filter'
import { HeaderLeft, HeaderRight } from '@components/Header' import { HeaderLeft, HeaderRight } from '@components/Header'
import Icon from '@components/Icon' import { SwipeToActions } from '@components/SwipeToActions'
import ComponentSeparator from '@components/Separator'
import apiInstance from '@utils/api/instance' import apiInstance from '@utils/api/instance'
import { TabMePreferencesStackScreenProps } from '@utils/navigation/navigators' import { TabMePreferencesStackScreenProps } from '@utils/navigation/navigators'
import { useFiltersQuery } from '@utils/queryHooks/filters' import { useFiltersQuery } from '@utils/queryHooks/filters'
import { StyleConstants } from '@utils/styles/constants' import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager' import { useTheme } from '@utils/styles/ThemeManager'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { Pressable, View } from 'react-native'
import { SwipeListView } from 'react-native-swipe-list-view'
const TabMePreferencesFilters: React.FC< const TabMePreferencesFilters: React.FC<
TabMePreferencesStackScreenProps<'Tab-Me-Preferences-Filters'> TabMePreferencesStackScreenProps<'Tab-Me-Preferences-Filters'>
> = ({ navigation }) => { > = ({ navigation }) => {
const { colors } = useTheme() const { colors } = useTheme()
const { t } = useTranslation(['common', 'screenTabs'])
useEffect(() => { useEffect(() => {
navigation.setOptions({ navigation.setOptions({
@@ -38,40 +33,31 @@ const TabMePreferencesFilters: React.FC<
const { data, refetch } = useFiltersQuery<'v2'>({ version: 'v2' }) const { data, refetch } = useFiltersQuery<'v2'>({ version: 'v2' })
return ( return (
<SwipeListView <SwipeToActions
contentContainerStyle={{ padding: StyleConstants.Spacing.Global.PagePadding }} actions={[
renderHiddenItem={({ item }) => ( {
<Pressable onPress: ({ item }) => {
style={{
flex: 1,
justifyContent: 'center',
alignItems: 'flex-end',
backgroundColor: colors.red
}}
onPress={() => {
apiInstance({ method: 'delete', version: 'v2', url: `filters/${item.id}` }).then(() => apiInstance({ method: 'delete', version: 'v2', url: `filters/${item.id}` }).then(() =>
refetch() refetch()
) )
}} },
> color: colors.red,
<View style={{ paddingHorizontal: StyleConstants.Spacing.L }}> icon: 'trash'
<Icon name='trash' color='white' size={StyleConstants.Font.Size.L} /> }
</View> ]}
</Pressable>
)}
rightOpenValue={-(StyleConstants.Spacing.L * 2 + StyleConstants.Font.Size.L)}
disableRightSwipe
closeOnRowPress
data={data?.sort(filter => data={data?.sort(filter =>
filter.expires_at ? new Date().getTime() - new Date(filter.expires_at).getTime() : 1 filter.expires_at ? new Date().getTime() - new Date(filter.expires_at).getTime() : 1
)} )}
renderItem={({ item: filter }) => ( renderItem={({ item: filter }) => (
<Filter <Filter
style={{
padding: StyleConstants.Spacing.Global.PagePadding,
paddingVertical: StyleConstants.Spacing.M
}}
filter={filter} filter={filter}
onPress={() => navigation.navigate('Tab-Me-Preferences-Filter', { type: 'edit', filter })} onPress={() => navigation.navigate('Tab-Me-Preferences-Filter', { type: 'edit', filter })}
/> />
)} )}
ItemSeparatorComponent={ComponentSeparator}
/> />
) )
} }

View File

@@ -1,8 +1,9 @@
import { HeaderLeft } from '@components/Header' import { HeaderLeft } from '@components/Header'
import { Message } from '@components/Message' import { Message } from '@components/Message'
import { useNavigationState } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack' import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { TabMePreferencesStackParamList, TabMeStackScreenProps } from '@utils/navigation/navigators' import { TabMePreferencesStackParamList, TabMeStackScreenProps } from '@utils/navigation/navigators'
import React, { useRef } from 'react' import React, { useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import FlashMessage from 'react-native-flash-message' import FlashMessage from 'react-native-flash-message'
import TabMePreferencesFilter from './Filter' import TabMePreferencesFilter from './Filter'
@@ -17,6 +18,15 @@ const TabMePreferences: React.FC<TabMeStackScreenProps<'Tab-Me-Preferences'>> =
const { t } = useTranslation('screenTabs') const { t } = useTranslation('screenTabs')
const messageRef = useRef<FlashMessage>(null) const messageRef = useRef<FlashMessage>(null)
const isNested =
(useNavigationState(
state => state.routes.find(route => route.name === 'Tab-Me-Preferences')?.state?.routes.length
) || 0) > 1
useEffect(() => {
navigation.setOptions({ gestureEnabled: !isNested })
}, [isNested])
return ( return (
<> <>
<Stack.Navigator screenOptions={{ headerShadowVisible: false }}> <Stack.Navigator screenOptions={{ headerShadowVisible: false }}>

View File

@@ -314,6 +314,55 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
const curr = item._level || 0 const curr = item._level || 0
const next = query.data?.pages[0].body[index + 1]?._level || 0 const next = query.data?.pages[0].body[index + 1]?._level || 0
const height = heights[index] || 300
let path = ''
if (curr > 1 || next > 1) {
Array.from({ length: curr }).forEach((_, i) => {
if (i > MAX_LEVEL) return null
const lastLine = curr === i + 1
if (lastLine) {
if (curr === prev + 1 || curr === next - 1) {
if (curr > next) return
path =
path +
` M ${curr * StyleConstants.Spacing.S + ARC} ${
StyleConstants.Spacing.M + StyleConstants.Avatar.XS / 2
} ` +
`a ${ARC} ${ARC} 0 0 0 -${ARC} ${ARC} ` +
`v ${height}`
} else {
if (i >= curr - 2) return
path =
path +
` M ${(i + 1) * StyleConstants.Spacing.S} 0 ` +
`v ${
height -
(StyleConstants.Spacing.S * 1.5 + StyleConstants.Font.Size.L) / 2 -
StyleConstants.Avatar.XS / 2
} ` +
`a ${ARC} ${ARC} 0 0 0 ${ARC} ${ARC}`
}
} else {
if (i >= next - 1) {
path =
path +
` M ${(i + 1) * StyleConstants.Spacing.S} 0 ` +
`v ${
height -
(StyleConstants.Spacing.S * 1.5 + StyleConstants.Font.Size.L * 1.35) / 2
} ` +
`h ${ARC}`
} else {
path = path + ` M ${(i + 1) * StyleConstants.Spacing.S} 0 ` + `v ${height}`
}
}
})
}
return ( return (
<View <View
style={{ paddingLeft: Math.min(curr - 1, MAX_LEVEL) * StyleConstants.Spacing.S }} style={{ paddingLeft: Math.min(curr - 1, MAX_LEVEL) * StyleConstants.Spacing.S }}
@@ -323,92 +372,11 @@ const TabSharedToot: React.FC<TabSharedStackScreenProps<'Tab-Shared-Toot'>> = ({
} }
}) => setHeights({ ...heights, [index]: height })} }) => setHeights({ ...heights, [index]: height })}
> >
{curr > 1 || next > 1 {path.length ? (
? [...new Array(curr)].map((_, i) => { <Svg style={{ position: 'absolute' }} fill='none'>
if (i > MAX_LEVEL) return null <Path d={path} strokeWidth={1} stroke={colors.border} strokeOpacity={0.6} />
const lastLine = curr === i + 1
if (lastLine) {
if (curr === prev + 1 || curr === next - 1) {
if (curr > next) {
return null
}
return (
<Svg key={i} style={{ position: 'absolute' }} fill='none'>
<Path
d={
`M ${curr * StyleConstants.Spacing.S + ARC} ${
StyleConstants.Spacing.M + StyleConstants.Avatar.XS / 2
} ` +
`a ${ARC} ${ARC} 0 0 0 -${ARC} ${ARC} ` +
`v ${heights[index] || 300}`
}
strokeWidth={1}
stroke={colors.border}
strokeOpacity={0.6}
/>
</Svg> </Svg>
) ) : null}
} else {
if (i >= curr - 2) return null
return (
<Svg key={i} style={{ position: 'absolute' }} fill='none'>
<Path
d={
`M ${(i + 1) * StyleConstants.Spacing.S} 0 ` +
`v ${
(heights[index] || 300) -
(StyleConstants.Spacing.S * 1.5 + StyleConstants.Font.Size.L) / 2 -
StyleConstants.Avatar.XS / 2
} ` +
`a ${ARC} ${ARC} 0 0 0 ${ARC} ${ARC}`
}
strokeWidth={1}
stroke={colors.border}
strokeOpacity={0.6}
/>
</Svg>
)
}
} else {
if (i >= next - 1) {
return (
<Svg key={i} style={{ position: 'absolute' }} fill='none'>
<Path
d={
`M ${(i + 1) * StyleConstants.Spacing.S} 0 ` +
`v ${
(heights[index] || 300) -
(StyleConstants.Spacing.S * 1.5 +
StyleConstants.Font.Size.L * 1.35) /
2
} ` +
`h ${ARC}`
}
strokeWidth={1}
stroke={colors.border}
strokeOpacity={0.6}
/>
</Svg>
)
} else {
return (
<Svg key={i} style={{ position: 'absolute' }} fill='none'>
<Path
d={
`M ${(i + 1) * StyleConstants.Spacing.S} 0 ` +
`v ${heights[index] || 300}`
}
strokeWidth={1}
stroke={colors.border}
strokeOpacity={0.6}
/>
</Svg>
)
}
}
})
: null}
<TimelineDefault <TimelineDefault
item={item} item={item}
queryKey={item._remote ? queryKey.remote : queryKey.local} queryKey={item._remote ? queryKey.remote : queryKey.local}