mirror of
https://github.com/tooot-app/app
synced 2025-02-07 15:38:47 +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 = {
|
export type Props = {
|
||||||
title?: string
|
title?: string
|
||||||
multiline?: boolean
|
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<
|
Omit<
|
||||||
TextInputProps,
|
TextInputProps,
|
||||||
| 'style'
|
| 'style'
|
||||||
@ -27,8 +29,9 @@ const ComponentInput = forwardRef(
|
|||||||
{
|
{
|
||||||
title,
|
title,
|
||||||
multiline = false,
|
multiline = false,
|
||||||
|
invalid = false,
|
||||||
value: [value, setValue],
|
value: [value, setValue],
|
||||||
selection: [selection, setSelection],
|
selection,
|
||||||
isFocused,
|
isFocused,
|
||||||
...props
|
...props
|
||||||
}: Props,
|
}: Props,
|
||||||
@ -43,7 +46,7 @@ const ComponentInput = forwardRef(
|
|||||||
paddingHorizontal: withTiming(StyleConstants.Spacing.XS),
|
paddingHorizontal: withTiming(StyleConstants.Spacing.XS),
|
||||||
left: withTiming(StyleConstants.Spacing.S),
|
left: withTiming(StyleConstants.Spacing.S),
|
||||||
top: withTiming(-(StyleConstants.Font.Size.S / 2) - 2),
|
top: withTiming(-(StyleConstants.Font.Size.S / 2) - 2),
|
||||||
backgroundColor: withTiming(colors.backgroundDefault)
|
backgroundColor: colors.backgroundDefault
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
@ -62,7 +65,7 @@ const ComponentInput = forwardRef(
|
|||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
marginVertical: StyleConstants.Spacing.S,
|
marginVertical: StyleConstants.Spacing.S,
|
||||||
padding: StyleConstants.Spacing.S,
|
padding: StyleConstants.Spacing.S,
|
||||||
borderColor: colors.border,
|
borderColor: invalid ? colors.red : colors.border,
|
||||||
flexDirection: multiline ? 'column' : 'row',
|
flexDirection: multiline ? 'column' : 'row',
|
||||||
alignItems: 'stretch'
|
alignItems: 'stretch'
|
||||||
}}
|
}}
|
||||||
@ -78,9 +81,13 @@ const ComponentInput = forwardRef(
|
|||||||
}}
|
}}
|
||||||
value={value}
|
value={value}
|
||||||
onChangeText={setValue}
|
onChangeText={setValue}
|
||||||
onFocus={() => (isFocused.current = true)}
|
{...(isFocused !== undefined && {
|
||||||
onBlur={() => (isFocused.current = false)}
|
onFocus: () => (isFocused.current = true),
|
||||||
onSelectionChange={({ nativeEvent }) => setSelection(nativeEvent.selection)}
|
onBlur: () => (isFocused.current = false)
|
||||||
|
})}
|
||||||
|
{...(selection !== undefined && {
|
||||||
|
onSelectionChange: ({ nativeEvent }) => selection[1](nativeEvent.selection)
|
||||||
|
})}
|
||||||
{...(multiline && {
|
{...(multiline && {
|
||||||
multiline,
|
multiline,
|
||||||
numberOfLines: Platform.OS === 'android' ? 5 : undefined
|
numberOfLines: Platform.OS === 'android' ? 5 : undefined
|
||||||
|
@ -44,7 +44,7 @@ const MenuRow: React.FC<Props> = ({
|
|||||||
loading = false,
|
loading = false,
|
||||||
onPress
|
onPress
|
||||||
}) => {
|
}) => {
|
||||||
const { colors, theme } = useTheme()
|
const { colors } = useTheme()
|
||||||
const { screenReaderEnabled } = useAccessibility()
|
const { screenReaderEnabled } = useAccessibility()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -8,17 +8,22 @@ import { ParseEmojis } from './Parse'
|
|||||||
import CustomText from './Text'
|
import CustomText from './Text'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
title?: string
|
||||||
|
|
||||||
multiple?: boolean
|
multiple?: boolean
|
||||||
options: { selected: boolean; content: string }[]
|
options: { selected: boolean; content: string }[]
|
||||||
setOptions: React.Dispatch<React.SetStateAction<{ selected: boolean; content: string }[]>>
|
setOptions: React.Dispatch<React.SetStateAction<{ selected: boolean; content: string }[]>>
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
invalid?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Selections: React.FC<Props> = ({
|
const Selections: React.FC<Props> = ({
|
||||||
|
title,
|
||||||
multiple = false,
|
multiple = false,
|
||||||
options,
|
options,
|
||||||
setOptions,
|
setOptions,
|
||||||
disabled = false
|
disabled = false,
|
||||||
|
invalid = false
|
||||||
}) => {
|
}) => {
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
|
|
||||||
@ -32,52 +37,71 @@ const Selections: React.FC<Props> = ({
|
|||||||
: 'circle'
|
: 'circle'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View style={{ marginVertical: StyleConstants.Spacing.S }}>
|
||||||
{options.map((option, index) => (
|
{title ? (
|
||||||
<Pressable
|
<CustomText
|
||||||
key={index}
|
fontStyle='M'
|
||||||
disabled={disabled}
|
children={title}
|
||||||
style={{ flex: 1, paddingVertical: StyleConstants.Spacing.S }}
|
style={{ color: disabled ? colors.disabled : colors.primaryDefault }}
|
||||||
onPress={() => {
|
/>
|
||||||
if (multiple) {
|
) : null}
|
||||||
haptics('Light')
|
<View
|
||||||
|
style={{
|
||||||
setOptions(options.map((o, i) => (i === index ? { ...o, selected: !o.selected } : o)))
|
paddingHorizontal: StyleConstants.Spacing.M,
|
||||||
} else {
|
paddingVertical: StyleConstants.Spacing.XS,
|
||||||
if (!option.selected) {
|
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')
|
haptics('Light')
|
||||||
|
|
||||||
setOptions(
|
setOptions(
|
||||||
options.map((o, i) => {
|
options.map((o, i) => (i === index ? { ...o, selected: !o.selected } : o))
|
||||||
if (i === index) {
|
|
||||||
return { ...o, selected: true }
|
|
||||||
} else {
|
|
||||||
return { ...o, selected: false }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
|
} 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' }}>
|
||||||
<View style={{ flex: 1, flexDirection: 'row' }}>
|
<Icon
|
||||||
<Icon
|
style={{
|
||||||
style={{
|
marginTop: (StyleConstants.Font.LineHeight.M - StyleConstants.Font.Size.M) / 2,
|
||||||
paddingTop: StyleConstants.Font.LineHeight.M - StyleConstants.Font.Size.M,
|
marginRight: StyleConstants.Spacing.S
|
||||||
marginRight: StyleConstants.Spacing.S
|
}}
|
||||||
}}
|
name={isSelected(index)}
|
||||||
name={isSelected(index)}
|
size={StyleConstants.Font.Size.M}
|
||||||
size={StyleConstants.Font.Size.M}
|
color={disabled ? colors.disabled : colors.primaryDefault}
|
||||||
color={disabled ? colors.disabled : colors.primaryDefault}
|
|
||||||
/>
|
|
||||||
<CustomText style={{ flex: 1 }}>
|
|
||||||
<ParseEmojis
|
|
||||||
content={option.content}
|
|
||||||
style={{ color: disabled ? colors.disabled : colors.primaryDefault }}
|
|
||||||
/>
|
/>
|
||||||
</CustomText>
|
<CustomText fontStyle='S' style={{ flex: 1 }}>
|
||||||
</View>
|
<ParseEmojis
|
||||||
</Pressable>
|
content={option.content}
|
||||||
))}
|
style={{ color: disabled ? colors.disabled : colors.primaryDefault }}
|
||||||
|
/>
|
||||||
|
</CustomText>
|
||||||
|
</View>
|
||||||
|
</Pressable>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ export const shouldFilter = ({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const queryKeyFilters: QueryKeyFilters = ['Filters']
|
const queryKeyFilters: QueryKeyFilters = ['Filters', { version: 'v1' }]
|
||||||
queryClient.getQueryData<Mastodon.Filter<'v1'>[]>(queryKeyFilters)?.forEach(filter => {
|
queryClient.getQueryData<Mastodon.Filter<'v1'>[]>(queryKeyFilters)?.forEach(filter => {
|
||||||
if (returnFilter) {
|
if (returnFilter) {
|
||||||
return
|
return
|
||||||
|
@ -133,7 +133,7 @@ const TimelinePoll: React.FC = () => {
|
|||||||
<View style={{ flex: 1, flexDirection: 'row' }}>
|
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||||
<Icon
|
<Icon
|
||||||
style={{
|
style={{
|
||||||
paddingTop: StyleConstants.Font.LineHeight.M - StyleConstants.Font.Size.M,
|
marginTop: (StyleConstants.Font.LineHeight.M - StyleConstants.Font.Size.M) / 2,
|
||||||
marginRight: StyleConstants.Spacing.S
|
marginRight: StyleConstants.Spacing.S
|
||||||
}}
|
}}
|
||||||
name={
|
name={
|
||||||
@ -205,7 +205,7 @@ const TimelinePoll: React.FC = () => {
|
|||||||
<View style={{ flex: 1, flexDirection: 'row' }}>
|
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||||
<Icon
|
<Icon
|
||||||
style={{
|
style={{
|
||||||
paddingTop: StyleConstants.Font.LineHeight.M - StyleConstants.Font.Size.M,
|
marginTop: (StyleConstants.Font.LineHeight.M - StyleConstants.Font.Size.M) / 2,
|
||||||
marginRight: StyleConstants.Spacing.S
|
marginRight: StyleConstants.Spacing.S
|
||||||
}}
|
}}
|
||||||
name={isSelected(index)}
|
name={isSelected(index)}
|
||||||
|
@ -72,6 +72,15 @@
|
|||||||
"preferences": {
|
"preferences": {
|
||||||
"name": "Preferences"
|
"name": "Preferences"
|
||||||
},
|
},
|
||||||
|
"preferencesFilters": {
|
||||||
|
"name": "All content filters"
|
||||||
|
},
|
||||||
|
"preferencesFilterAdd": {
|
||||||
|
"name": "Create a Filter"
|
||||||
|
},
|
||||||
|
"preferencesFilterEdit": {
|
||||||
|
"name": "Edit Filter"
|
||||||
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"name": "Edit Profile"
|
"name": "Edit Profile"
|
||||||
},
|
},
|
||||||
@ -127,7 +136,7 @@
|
|||||||
},
|
},
|
||||||
"preferences": {
|
"preferences": {
|
||||||
"visibility": {
|
"visibility": {
|
||||||
"title": "Posting visibility",
|
"title": "Default posting visibility",
|
||||||
"options": {
|
"options": {
|
||||||
"public": "Public",
|
"public": "Public",
|
||||||
"unlisted": "Unlisted",
|
"unlisted": "Unlisted",
|
||||||
@ -135,7 +144,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sensitive": {
|
"sensitive": {
|
||||||
"title": "Posting media sensitive"
|
"title": "Default mark media as sensitive"
|
||||||
},
|
},
|
||||||
"media": {
|
"media": {
|
||||||
"title": "Media display",
|
"title": "Media display",
|
||||||
@ -146,16 +155,63 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"spoilers": {
|
"spoilers": {
|
||||||
"title": "Auto expand toots with content warning",
|
"title": "Auto expand toots with content warning"
|
||||||
},
|
},
|
||||||
"autoplay_gifs": {
|
"autoplay_gifs": {
|
||||||
"title": "Autoplay GIF in toots"
|
"title": "Autoplay GIF in toots"
|
||||||
},
|
},
|
||||||
|
"filters": {
|
||||||
|
"title": "Content filters",
|
||||||
|
"content": "{{count}} active"
|
||||||
|
},
|
||||||
"web_only": {
|
"web_only": {
|
||||||
"title": "Update settings",
|
"title": "Update settings",
|
||||||
"description": "Settings below can only be updated using the web UI"
|
"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": {
|
"profile": {
|
||||||
"feedback": {
|
"feedback": {
|
||||||
"succeed": "{{type}} updated",
|
"succeed": "{{type}} updated",
|
||||||
|
@ -19,7 +19,7 @@ const TabMeListEdit: React.FC<TabMeStackScreenProps<'Tab-Me-List-Edit'>> = ({
|
|||||||
navigation,
|
navigation,
|
||||||
route: { params }
|
route: { params }
|
||||||
}) => {
|
}) => {
|
||||||
const { colors, theme } = useTheme()
|
const { colors } = useTheme()
|
||||||
const { t } = useTranslation(['common', 'screenTabs'])
|
const { t } = useTranslation(['common', 'screenTabs'])
|
||||||
|
|
||||||
const messageRef = useRef(null)
|
const messageRef = useRef(null)
|
||||||
@ -147,18 +147,11 @@ const TabMeListEdit: React.FC<TabMeStackScreenProps<'Tab-Me-List-Edit'>> = ({
|
|||||||
<ScrollView style={{ paddingHorizontal: StyleConstants.Spacing.Global.PagePadding }}>
|
<ScrollView style={{ paddingHorizontal: StyleConstants.Spacing.Global.PagePadding }}>
|
||||||
<ComponentInput {...inputProps} autoFocus title={t('screenTabs:me.listEdit.title')} />
|
<ComponentInput {...inputProps} autoFocus title={t('screenTabs:me.listEdit.title')} />
|
||||||
|
|
||||||
<CustomText
|
<Selections
|
||||||
fontStyle='M'
|
title={t('screenTabs:me.listEdit.repliesPolicy.heading')}
|
||||||
fontWeight='Bold'
|
options={options}
|
||||||
style={{
|
setOptions={setOptions}
|
||||||
color: colors.primaryDefault,
|
/>
|
||||||
marginBottom: StyleConstants.Spacing.XS,
|
|
||||||
marginTop: StyleConstants.Spacing.M
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('screenTabs:me.listEdit.repliesPolicy.heading')}
|
|
||||||
</CustomText>
|
|
||||||
<Selections options={options} setOptions={setOptions} />
|
|
||||||
|
|
||||||
<Message ref={messageRef} />
|
<Message ref={messageRef} />
|
||||||
</ScrollView>
|
</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 { MenuContainer, MenuRow } from '@components/Menu'
|
||||||
import { Message } from '@components/Message'
|
|
||||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||||
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
import { androidActionSheetStyles } from '@utils/helpers/androidActionSheetStyles'
|
||||||
import browserPackage from '@utils/helpers/browserPackage'
|
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 { usePreferencesQuery } from '@utils/queryHooks/preferences'
|
||||||
import { useProfileMutation } from '@utils/queryHooks/profile'
|
import { useProfileMutation } from '@utils/queryHooks/profile'
|
||||||
import { getAccountStorage } from '@utils/storage/actions'
|
import { getAccountStorage } from '@utils/storage/actions'
|
||||||
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 * as WebBrowser from 'expo-web-browser'
|
import * as WebBrowser from 'expo-web-browser'
|
||||||
import React, { useRef } from 'react'
|
import React, { RefObject } 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 { ScrollView } from 'react-native-gesture-handler'
|
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 { colors } = useTheme()
|
||||||
const { t } = useTranslation(['common', 'screenTabs'])
|
const { t } = useTranslation(['common', 'screenTabs'])
|
||||||
|
|
||||||
@ -25,7 +30,7 @@ const TabMePreferences: React.FC<TabMeProfileStackScreenProps<'Tab-Me-Profile-Ro
|
|||||||
|
|
||||||
const { data, isFetching, refetch } = usePreferencesQuery()
|
const { data, isFetching, refetch } = usePreferencesQuery()
|
||||||
|
|
||||||
const messageRef = useRef<FlashMessage>(null)
|
const { data: filters, isFetching: filtersIsFetching } = useFiltersQuery<'v2'>({ version: 'v2' })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
@ -149,10 +154,23 @@ const TabMePreferences: React.FC<TabMeProfileStackScreenProps<'Tab-Me-Profile-Ro
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</MenuContainer>
|
</MenuContainer>
|
||||||
|
{featureCheck('filter_server_side') ? (
|
||||||
<Message ref={messageRef} />
|
<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>
|
</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({
|
navigation.setOptions({
|
||||||
headerLeft: () => (
|
headerLeft: () => (
|
||||||
<HeaderLeft
|
<HeaderLeft
|
||||||
content='x'
|
content='chevron-left'
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (dirty) {
|
if (dirty) {
|
||||||
Alert.alert(t('common:discard.title'), t('common:discard.message'), [
|
Alert.alert(t('common:discard.title'), t('common:discard.message'), [
|
||||||
|
@ -44,7 +44,7 @@ const TabMeProfileName: React.FC<
|
|||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
headerLeft: () => (
|
headerLeft: () => (
|
||||||
<HeaderLeft
|
<HeaderLeft
|
||||||
content='x'
|
content='chevron-left'
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (dirty) {
|
if (dirty) {
|
||||||
Alert.alert(t('common:discard.title'), t('common:discard.message'), [
|
Alert.alert(t('common:discard.title'), t('common:discard.message'), [
|
||||||
|
@ -44,7 +44,7 @@ const TabMeProfileNote: React.FC<
|
|||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
headerLeft: () => (
|
headerLeft: () => (
|
||||||
<HeaderLeft
|
<HeaderLeft
|
||||||
content='x'
|
content='chevron-left'
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (dirty) {
|
if (dirty) {
|
||||||
Alert.alert(t('common:discard.title'), t('common:discard.message'), [
|
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 }) => (
|
{props => <TabMeProfileRoot messageRef={messageRef} {...props} />}
|
||||||
<TabMeProfileRoot messageRef={messageRef} route={route} navigation={navigation} />
|
|
||||||
)}
|
|
||||||
</Stack.Screen>
|
</Stack.Screen>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name='Tab-Me-Profile-Name'
|
name='Tab-Me-Profile-Name'
|
||||||
options={{ title: t('me.stacks.profileName.name') }}
|
options={{ title: t('me.stacks.profileName.name') }}
|
||||||
>
|
>
|
||||||
{({ route, navigation }) => (
|
{props => <TabMeProfileName messageRef={messageRef} {...props} />}
|
||||||
<TabMeProfileName messageRef={messageRef} route={route} navigation={navigation} />
|
|
||||||
)}
|
|
||||||
</Stack.Screen>
|
</Stack.Screen>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name='Tab-Me-Profile-Note'
|
name='Tab-Me-Profile-Note'
|
||||||
options={{ title: t('me.stacks.profileNote.name') }}
|
options={{ title: t('me.stacks.profileNote.name') }}
|
||||||
>
|
>
|
||||||
{({ route, navigation }) => (
|
{props => <TabMeProfileNote messageRef={messageRef} {...props} />}
|
||||||
<TabMeProfileNote messageRef={messageRef} route={route} navigation={navigation} />
|
|
||||||
)}
|
|
||||||
</Stack.Screen>
|
</Stack.Screen>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name='Tab-Me-Profile-Fields'
|
name='Tab-Me-Profile-Fields'
|
||||||
options={{ title: t('me.stacks.profileFields.name') }}
|
options={{ title: t('me.stacks.profileFields.name') }}
|
||||||
>
|
>
|
||||||
{({ route, navigation }) => (
|
{props => <TabMeProfileFields messageRef={messageRef} {...props} />}
|
||||||
<TabMeProfileFields messageRef={messageRef} route={route} navigation={navigation} />
|
|
||||||
)}
|
|
||||||
</Stack.Screen>
|
</Stack.Screen>
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
|
|
||||||
|
@ -104,11 +104,7 @@ const TabMe: React.FC = () => {
|
|||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name='Tab-Me-Preferences'
|
name='Tab-Me-Preferences'
|
||||||
component={TabMePreferences}
|
component={TabMePreferences}
|
||||||
options={({ navigation }: any) => ({
|
options={{ headerShown: false, presentation: 'modal' }}
|
||||||
presentation: 'modal',
|
|
||||||
title: t('me.stacks.preferences.name'),
|
|
||||||
headerLeft: () => <HeaderLeft content='chevron-down' onPress={() => navigation.pop(1)} />
|
|
||||||
})}
|
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name='Tab-Me-Profile'
|
name='Tab-Me-Profile'
|
||||||
@ -154,7 +150,9 @@ const TabMe: React.FC = () => {
|
|||||||
presentation: 'modal',
|
presentation: 'modal',
|
||||||
headerShown: true,
|
headerShown: true,
|
||||||
title: t('me.stacks.switch.name'),
|
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}
|
disabled={account === undefined}
|
||||||
content='sliders'
|
content='sliders'
|
||||||
style={{ marginLeft: StyleConstants.Spacing.S }}
|
style={{ marginLeft: StyleConstants.Spacing.S }}
|
||||||
onPress={() => navigation.navigate('Tab-Me-Preferences')}
|
onPress={() =>
|
||||||
|
navigation.navigate('Tab-Me-Preferences', { screen: 'Tab-Me-Preferences-Root' })
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
@ -125,7 +125,11 @@ const TabSharedReport: React.FC<TabSharedStackScreenProps<'Tab-Shared-Report'>>
|
|||||||
>
|
>
|
||||||
<CustomText
|
<CustomText
|
||||||
fontStyle='M'
|
fontStyle='M'
|
||||||
style={{ color: colors.primaryDefault, paddingRight: StyleConstants.Spacing.M }}
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
color: colors.primaryDefault,
|
||||||
|
paddingRight: StyleConstants.Spacing.M
|
||||||
|
}}
|
||||||
numberOfLines={2}
|
numberOfLines={2}
|
||||||
>
|
>
|
||||||
{t('screenTabs:shared.report.forward.heading', {
|
{t('screenTabs:shared.report.forward.heading', {
|
||||||
@ -140,15 +144,11 @@ const TabSharedReport: React.FC<TabSharedStackScreenProps<'Tab-Shared-Report'>>
|
|||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<CustomText
|
<Selections
|
||||||
fontStyle='M'
|
title={t('screenTabs:shared.report.reasons.heading')}
|
||||||
style={{ color: colors.primaryDefault, marginBottom: StyleConstants.Spacing.S }}
|
options={categories}
|
||||||
>
|
setOptions={setCategories}
|
||||||
{t('screenTabs:shared.report.reasons.heading')}
|
/>
|
||||||
</CustomText>
|
|
||||||
<View style={{ marginLeft: StyleConstants.Spacing.M }}>
|
|
||||||
<Selections options={categories} setOptions={setCategories} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{categories[1].selected || comment.length ? (
|
{categories[1].selected || comment.length ? (
|
||||||
<>
|
<>
|
||||||
@ -200,26 +200,13 @@ const TabSharedReport: React.FC<TabSharedStackScreenProps<'Tab-Shared-Report'>>
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{rules.length ? (
|
{rules.length ? (
|
||||||
<>
|
<Selections
|
||||||
<CustomText
|
title={t('screenTabs:shared.report.violatedRules.heading')}
|
||||||
fontStyle='M'
|
disabled={!categories[2].selected}
|
||||||
style={{
|
multiple
|
||||||
color: categories[2].selected ? colors.primaryDefault : colors.disabled,
|
options={rules}
|
||||||
marginTop: StyleConstants.Spacing.M,
|
setOptions={setRules}
|
||||||
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>
|
|
||||||
</>
|
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
@ -186,3 +186,18 @@ export type TabMeProfileStackParamList = {
|
|||||||
}
|
}
|
||||||
export type TabMeProfileStackScreenProps<T extends keyof TabMeProfileStackParamList> =
|
export type TabMeProfileStackScreenProps<T extends keyof TabMeProfileStackParamList> =
|
||||||
NativeStackScreenProps<TabMeProfileStackParamList, T>
|
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 {
|
import { useMutation, UseMutationOptions, useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||||
QueryFunctionContext,
|
|
||||||
useMutation,
|
|
||||||
UseMutationOptions,
|
|
||||||
useQuery,
|
|
||||||
UseQueryOptions
|
|
||||||
} from '@tanstack/react-query'
|
|
||||||
import apiGeneral from '@utils/api/general'
|
import apiGeneral from '@utils/api/general'
|
||||||
import apiInstance from '@utils/api/instance'
|
import apiInstance from '@utils/api/instance'
|
||||||
import { AxiosError } from 'axios'
|
import { AxiosError } from 'axios'
|
||||||
@ -12,7 +6,7 @@ import * as AuthSession from 'expo-auth-session'
|
|||||||
|
|
||||||
export type QueryKeyApps = ['Apps']
|
export type QueryKeyApps = ['Apps']
|
||||||
|
|
||||||
const queryFunctionApps = async ({ queryKey }: QueryFunctionContext<QueryKeyApps>) => {
|
const queryFunctionApps = async () => {
|
||||||
const res = await apiInstance<Mastodon.Apps>({
|
const res = await apiInstance<Mastodon.Apps>({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: 'apps/verify_credentials'
|
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 apiInstance from '@utils/api/instance'
|
||||||
import { AxiosError } from 'axios'
|
import { AxiosError } from 'axios'
|
||||||
|
|
||||||
export type QueryKeyFilters = ['Filters']
|
export type QueryKeyFilter = ['Filter', { id: Mastodon.Filter<'v2'>['id'] }]
|
||||||
|
|
||||||
const queryFunction = () =>
|
const filterQueryFunction = async ({ queryKey }: QueryFunctionContext<QueryKeyFilter>) => {
|
||||||
apiInstance<Mastodon.Filter<'v1'>[]>({
|
const res = await apiInstance<Mastodon.Filter<'v2'>>({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: 'filters'
|
version: 'v2',
|
||||||
}).then(res => res.body)
|
url: `filters/${queryKey[1].id}`
|
||||||
|
})
|
||||||
|
return res.body
|
||||||
|
}
|
||||||
|
|
||||||
const useFiltersQuery = (params?: {
|
const useFilterQuery = ({
|
||||||
options: UseQueryOptions<Mastodon.Filter<'v1'>[], AxiosError>
|
filter,
|
||||||
|
options
|
||||||
|
}: {
|
||||||
|
filter: Mastodon.Filter<'v2'>
|
||||||
|
options?: UseQueryOptions<Mastodon.Filter<'v2'>, AxiosError>
|
||||||
}) => {
|
}) => {
|
||||||
const queryKey: QueryKeyFilters = ['Filters']
|
const queryKey: QueryKeyFilter = ['Filter', { id: filter.id }]
|
||||||
return useQuery(queryKey, queryFunction, {
|
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,
|
...params?.options,
|
||||||
staleTime: Infinity,
|
staleTime: Infinity,
|
||||||
cacheTime: Infinity
|
cacheTime: Infinity
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export { useFiltersQuery }
|
export { useFilterQuery, useFiltersQuery }
|
||||||
|
@ -42,9 +42,9 @@ const themeColors: {
|
|||||||
dark_darker: 'rgb(130, 130, 130)'
|
dark_darker: 'rgb(130, 130, 130)'
|
||||||
},
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
light: 'rgb(200, 200, 200)',
|
light: 'rgb(220, 220, 220)',
|
||||||
dark_lighter: 'rgb(120, 120, 120)',
|
dark_lighter: 'rgb(70, 70, 70)',
|
||||||
dark_darker: 'rgb(66, 66, 66)'
|
dark_darker: 'rgb(50, 50, 50)'
|
||||||
},
|
},
|
||||||
blue: {
|
blue: {
|
||||||
light: 'rgb(43, 144, 221)',
|
light: 'rgb(43, 144, 221)',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user