mirror of
https://github.com/tooot-app/app
synced 2025-01-31 10:44:58 +01:00
First step of adding filter editing support
This commit is contained in:
parent
2d91d1f7fb
commit
d73857eef4
23
src/components/Hr.tsx
Normal file
23
src/components/Hr.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { View, ViewStyle } from 'react-native'
|
||||
|
||||
const Hr: React.FC<{ style?: ViewStyle }> = ({ style }) => {
|
||||
const { colors } = useTheme()
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
borderTopColor: colors.border,
|
||||
borderTopWidth: 1,
|
||||
height: 1,
|
||||
marginVertical: StyleConstants.Spacing.S
|
||||
},
|
||||
style
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Hr
|
@ -9,7 +9,9 @@ import CustomText from './Text'
|
||||
export type Props = {
|
||||
title?: string
|
||||
multiline?: boolean
|
||||
} & Pick<NonNullable<EmojisState['inputProps'][0]>, 'value' | 'selection' | 'isFocused'> &
|
||||
invalid?: boolean
|
||||
} & Pick<NonNullable<EmojisState['inputProps'][0]>, 'value'> &
|
||||
Pick<Partial<EmojisState['inputProps'][0]>, 'isFocused' | 'selection'> &
|
||||
Omit<
|
||||
TextInputProps,
|
||||
| 'style'
|
||||
@ -27,8 +29,9 @@ const ComponentInput = forwardRef(
|
||||
{
|
||||
title,
|
||||
multiline = false,
|
||||
invalid = false,
|
||||
value: [value, setValue],
|
||||
selection: [selection, setSelection],
|
||||
selection,
|
||||
isFocused,
|
||||
...props
|
||||
}: Props,
|
||||
@ -43,7 +46,7 @@ const ComponentInput = forwardRef(
|
||||
paddingHorizontal: withTiming(StyleConstants.Spacing.XS),
|
||||
left: withTiming(StyleConstants.Spacing.S),
|
||||
top: withTiming(-(StyleConstants.Font.Size.S / 2) - 2),
|
||||
backgroundColor: withTiming(colors.backgroundDefault)
|
||||
backgroundColor: colors.backgroundDefault
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
@ -62,7 +65,7 @@ const ComponentInput = forwardRef(
|
||||
borderWidth: 1,
|
||||
marginVertical: StyleConstants.Spacing.S,
|
||||
padding: StyleConstants.Spacing.S,
|
||||
borderColor: colors.border,
|
||||
borderColor: invalid ? colors.red : colors.border,
|
||||
flexDirection: multiline ? 'column' : 'row',
|
||||
alignItems: 'stretch'
|
||||
}}
|
||||
@ -78,9 +81,13 @@ const ComponentInput = forwardRef(
|
||||
}}
|
||||
value={value}
|
||||
onChangeText={setValue}
|
||||
onFocus={() => (isFocused.current = true)}
|
||||
onBlur={() => (isFocused.current = false)}
|
||||
onSelectionChange={({ nativeEvent }) => setSelection(nativeEvent.selection)}
|
||||
{...(isFocused !== undefined && {
|
||||
onFocus: () => (isFocused.current = true),
|
||||
onBlur: () => (isFocused.current = false)
|
||||
})}
|
||||
{...(selection !== undefined && {
|
||||
onSelectionChange: ({ nativeEvent }) => selection[1](nativeEvent.selection)
|
||||
})}
|
||||
{...(multiline && {
|
||||
multiline,
|
||||
numberOfLines: Platform.OS === 'android' ? 5 : undefined
|
||||
|
@ -44,7 +44,7 @@ const MenuRow: React.FC<Props> = ({
|
||||
loading = false,
|
||||
onPress
|
||||
}) => {
|
||||
const { colors, theme } = useTheme()
|
||||
const { colors } = useTheme()
|
||||
const { screenReaderEnabled } = useAccessibility()
|
||||
|
||||
return (
|
||||
|
@ -8,17 +8,22 @@ import { ParseEmojis } from './Parse'
|
||||
import CustomText from './Text'
|
||||
|
||||
export interface Props {
|
||||
title?: string
|
||||
|
||||
multiple?: boolean
|
||||
options: { selected: boolean; content: string }[]
|
||||
setOptions: React.Dispatch<React.SetStateAction<{ selected: boolean; content: string }[]>>
|
||||
disabled?: boolean
|
||||
invalid?: boolean
|
||||
}
|
||||
|
||||
const Selections: React.FC<Props> = ({
|
||||
title,
|
||||
multiple = false,
|
||||
options,
|
||||
setOptions,
|
||||
disabled = false
|
||||
disabled = false,
|
||||
invalid = false
|
||||
}) => {
|
||||
const { colors } = useTheme()
|
||||
|
||||
@ -32,52 +37,71 @@ const Selections: React.FC<Props> = ({
|
||||
: 'circle'
|
||||
|
||||
return (
|
||||
<View>
|
||||
{options.map((option, index) => (
|
||||
<Pressable
|
||||
key={index}
|
||||
disabled={disabled}
|
||||
style={{ flex: 1, paddingVertical: StyleConstants.Spacing.S }}
|
||||
onPress={() => {
|
||||
if (multiple) {
|
||||
haptics('Light')
|
||||
|
||||
setOptions(options.map((o, i) => (i === index ? { ...o, selected: !o.selected } : o)))
|
||||
} else {
|
||||
if (!option.selected) {
|
||||
<View style={{ marginVertical: StyleConstants.Spacing.S }}>
|
||||
{title ? (
|
||||
<CustomText
|
||||
fontStyle='M'
|
||||
children={title}
|
||||
style={{ color: disabled ? colors.disabled : colors.primaryDefault }}
|
||||
/>
|
||||
) : null}
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: StyleConstants.Spacing.M,
|
||||
paddingVertical: StyleConstants.Spacing.XS,
|
||||
marginTop: StyleConstants.Spacing.S,
|
||||
borderWidth: 1,
|
||||
borderColor: disabled ? colors.disabled : invalid ? colors.red : colors.border
|
||||
}}
|
||||
>
|
||||
{options.map((option, index) => (
|
||||
<Pressable
|
||||
key={index}
|
||||
disabled={disabled}
|
||||
style={{ flex: 1, paddingVertical: StyleConstants.Spacing.S }}
|
||||
onPress={() => {
|
||||
if (multiple) {
|
||||
haptics('Light')
|
||||
|
||||
setOptions(
|
||||
options.map((o, i) => {
|
||||
if (i === index) {
|
||||
return { ...o, selected: true }
|
||||
} else {
|
||||
return { ...o, selected: false }
|
||||
}
|
||||
})
|
||||
options.map((o, i) => (i === index ? { ...o, selected: !o.selected } : o))
|
||||
)
|
||||
} else {
|
||||
if (!option.selected) {
|
||||
haptics('Light')
|
||||
setOptions(
|
||||
options.map((o, i) => {
|
||||
if (i === index) {
|
||||
return { ...o, selected: true }
|
||||
} else {
|
||||
return { ...o, selected: false }
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||
<Icon
|
||||
style={{
|
||||
paddingTop: StyleConstants.Font.LineHeight.M - StyleConstants.Font.Size.M,
|
||||
marginRight: StyleConstants.Spacing.S
|
||||
}}
|
||||
name={isSelected(index)}
|
||||
size={StyleConstants.Font.Size.M}
|
||||
color={disabled ? colors.disabled : colors.primaryDefault}
|
||||
/>
|
||||
<CustomText style={{ flex: 1 }}>
|
||||
<ParseEmojis
|
||||
content={option.content}
|
||||
style={{ color: disabled ? colors.disabled : colors.primaryDefault }}
|
||||
}}
|
||||
>
|
||||
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||
<Icon
|
||||
style={{
|
||||
marginTop: (StyleConstants.Font.LineHeight.M - StyleConstants.Font.Size.M) / 2,
|
||||
marginRight: StyleConstants.Spacing.S
|
||||
}}
|
||||
name={isSelected(index)}
|
||||
size={StyleConstants.Font.Size.M}
|
||||
color={disabled ? colors.disabled : colors.primaryDefault}
|
||||
/>
|
||||
</CustomText>
|
||||
</View>
|
||||
</Pressable>
|
||||
))}
|
||||
<CustomText fontStyle='S' style={{ flex: 1 }}>
|
||||
<ParseEmojis
|
||||
content={option.content}
|
||||
style={{ color: disabled ? colors.disabled : colors.primaryDefault }}
|
||||
/>
|
||||
</CustomText>
|
||||
</View>
|
||||
</Pressable>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ export const shouldFilter = ({
|
||||
break
|
||||
}
|
||||
}
|
||||
const queryKeyFilters: QueryKeyFilters = ['Filters']
|
||||
const queryKeyFilters: QueryKeyFilters = ['Filters', { version: 'v1' }]
|
||||
queryClient.getQueryData<Mastodon.Filter<'v1'>[]>(queryKeyFilters)?.forEach(filter => {
|
||||
if (returnFilter) {
|
||||
return
|
||||
|
@ -133,7 +133,7 @@ const TimelinePoll: React.FC = () => {
|
||||
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||
<Icon
|
||||
style={{
|
||||
paddingTop: StyleConstants.Font.LineHeight.M - StyleConstants.Font.Size.M,
|
||||
marginTop: (StyleConstants.Font.LineHeight.M - StyleConstants.Font.Size.M) / 2,
|
||||
marginRight: StyleConstants.Spacing.S
|
||||
}}
|
||||
name={
|
||||
@ -205,7 +205,7 @@ const TimelinePoll: React.FC = () => {
|
||||
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||
<Icon
|
||||
style={{
|
||||
paddingTop: StyleConstants.Font.LineHeight.M - StyleConstants.Font.Size.M,
|
||||
marginTop: (StyleConstants.Font.LineHeight.M - StyleConstants.Font.Size.M) / 2,
|
||||
marginRight: StyleConstants.Spacing.S
|
||||
}}
|
||||
name={isSelected(index)}
|
||||
|
@ -72,6 +72,15 @@
|
||||
"preferences": {
|
||||
"name": "Preferences"
|
||||
},
|
||||
"preferencesFilters": {
|
||||
"name": "All content filters"
|
||||
},
|
||||
"preferencesFilterAdd": {
|
||||
"name": "Create a Filter"
|
||||
},
|
||||
"preferencesFilterEdit": {
|
||||
"name": "Edit Filter"
|
||||
},
|
||||
"profile": {
|
||||
"name": "Edit Profile"
|
||||
},
|
||||
@ -127,7 +136,7 @@
|
||||
},
|
||||
"preferences": {
|
||||
"visibility": {
|
||||
"title": "Posting visibility",
|
||||
"title": "Default posting visibility",
|
||||
"options": {
|
||||
"public": "Public",
|
||||
"unlisted": "Unlisted",
|
||||
@ -135,7 +144,7 @@
|
||||
}
|
||||
},
|
||||
"sensitive": {
|
||||
"title": "Posting media sensitive"
|
||||
"title": "Default mark media as sensitive"
|
||||
},
|
||||
"media": {
|
||||
"title": "Media display",
|
||||
@ -146,16 +155,63 @@
|
||||
}
|
||||
},
|
||||
"spoilers": {
|
||||
"title": "Auto expand toots with content warning",
|
||||
"title": "Auto expand toots with content warning"
|
||||
},
|
||||
"autoplay_gifs": {
|
||||
"title": "Autoplay GIF in toots"
|
||||
},
|
||||
"filters": {
|
||||
"title": "Content filters",
|
||||
"content": "{{count}} active"
|
||||
},
|
||||
"web_only": {
|
||||
"title": "Update settings",
|
||||
"description": "Settings below can only be updated using the web UI"
|
||||
}
|
||||
},
|
||||
"preferencesFilters": {
|
||||
"expired": "Expired",
|
||||
"keywords_one": "{{count}} keyword",
|
||||
"keywords_other": "{{count}} keywords",
|
||||
"statuses_one": "{{count}} toot",
|
||||
"statuses_other": "{{count}} toots",
|
||||
"context": "Applies in <0 />",
|
||||
"contexts": {
|
||||
"home": "following and lists",
|
||||
"notifications": "notification",
|
||||
"public": "federated",
|
||||
"thread": "conversation",
|
||||
"account": "profile"
|
||||
}
|
||||
},
|
||||
"preferencesFilter": {
|
||||
"name": "Name",
|
||||
"expiration": "Expiration",
|
||||
"expirationOptions": {
|
||||
"0": "Never",
|
||||
"1800": "After 30 minutes",
|
||||
"3600": "After 1 hour",
|
||||
"43200": "After 12 hours",
|
||||
"86400": "After 1 day",
|
||||
"604800": "After 1 week",
|
||||
"18144000": "After 1 month"
|
||||
},
|
||||
"context": "Applies in",
|
||||
"contexts": {
|
||||
"home": "Following and lists",
|
||||
"notifications": "Notification",
|
||||
"public": "Federated timeline",
|
||||
"thread": "Conversation view",
|
||||
"account": "Profile view"
|
||||
},
|
||||
"action": "When matched",
|
||||
"actions": {
|
||||
"warn": "Collapsed but can be revealed",
|
||||
"hide": "Hidden completely"
|
||||
},
|
||||
"keywords": "Matches for these keywords",
|
||||
"keyword": "Keyword"
|
||||
},
|
||||
"profile": {
|
||||
"feedback": {
|
||||
"succeed": "{{type}} updated",
|
||||
|
@ -19,7 +19,7 @@ const TabMeListEdit: React.FC<TabMeStackScreenProps<'Tab-Me-List-Edit'>> = ({
|
||||
navigation,
|
||||
route: { params }
|
||||
}) => {
|
||||
const { colors, theme } = useTheme()
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation(['common', 'screenTabs'])
|
||||
|
||||
const messageRef = useRef(null)
|
||||
@ -147,18 +147,11 @@ const TabMeListEdit: React.FC<TabMeStackScreenProps<'Tab-Me-List-Edit'>> = ({
|
||||
<ScrollView style={{ paddingHorizontal: StyleConstants.Spacing.Global.PagePadding }}>
|
||||
<ComponentInput {...inputProps} autoFocus title={t('screenTabs:me.listEdit.title')} />
|
||||
|
||||
<CustomText
|
||||
fontStyle='M'
|
||||
fontWeight='Bold'
|
||||
style={{
|
||||
color: colors.primaryDefault,
|
||||
marginBottom: StyleConstants.Spacing.XS,
|
||||
marginTop: StyleConstants.Spacing.M
|
||||
}}
|
||||
>
|
||||
{t('screenTabs:me.listEdit.repliesPolicy.heading')}
|
||||
</CustomText>
|
||||
<Selections options={options} setOptions={setOptions} />
|
||||
<Selections
|
||||
title={t('screenTabs:me.listEdit.repliesPolicy.heading')}
|
||||
options={options}
|
||||
setOptions={setOptions}
|
||||
/>
|
||||
|
||||
<Message ref={messageRef} />
|
||||
</ScrollView>
|
||||
|
265
src/screens/Tabs/Me/Preferences/Filter.tsx
Normal file
265
src/screens/Tabs/Me/Preferences/Filter.tsx
Normal file
@ -0,0 +1,265 @@
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import Hr from '@components/Hr'
|
||||
import ComponentInput from '@components/Input'
|
||||
import { MenuRow } from '@components/Menu'
|
||||
import Selections from '@components/Selections'
|
||||
import CustomText from '@components/Text'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import { TabMePreferencesStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { queryClient } from '@utils/queryHooks'
|
||||
import { QueryKeyFilters } from '@utils/queryHooks/filters'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { RefObject, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { KeyboardAvoidingView, Platform, View } from 'react-native'
|
||||
import FlashMessage from 'react-native-flash-message'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
|
||||
const TabMePreferencesFilter: React.FC<
|
||||
TabMePreferencesStackScreenProps<'Tab-Me-Preferences-Filter'> & {
|
||||
messageRef: RefObject<FlashMessage>
|
||||
}
|
||||
> = ({ navigation, route: { params } }) => {
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation(['common', 'screenTabs'])
|
||||
|
||||
const { showActionSheetWithOptions } = useActionSheet()
|
||||
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
title:
|
||||
params.type === 'add'
|
||||
? t('screenTabs:me.stacks.preferencesFilterAdd.name')
|
||||
: t('screenTabs:me.stacks.preferencesFilterEdit.name'),
|
||||
headerLeft: () => (
|
||||
<HeaderLeft
|
||||
content='chevron-left'
|
||||
onPress={() => navigation.navigate('Tab-Me-Preferences-Filters')}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}, [])
|
||||
|
||||
const titleState = useState(params.type === 'edit' ? params.filter.title : '')
|
||||
|
||||
const expirations = ['0', '1800', '3600', '43200', '86400', '604800', '18144000'] as const
|
||||
const [expiration, setExpiration] = useState<typeof expirations[number]>('0')
|
||||
|
||||
const [contexts, setContexts] = useState<
|
||||
{
|
||||
selected: boolean
|
||||
content: string
|
||||
type: 'home' | 'notifications' | 'public' | 'thread' | 'account'
|
||||
}[]
|
||||
>([
|
||||
{
|
||||
selected: params.type === 'edit' ? params.filter.context.includes('home') : true,
|
||||
content: t('screenTabs:me.preferencesFilter.contexts.home'),
|
||||
type: 'home'
|
||||
},
|
||||
{
|
||||
selected: params.type === 'edit' ? params.filter.context.includes('notifications') : false,
|
||||
content: t('screenTabs:me.preferencesFilter.contexts.notifications'),
|
||||
type: 'notifications'
|
||||
},
|
||||
{
|
||||
selected: params.type === 'edit' ? params.filter.context.includes('public') : false,
|
||||
content: t('screenTabs:me.preferencesFilter.contexts.public'),
|
||||
type: 'public'
|
||||
},
|
||||
{
|
||||
selected: params.type === 'edit' ? params.filter.context.includes('thread') : false,
|
||||
content: t('screenTabs:me.preferencesFilter.contexts.thread'),
|
||||
type: 'thread'
|
||||
},
|
||||
{
|
||||
selected: params.type === 'edit' ? params.filter.context.includes('account') : false,
|
||||
content: t('screenTabs:me.preferencesFilter.contexts.account'),
|
||||
type: 'account'
|
||||
}
|
||||
])
|
||||
|
||||
const [actions, setActions] = useState<
|
||||
{ selected: boolean; content: string; type: 'warn' | 'hide' }[]
|
||||
>([
|
||||
{
|
||||
selected: params.type === 'edit' ? params.filter.filter_action === 'warn' : true,
|
||||
content: t('screenTabs:me.preferencesFilter.actions.warn'),
|
||||
type: 'warn'
|
||||
},
|
||||
{
|
||||
selected: params.type === 'edit' ? params.filter.filter_action === 'hide' : false,
|
||||
content: t('screenTabs:me.preferencesFilter.actions.hide'),
|
||||
type: 'hide'
|
||||
}
|
||||
])
|
||||
|
||||
const [keywords, setKeywords] = useState<string[]>(
|
||||
params.type === 'edit' ? params.filter.keywords.map(({ keyword }) => keyword) : []
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
let isLoading = false
|
||||
navigation.setOptions({
|
||||
headerRight: () => (
|
||||
<HeaderRight
|
||||
content='save'
|
||||
loading={isLoading}
|
||||
onPress={async () => {
|
||||
if (!titleState[0].length || !contexts.filter(context => context.selected).length)
|
||||
return
|
||||
|
||||
switch (params.type) {
|
||||
case 'add':
|
||||
isLoading = true
|
||||
await apiInstance({
|
||||
method: 'post',
|
||||
version: 'v2',
|
||||
url: 'filters',
|
||||
body: {
|
||||
title: titleState[0],
|
||||
context: contexts
|
||||
.filter(context => context.selected)
|
||||
.map(context => context.type),
|
||||
filter_action: actions.filter(
|
||||
action => action.type === 'hide' && action.selected
|
||||
).length
|
||||
? 'hide'
|
||||
: 'warn',
|
||||
...(parseInt(expiration) && { expires_in: parseInt(expiration) }),
|
||||
...(keywords.filter(keyword => keyword.length).length && {
|
||||
keywords_attributes: keywords
|
||||
.filter(keyword => keyword.length)
|
||||
.map(keyword => ({ keyword, whole_word: true }))
|
||||
})
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
isLoading = false
|
||||
const queryKey: QueryKeyFilters = ['Filters', { version: 'v2' }]
|
||||
queryClient.refetchQueries(queryKey)
|
||||
navigation.navigate('Tab-Me-Preferences-Filters')
|
||||
})
|
||||
.catch(() => {
|
||||
isLoading = false
|
||||
haptics('Error')
|
||||
})
|
||||
break
|
||||
case 'edit':
|
||||
break
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}, [titleState[0], expiration, contexts, actions, keywords])
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
style={{ flex: 1 }}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
>
|
||||
<SafeAreaView style={{ flex: 1 }} edges={['bottom']}>
|
||||
<ScrollView style={{ padding: StyleConstants.Spacing.Global.PagePadding }}>
|
||||
<ComponentInput title={t('screenTabs:me.preferencesFilter.name')} value={titleState} />
|
||||
<MenuRow
|
||||
title={t('screenTabs:me.preferencesFilter.expiration')}
|
||||
content={t(`screenTabs:me.preferencesFilter.expirationOptions.${expiration}`)}
|
||||
iconBack='chevron-right'
|
||||
onPress={() =>
|
||||
showActionSheetWithOptions(
|
||||
{
|
||||
title: t('screenTabs:me.preferencesFilter.expiration'),
|
||||
options: [
|
||||
...expirations.map(opt =>
|
||||
t(`screenTabs:me.preferencesFilter.expirationOptions.${opt}`)
|
||||
),
|
||||
t('common:buttons.cancel')
|
||||
],
|
||||
cancelButtonIndex: expirations.length,
|
||||
...androidActionSheetStyles(colors)
|
||||
},
|
||||
(selectedIndex: number) => {
|
||||
selectedIndex < expirations.length && setExpiration(expirations[selectedIndex])
|
||||
}
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Hr />
|
||||
|
||||
<Selections
|
||||
title={t('screenTabs:me.preferencesFilter.context')}
|
||||
multiple
|
||||
invalid={!contexts.filter(context => context.selected).length}
|
||||
options={contexts}
|
||||
setOptions={setContexts}
|
||||
/>
|
||||
<Selections
|
||||
title={t('screenTabs:me.preferencesFilter.action')}
|
||||
options={actions}
|
||||
setOptions={setActions}
|
||||
/>
|
||||
<Hr style={{ marginVertical: StyleConstants.Spacing.M }} />
|
||||
|
||||
<CustomText
|
||||
fontStyle='M'
|
||||
children={t('screenTabs:me.preferencesFilter.keywords')}
|
||||
style={{ color: colors.primaryDefault }}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
marginTop: StyleConstants.Spacing.M,
|
||||
marginBottom: StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
{[...Array(keywords.length)].map((_, i) => (
|
||||
<ComponentInput
|
||||
key={i}
|
||||
title={t('screenTabs:me.preferencesFilter.keyword')}
|
||||
value={[
|
||||
keywords[i],
|
||||
k => setKeywords(keywords.map((curr, ii) => (i === ii ? k : curr)))
|
||||
]}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
marginRight: StyleConstants.Spacing.M
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onPress={() => setKeywords(keywords.slice(0, keywords.length - 1))}
|
||||
type='icon'
|
||||
content='minus'
|
||||
round
|
||||
disabled={keywords.length < 1}
|
||||
/>
|
||||
<CustomText
|
||||
style={{ marginHorizontal: StyleConstants.Spacing.M, color: colors.secondary }}
|
||||
children={keywords.length}
|
||||
/>
|
||||
<Button
|
||||
onPress={() => setKeywords([...keywords, ''])}
|
||||
type='icon'
|
||||
content='plus'
|
||||
round
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</KeyboardAvoidingView>
|
||||
)
|
||||
}
|
||||
|
||||
export default TabMePreferencesFilter
|
166
src/screens/Tabs/Me/Preferences/Filters.tsx
Normal file
166
src/screens/Tabs/Me/Preferences/Filters.tsx
Normal file
@ -0,0 +1,166 @@
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import Icon from '@components/Icon'
|
||||
import ComponentSeparator from '@components/Separator'
|
||||
import CustomText from '@components/Text'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { TabMePreferencesStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { useFiltersQuery } from '@utils/queryHooks/filters'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { Fragment, useEffect } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { Pressable, TouchableNativeFeedback, View } from 'react-native'
|
||||
import { SwipeListView } from 'react-native-swipe-list-view'
|
||||
|
||||
const TabMePreferencesFilters: React.FC<
|
||||
TabMePreferencesStackScreenProps<'Tab-Me-Preferences-Filters'>
|
||||
> = ({ navigation }) => {
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation(['common', 'screenTabs'])
|
||||
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerLeft: () => (
|
||||
<HeaderLeft
|
||||
content='chevron-left'
|
||||
onPress={() => navigation.navigate('Tab-Me-Preferences-Root')}
|
||||
/>
|
||||
),
|
||||
headerRight: () => (
|
||||
<HeaderRight
|
||||
content='plus'
|
||||
onPress={() => navigation.navigate('Tab-Me-Preferences-Filter', { type: 'add' })}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}, [])
|
||||
|
||||
const { data, refetch } = useFiltersQuery<'v2'>({ version: 'v2' })
|
||||
|
||||
return (
|
||||
<SwipeListView
|
||||
renderHiddenItem={({ item }) => (
|
||||
<Pressable
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-end',
|
||||
backgroundColor: colors.red
|
||||
}}
|
||||
onPress={() => {
|
||||
apiInstance({ method: 'delete', version: 'v2', url: `filters/${item.id}` }).then(() =>
|
||||
refetch()
|
||||
)
|
||||
}}
|
||||
>
|
||||
<View style={{ paddingHorizontal: StyleConstants.Spacing.L }}>
|
||||
<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 =>
|
||||
filter.expires_at ? new Date().getTime() - new Date(filter.expires_at).getTime() : 1
|
||||
)}
|
||||
renderItem={({ item: filter }) => (
|
||||
<TouchableNativeFeedback
|
||||
onPress={() => navigation.navigate('Tab-Me-Preferences-Filter', { type: 'edit', filter })}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
padding: StyleConstants.Spacing.Global.PagePadding,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.backgroundDefault
|
||||
}}
|
||||
>
|
||||
<View style={{ flex: 1 }}>
|
||||
<CustomText
|
||||
fontStyle='M'
|
||||
children={filter.title}
|
||||
style={{ color: colors.primaryDefault }}
|
||||
numberOfLines={1}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginVertical: StyleConstants.Spacing.XS
|
||||
}}
|
||||
>
|
||||
{filter.expires_at && new Date() > new Date(filter.expires_at) ? (
|
||||
<CustomText
|
||||
fontStyle='S'
|
||||
fontWeight='Bold'
|
||||
children={t('screenTabs:me.preferencesFilters.expired')}
|
||||
style={{ color: colors.red, marginRight: StyleConstants.Spacing.M }}
|
||||
/>
|
||||
) : null}
|
||||
{filter.keywords?.length ? (
|
||||
<CustomText
|
||||
children={t('screenTabs:me.preferencesFilters.keywords', {
|
||||
count: filter.keywords.length
|
||||
})}
|
||||
style={{ color: colors.primaryDefault }}
|
||||
/>
|
||||
) : null}
|
||||
{filter.keywords?.length && filter.statuses?.length ? (
|
||||
<CustomText
|
||||
children={t('common:separator')}
|
||||
style={{ color: colors.primaryDefault }}
|
||||
/>
|
||||
) : null}
|
||||
{filter.statuses?.length ? (
|
||||
<CustomText
|
||||
children={t('screenTabs:me.preferencesFilters.statuses', {
|
||||
count: filter.statuses.length
|
||||
})}
|
||||
style={{ color: colors.primaryDefault }}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
<CustomText
|
||||
style={{ color: colors.secondary }}
|
||||
children={
|
||||
<Trans
|
||||
ns='screenTabs'
|
||||
i18nKey='me.preferencesFilters.context'
|
||||
components={[
|
||||
<>
|
||||
{filter.context.map((c, index) => (
|
||||
<Fragment key={index}>
|
||||
<CustomText
|
||||
style={{
|
||||
color: colors.secondary,
|
||||
textDecorationColor: colors.disabled,
|
||||
textDecorationLine: 'underline',
|
||||
textDecorationStyle: 'solid'
|
||||
}}
|
||||
children={t(`screenTabs:me.preferencesFilters.contexts.${c}`)}
|
||||
/>
|
||||
<CustomText children={t('common:separator')} />
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<Icon
|
||||
name='chevron-right'
|
||||
size={StyleConstants.Font.Size.L}
|
||||
color={colors.primaryDefault}
|
||||
style={{ marginLeft: 8 }}
|
||||
/>
|
||||
</View>
|
||||
</TouchableNativeFeedback>
|
||||
)}
|
||||
ItemSeparatorComponent={ComponentSeparator}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default TabMePreferencesFilters
|
@ -1,21 +1,26 @@
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { Message } from '@components/Message'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||
import browserPackage from '@utils/helpers/browserPackage'
|
||||
import { TabMeProfileStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { featureCheck } from '@utils/helpers/featureCheck'
|
||||
import { TabMePreferencesStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { useFiltersQuery } from '@utils/queryHooks/filters'
|
||||
import { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
||||
import { useProfileMutation } from '@utils/queryHooks/profile'
|
||||
import { getAccountStorage } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import React, { useRef } from 'react'
|
||||
import React, { RefObject } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import FlashMessage from 'react-native-flash-message'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
|
||||
const TabMePreferences: React.FC<TabMeProfileStackScreenProps<'Tab-Me-Profile-Root'>> = () => {
|
||||
const TabMePreferencesRoot: React.FC<
|
||||
TabMePreferencesStackScreenProps<'Tab-Me-Preferences-Root'> & {
|
||||
messageRef: RefObject<FlashMessage>
|
||||
}
|
||||
> = ({ navigation, messageRef }) => {
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation(['common', 'screenTabs'])
|
||||
|
||||
@ -25,7 +30,7 @@ const TabMePreferences: React.FC<TabMeProfileStackScreenProps<'Tab-Me-Profile-Ro
|
||||
|
||||
const { data, isFetching, refetch } = usePreferencesQuery()
|
||||
|
||||
const messageRef = useRef<FlashMessage>(null)
|
||||
const { data: filters, isFetching: filtersIsFetching } = useFiltersQuery<'v2'>({ version: 'v2' })
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
@ -149,10 +154,23 @@ const TabMePreferences: React.FC<TabMeProfileStackScreenProps<'Tab-Me-Profile-Ro
|
||||
/>
|
||||
) : null}
|
||||
</MenuContainer>
|
||||
|
||||
<Message ref={messageRef} />
|
||||
{featureCheck('filter_server_side') ? (
|
||||
<MenuContainer>
|
||||
<MenuRow
|
||||
title={t('screenTabs:me.preferences.filters.title')}
|
||||
content={t('screenTabs:me.preferences.filters.content', {
|
||||
count: filters?.filter(filter =>
|
||||
filter.expires_at ? new Date(filter.expires_at) > new Date() : true
|
||||
).length
|
||||
})}
|
||||
loading={filtersIsFetching}
|
||||
iconBack='chevron-right'
|
||||
onPress={() => navigation.navigate('Tab-Me-Preferences-Filters')}
|
||||
/>
|
||||
</MenuContainer>
|
||||
) : null}
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
|
||||
export default TabMePreferences
|
||||
export default TabMePreferencesRoot
|
49
src/screens/Tabs/Me/Preferences/index.tsx
Normal file
49
src/screens/Tabs/Me/Preferences/index.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import { HeaderLeft } from '@components/Header'
|
||||
import { Message } from '@components/Message'
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||
import { TabMePreferencesStackParamList, TabMeStackScreenProps } from '@utils/navigation/navigators'
|
||||
import React, { useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import FlashMessage from 'react-native-flash-message'
|
||||
import TabMePreferencesFilter from './Filter'
|
||||
import TabMePreferencesFilters from './Filters'
|
||||
import TabMePreferencesRoot from './Root'
|
||||
|
||||
const Stack = createNativeStackNavigator<TabMePreferencesStackParamList>()
|
||||
|
||||
const TabMePreferences: React.FC<TabMeStackScreenProps<'Tab-Me-Preferences'>> = ({
|
||||
navigation
|
||||
}) => {
|
||||
const { t } = useTranslation('screenTabs')
|
||||
const messageRef = useRef<FlashMessage>(null)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Navigator screenOptions={{ headerShadowVisible: false }}>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Preferences-Root'
|
||||
options={{
|
||||
title: t('me.stacks.preferences.name'),
|
||||
headerLeft: () => (
|
||||
<HeaderLeft content='chevron-down' onPress={() => navigation.goBack()} />
|
||||
)
|
||||
}}
|
||||
>
|
||||
{props => <TabMePreferencesRoot messageRef={messageRef} {...props} />}
|
||||
</Stack.Screen>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Preferences-Filters'
|
||||
component={TabMePreferencesFilters}
|
||||
options={{ title: t('me.stacks.preferencesFilters.name') }}
|
||||
/>
|
||||
<Stack.Screen name='Tab-Me-Preferences-Filter'>
|
||||
{props => <TabMePreferencesFilter messageRef={messageRef} {...props} />}
|
||||
</Stack.Screen>
|
||||
</Stack.Navigator>
|
||||
|
||||
<Message ref={messageRef} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TabMePreferences
|
@ -90,7 +90,7 @@ const TabMeProfileFields: React.FC<
|
||||
navigation.setOptions({
|
||||
headerLeft: () => (
|
||||
<HeaderLeft
|
||||
content='x'
|
||||
content='chevron-left'
|
||||
onPress={() => {
|
||||
if (dirty) {
|
||||
Alert.alert(t('common:discard.title'), t('common:discard.message'), [
|
||||
|
@ -44,7 +44,7 @@ const TabMeProfileName: React.FC<
|
||||
navigation.setOptions({
|
||||
headerLeft: () => (
|
||||
<HeaderLeft
|
||||
content='x'
|
||||
content='chevron-left'
|
||||
onPress={() => {
|
||||
if (dirty) {
|
||||
Alert.alert(t('common:discard.title'), t('common:discard.message'), [
|
||||
|
@ -44,7 +44,7 @@ const TabMeProfileNote: React.FC<
|
||||
navigation.setOptions({
|
||||
headerLeft: () => (
|
||||
<HeaderLeft
|
||||
content='x'
|
||||
content='chevron-left'
|
||||
onPress={() => {
|
||||
if (dirty) {
|
||||
Alert.alert(t('common:discard.title'), t('common:discard.message'), [
|
||||
|
@ -32,33 +32,25 @@ const TabMeProfile: React.FC<TabMeStackScreenProps<'Tab-Me-Switch'>> = ({ naviga
|
||||
)
|
||||
}}
|
||||
>
|
||||
{({ route, navigation }) => (
|
||||
<TabMeProfileRoot messageRef={messageRef} route={route} navigation={navigation} />
|
||||
)}
|
||||
{props => <TabMeProfileRoot messageRef={messageRef} {...props} />}
|
||||
</Stack.Screen>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Profile-Name'
|
||||
options={{ title: t('me.stacks.profileName.name') }}
|
||||
>
|
||||
{({ route, navigation }) => (
|
||||
<TabMeProfileName messageRef={messageRef} route={route} navigation={navigation} />
|
||||
)}
|
||||
{props => <TabMeProfileName messageRef={messageRef} {...props} />}
|
||||
</Stack.Screen>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Profile-Note'
|
||||
options={{ title: t('me.stacks.profileNote.name') }}
|
||||
>
|
||||
{({ route, navigation }) => (
|
||||
<TabMeProfileNote messageRef={messageRef} route={route} navigation={navigation} />
|
||||
)}
|
||||
{props => <TabMeProfileNote messageRef={messageRef} {...props} />}
|
||||
</Stack.Screen>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Profile-Fields'
|
||||
options={{ title: t('me.stacks.profileFields.name') }}
|
||||
>
|
||||
{({ route, navigation }) => (
|
||||
<TabMeProfileFields messageRef={messageRef} route={route} navigation={navigation} />
|
||||
)}
|
||||
{props => <TabMeProfileFields messageRef={messageRef} {...props} />}
|
||||
</Stack.Screen>
|
||||
</Stack.Navigator>
|
||||
|
||||
|
@ -104,11 +104,7 @@ const TabMe: React.FC = () => {
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Preferences'
|
||||
component={TabMePreferences}
|
||||
options={({ navigation }: any) => ({
|
||||
presentation: 'modal',
|
||||
title: t('me.stacks.preferences.name'),
|
||||
headerLeft: () => <HeaderLeft content='chevron-down' onPress={() => navigation.pop(1)} />
|
||||
})}
|
||||
options={{ headerShown: false, presentation: 'modal' }}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Profile'
|
||||
@ -154,7 +150,9 @@ const TabMe: React.FC = () => {
|
||||
presentation: 'modal',
|
||||
headerShown: true,
|
||||
title: t('me.stacks.switch.name'),
|
||||
headerLeft: () => <HeaderLeft content='chevron-down' onPress={() => navigation.goBack()} />
|
||||
headerLeft: () => (
|
||||
<HeaderLeft content='chevron-down' onPress={() => navigation.goBack()} />
|
||||
)
|
||||
})}
|
||||
/>
|
||||
|
||||
|
@ -48,7 +48,9 @@ const AccountInformationActions: React.FC = () => {
|
||||
disabled={account === undefined}
|
||||
content='sliders'
|
||||
style={{ marginLeft: StyleConstants.Spacing.S }}
|
||||
onPress={() => navigation.navigate('Tab-Me-Preferences')}
|
||||
onPress={() =>
|
||||
navigation.navigate('Tab-Me-Preferences', { screen: 'Tab-Me-Preferences-Root' })
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
|
@ -125,7 +125,11 @@ const TabSharedReport: React.FC<TabSharedStackScreenProps<'Tab-Shared-Report'>>
|
||||
>
|
||||
<CustomText
|
||||
fontStyle='M'
|
||||
style={{ color: colors.primaryDefault, paddingRight: StyleConstants.Spacing.M }}
|
||||
style={{
|
||||
flex: 1,
|
||||
color: colors.primaryDefault,
|
||||
paddingRight: StyleConstants.Spacing.M
|
||||
}}
|
||||
numberOfLines={2}
|
||||
>
|
||||
{t('screenTabs:shared.report.forward.heading', {
|
||||
@ -140,15 +144,11 @@ const TabSharedReport: React.FC<TabSharedStackScreenProps<'Tab-Shared-Report'>>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
<CustomText
|
||||
fontStyle='M'
|
||||
style={{ color: colors.primaryDefault, marginBottom: StyleConstants.Spacing.S }}
|
||||
>
|
||||
{t('screenTabs:shared.report.reasons.heading')}
|
||||
</CustomText>
|
||||
<View style={{ marginLeft: StyleConstants.Spacing.M }}>
|
||||
<Selections options={categories} setOptions={setCategories} />
|
||||
</View>
|
||||
<Selections
|
||||
title={t('screenTabs:shared.report.reasons.heading')}
|
||||
options={categories}
|
||||
setOptions={setCategories}
|
||||
/>
|
||||
|
||||
{categories[1].selected || comment.length ? (
|
||||
<>
|
||||
@ -200,26 +200,13 @@ const TabSharedReport: React.FC<TabSharedStackScreenProps<'Tab-Shared-Report'>>
|
||||
) : null}
|
||||
|
||||
{rules.length ? (
|
||||
<>
|
||||
<CustomText
|
||||
fontStyle='M'
|
||||
style={{
|
||||
color: categories[2].selected ? colors.primaryDefault : colors.disabled,
|
||||
marginTop: StyleConstants.Spacing.M,
|
||||
marginBottom: StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
{t('screenTabs:shared.report.violatedRules.heading')}
|
||||
</CustomText>
|
||||
<View style={{ marginLeft: StyleConstants.Spacing.M }}>
|
||||
<Selections
|
||||
disabled={!categories[2].selected}
|
||||
multiple
|
||||
options={rules}
|
||||
setOptions={setRules}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
<Selections
|
||||
title={t('screenTabs:shared.report.violatedRules.heading')}
|
||||
disabled={!categories[2].selected}
|
||||
multiple
|
||||
options={rules}
|
||||
setOptions={setRules}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
@ -186,3 +186,18 @@ export type TabMeProfileStackParamList = {
|
||||
}
|
||||
export type TabMeProfileStackScreenProps<T extends keyof TabMeProfileStackParamList> =
|
||||
NativeStackScreenProps<TabMeProfileStackParamList, T>
|
||||
|
||||
export type TabMePreferencesStackParamList = {
|
||||
'Tab-Me-Preferences-Root': undefined
|
||||
'Tab-Me-Preferences-Filters': undefined
|
||||
'Tab-Me-Preferences-Filter':
|
||||
| {
|
||||
type: 'add'
|
||||
}
|
||||
| {
|
||||
type: 'edit'
|
||||
filter: Mastodon.Filter<'v2'>
|
||||
}
|
||||
}
|
||||
export type TabMePreferencesStackScreenProps<T extends keyof TabMePreferencesStackParamList> =
|
||||
NativeStackScreenProps<TabMePreferencesStackParamList, T>
|
||||
|
@ -1,10 +1,4 @@
|
||||
import {
|
||||
QueryFunctionContext,
|
||||
useMutation,
|
||||
UseMutationOptions,
|
||||
useQuery,
|
||||
UseQueryOptions
|
||||
} from '@tanstack/react-query'
|
||||
import { useMutation, UseMutationOptions, useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
import apiGeneral from '@utils/api/general'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { AxiosError } from 'axios'
|
||||
@ -12,7 +6,7 @@ import * as AuthSession from 'expo-auth-session'
|
||||
|
||||
export type QueryKeyApps = ['Apps']
|
||||
|
||||
const queryFunctionApps = async ({ queryKey }: QueryFunctionContext<QueryKeyApps>) => {
|
||||
const queryFunctionApps = async () => {
|
||||
const res = await apiInstance<Mastodon.Apps>({
|
||||
method: 'get',
|
||||
url: 'apps/verify_credentials'
|
||||
|
@ -1,24 +1,57 @@
|
||||
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { QueryFunctionContext, useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||
import apiInstance from '@utils/api/instance'
|
||||
import { AxiosError } from 'axios'
|
||||
|
||||
export type QueryKeyFilters = ['Filters']
|
||||
export type QueryKeyFilter = ['Filter', { id: Mastodon.Filter<'v2'>['id'] }]
|
||||
|
||||
const queryFunction = () =>
|
||||
apiInstance<Mastodon.Filter<'v1'>[]>({
|
||||
const filterQueryFunction = async ({ queryKey }: QueryFunctionContext<QueryKeyFilter>) => {
|
||||
const res = await apiInstance<Mastodon.Filter<'v2'>>({
|
||||
method: 'get',
|
||||
url: 'filters'
|
||||
}).then(res => res.body)
|
||||
version: 'v2',
|
||||
url: `filters/${queryKey[1].id}`
|
||||
})
|
||||
return res.body
|
||||
}
|
||||
|
||||
const useFiltersQuery = (params?: {
|
||||
options: UseQueryOptions<Mastodon.Filter<'v1'>[], AxiosError>
|
||||
const useFilterQuery = ({
|
||||
filter,
|
||||
options
|
||||
}: {
|
||||
filter: Mastodon.Filter<'v2'>
|
||||
options?: UseQueryOptions<Mastodon.Filter<'v2'>, AxiosError>
|
||||
}) => {
|
||||
const queryKey: QueryKeyFilters = ['Filters']
|
||||
return useQuery(queryKey, queryFunction, {
|
||||
const queryKey: QueryKeyFilter = ['Filter', { id: filter.id }]
|
||||
return useQuery(queryKey, filterQueryFunction, {
|
||||
...options,
|
||||
staleTime: Infinity,
|
||||
cacheTime: Infinity
|
||||
})
|
||||
}
|
||||
|
||||
export type QueryKeyFilters = ['Filters', { version: 'v1' | 'v2' }]
|
||||
|
||||
const filtersQueryFunction = async <T extends 'v1' | 'v2' = 'v1'>({
|
||||
queryKey
|
||||
}: QueryFunctionContext<QueryKeyFilters>) => {
|
||||
const version = queryKey[1].version
|
||||
const res = await apiInstance<Mastodon.Filter<T>[]>({
|
||||
method: 'get',
|
||||
version,
|
||||
url: 'filters'
|
||||
})
|
||||
return res.body
|
||||
}
|
||||
|
||||
const useFiltersQuery = <T extends 'v1' | 'v2' = 'v1'>(params?: {
|
||||
version?: T
|
||||
options?: UseQueryOptions<Mastodon.Filter<T>[], AxiosError>
|
||||
}) => {
|
||||
const queryKey: QueryKeyFilters = ['Filters', { version: params?.version || 'v1' }]
|
||||
return useQuery(queryKey, filtersQueryFunction, {
|
||||
...params?.options,
|
||||
staleTime: Infinity,
|
||||
cacheTime: Infinity
|
||||
})
|
||||
}
|
||||
|
||||
export { useFiltersQuery }
|
||||
export { useFilterQuery, useFiltersQuery }
|
||||
|
@ -42,9 +42,9 @@ const themeColors: {
|
||||
dark_darker: 'rgb(130, 130, 130)'
|
||||
},
|
||||
disabled: {
|
||||
light: 'rgb(200, 200, 200)',
|
||||
dark_lighter: 'rgb(120, 120, 120)',
|
||||
dark_darker: 'rgb(66, 66, 66)'
|
||||
light: 'rgb(220, 220, 220)',
|
||||
dark_lighter: 'rgb(70, 70, 70)',
|
||||
dark_darker: 'rgb(50, 50, 50)'
|
||||
},
|
||||
blue: {
|
||||
light: 'rgb(43, 144, 221)',
|
||||
|
Loading…
x
Reference in New Issue
Block a user