Refined filter view

This commit is contained in:
xmflsct 2023-01-26 13:59:42 +01:00
parent 2a806695ca
commit e8eb62e2d0
9 changed files with 304 additions and 225 deletions

View File

@ -235,12 +235,7 @@ const ComponentInstance: React.FC<Props> = ({
/>
</View>
) : null}
<View
style={{
marginTop: StyleConstants.Spacing.L,
marginHorizontal: StyleConstants.Spacing.Global.PagePadding
}}
>
<View style={{ marginTop: StyleConstants.Spacing.L }}>
<View
style={{
flexDirection: 'row',

View File

@ -0,0 +1,29 @@
import { useHeaderHeight } from '@react-navigation/elements'
import { StyleConstants } from '@utils/styles/constants'
import { forwardRef, PropsWithChildren, RefObject } from 'react'
import { KeyboardAvoidingView, Platform, ScrollView } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
export const ModalScrollView = forwardRef(
({ children }: PropsWithChildren, ref: RefObject<ScrollView>) => {
const headerHeight = useHeaderHeight()
return (
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
keyboardVerticalOffset={headerHeight}
>
<SafeAreaView style={{ flex: 1 }} edges={['bottom']}>
<ScrollView
ref={ref}
keyboardShouldPersistTaps='always'
contentContainerStyle={{ padding: StyleConstants.Spacing.Global.PagePadding }}
>
{children}
</ScrollView>
</SafeAreaView>
</KeyboardAvoidingView>
)
}
)

View File

@ -309,7 +309,11 @@ const ParseHTML: React.FC<Props> = ({
height: numberOfLines === 1 && !expanded ? 0 : undefined
}}
numberOfLines={
typeof totalLines === 'number' ? (expanded ? 999 : numberOfLines) : MAX_ALLOWED_LINES
typeof totalLines === 'number'
? expanded
? 999
: numberOfLines
: Math.max(MAX_ALLOWED_LINES, numberOfLines)
}
selectable={selectable}
/>

View File

@ -210,7 +210,8 @@
"hide": "Hidden completely"
},
"keywords": "Matches for these keywords",
"keyword": "Keyword"
"keyword": "Keyword",
"statuses": "Matches these toots"
},
"profile": {
"feedback": {

View File

@ -1,5 +1,6 @@
import haptics from '@components/haptics'
import { HeaderLeft, HeaderRight } from '@components/Header'
import { ModalScrollView } from '@components/ModalScrollView'
import CustomText from '@components/Text'
import apiInstance from '@utils/api/instance'
import { ScreenComposeStackScreenProps } from '@utils/navigation/navigators'
@ -7,8 +8,7 @@ import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useContext, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Alert, KeyboardAvoidingView, Platform, ScrollView, TextInput } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import { Alert, TextInput } from 'react-native'
import ComposeContext from './utils/createContext'
const ComposeEditAttachment: React.FC<
@ -34,9 +34,7 @@ const ComposeEditAttachment: React.FC<
useEffect(() => {
navigation.setOptions({
title: t('content.editAttachment.header.title'),
headerLeft: () => (
<HeaderLeft content='chevron-down' onPress={() => navigation.goBack()} />
),
headerLeft: () => <HeaderLeft content='chevron-down' onPress={() => navigation.goBack()} />,
headerRight: () => (
<HeaderRight
accessibilityLabel={t('content.editAttachment.header.right.accessibilityLabel')}
@ -88,59 +86,48 @@ const ComposeEditAttachment: React.FC<
}, [theAttachment])
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ flex: 1 }}
>
<SafeAreaView
style={{ flex: 1, padding: StyleConstants.Spacing.Global.PagePadding }}
edges={['left', 'right', 'bottom']}
>
<ScrollView>
<CustomText fontStyle='M' style={{ color: colors.primaryDefault }} fontWeight='Bold'>
{t('content.editAttachment.content.altText.heading')}
</CustomText>
<TextInput
style={{
height:
StyleConstants.Font.Size.M * 11 + StyleConstants.Spacing.Global.PagePadding * 2,
...StyleConstants.FontStyle.M,
marginTop: StyleConstants.Spacing.M,
marginBottom: StyleConstants.Spacing.S,
padding: StyleConstants.Spacing.Global.PagePadding,
borderWidth: 1,
borderColor: colors.border,
color: colors.primaryDefault
}}
maxLength={1500}
multiline
onChangeText={e =>
composeDispatch({
type: 'attachment/edit',
payload: {
...theAttachment,
description: e
}
})
<ModalScrollView>
<CustomText fontStyle='M' style={{ color: colors.primaryDefault }} fontWeight='Bold'>
{t('content.editAttachment.content.altText.heading')}
</CustomText>
<TextInput
style={{
height: StyleConstants.Font.Size.M * 11 + StyleConstants.Spacing.Global.PagePadding * 2,
...StyleConstants.FontStyle.M,
marginTop: StyleConstants.Spacing.M,
marginBottom: StyleConstants.Spacing.S,
padding: StyleConstants.Spacing.Global.PagePadding,
borderWidth: 1,
borderColor: colors.border,
color: colors.primaryDefault
}}
maxLength={1500}
multiline
onChangeText={e =>
composeDispatch({
type: 'attachment/edit',
payload: {
...theAttachment,
description: e
}
placeholder={t('content.editAttachment.content.altText.placeholder')}
placeholderTextColor={colors.secondary}
value={theAttachment.description}
/>
<CustomText
fontStyle='S'
style={{
textAlign: 'right',
marginRight: StyleConstants.Spacing.S,
marginBottom: StyleConstants.Spacing.M,
color: colors.secondary
}}
>
{theAttachment.description?.length || 0} / 1500
</CustomText>
</ScrollView>
</SafeAreaView>
</KeyboardAvoidingView>
})
}
placeholder={t('content.editAttachment.content.altText.placeholder')}
placeholderTextColor={colors.secondary}
value={theAttachment.description}
/>
<CustomText
fontStyle='S'
style={{
textAlign: 'right',
marginRight: StyleConstants.Spacing.S,
marginBottom: StyleConstants.Spacing.M,
color: colors.secondary
}}
>
{theAttachment.description?.length || 0} / 1500
</CustomText>
</ModalScrollView>
)
}

View File

@ -4,22 +4,24 @@ import { HeaderLeft, HeaderRight } from '@components/Header'
import Hr from '@components/Hr'
import ComponentInput from '@components/Input'
import { MenuRow } from '@components/Menu'
import { ModalScrollView } from '@components/ModalScrollView'
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 browserPackage from '@utils/helpers/browserPackage'
import { TabMePreferencesStackScreenProps } from '@utils/navigation/navigators'
import { queryClient } from '@utils/queryHooks'
import { QueryKeyFilters } from '@utils/queryHooks/filters'
import { getAccountStorage } from '@utils/storage/actions'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { RefObject, useEffect, useState } from 'react'
import * as WebBrowser from 'expo-web-browser'
import React, { RefObject, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { KeyboardAvoidingView, Platform, View } from 'react-native'
import { ScrollView, 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'> & {
@ -101,7 +103,11 @@ const TabMePreferencesFilter: React.FC<
])
const [keywords, setKeywords] = useState<string[]>(
params.type === 'edit' ? params.filter.keywords.map(({ keyword }) => keyword) : []
params.type === 'edit'
? params.filter.keywords.length
? params.filter.keywords.map(({ keyword }) => keyword)
: ['']
: ['']
)
useEffect(() => {
@ -152,6 +158,48 @@ const TabMePreferencesFilter: React.FC<
})
break
case 'edit':
isLoading = true
await apiInstance({
method: 'put',
version: 'v2',
url: `filters/${params.filter.id}`,
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 }))
}
: params.filter.keywords.length && {
keywords_attributes: params.filter.keywords.map(keyword => ({
...keyword,
_destroy: 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
}
}}
@ -160,105 +208,132 @@ const TabMePreferencesFilter: React.FC<
})
}, [titleState[0], expiration, contexts, actions, keywords])
const scrollViewRef = useRef<ScrollView>(null)
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} />
<ModalScrollView ref={scrollViewRef}>
<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 }} />
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
<CustomText
fontStyle='M'
children={t('screenTabs:me.preferencesFilter.keywords')}
style={{ color: colors.primaryDefault }}
/>
<CustomText
style={{ marginHorizontal: StyleConstants.Spacing.M, color: colors.secondary }}
children={t('screenTabs:me.preferencesFilters.keywords', { count: keywords.length })}
/>
</View>
<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}
style={{ marginRight: StyleConstants.Spacing.M }}
/>
<Button
onPress={() => {
setKeywords([...keywords, ''])
setTimeout(() => scrollViewRef.current?.scrollToEnd(), 50)
}}
type='icon'
content='plus'
round
/>
</View>
{params.type === 'edit' && params.filter.statuses?.length ? (
<>
<Hr style={{ marginVertical: StyleConstants.Spacing.M }} />
<MenuRow
title={t('screenTabs:me.preferencesFilter.expiration')}
content={t(`screenTabs:me.preferencesFilter.expirationOptions.${expiration}`)}
iconBack='chevron-right'
onPress={() =>
showActionSheetWithOptions(
title={t('screenTabs:me.preferencesFilter.statuses')}
content={t('screenTabs:me.preferencesFilters.statuses', {
count: params.filter.statuses.length
})}
iconBack='external-link'
onPress={async () =>
WebBrowser.openAuthSessionAsync(
`https://${getAccountStorage.string('auth.domain')}/filters/${
params.filter.id
}/statuses`,
'tooot://tooot',
{
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])
...(await browserPackage()),
dismissButtonStyle: 'done',
readerMode: false
}
)
}
/>
<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>
</>
) : null}
</ModalScrollView>
)
}

View File

@ -4,7 +4,6 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { TabMeProfileStackParamList, TabMeStackScreenProps } from '@utils/navigation/navigators'
import React, { useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { KeyboardAvoidingView, Platform } from 'react-native'
import FlashMessage from 'react-native-flash-message'
import TabMeProfileFields from './Fields'
import TabMeProfileName from './Name'
@ -18,10 +17,7 @@ const TabMeProfile: React.FC<TabMeStackScreenProps<'Tab-Me-Switch'>> = ({ naviga
const messageRef = useRef<FlashMessage>(null)
return (
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
<>
<Stack.Navigator screenOptions={{ headerShadowVisible: false }}>
<Stack.Screen
name='Tab-Me-Profile-Root'
@ -55,7 +51,7 @@ const TabMeProfile: React.FC<TabMeStackScreenProps<'Tab-Me-Switch'>> = ({ naviga
</Stack.Navigator>
<Message ref={messageRef} />
</KeyboardAvoidingView>
</>
)
}

View File

@ -1,12 +1,13 @@
import AccountButton from '@components/AccountButton'
import ComponentInstance from '@components/Instance'
import { ModalScrollView } from '@components/ModalScrollView'
import CustomText from '@components/Text'
import { getReadableAccounts } from '@utils/storage/actions'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { KeyboardAvoidingView, Platform, View } from 'react-native'
import { View } from 'react-native'
import { ScrollView } from 'react-native-gesture-handler'
const TabMeSwitch: React.FC = () => {
@ -20,63 +21,53 @@ const TabMeSwitch: React.FC = () => {
}, [scrollViewRef.current])
return (
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
<ScrollView
ref={scrollViewRef}
style={{ marginBottom: StyleConstants.Spacing.L * 2 }}
keyboardShouldPersistTaps='always'
>
<View>
<CustomText
fontStyle='M'
style={{
textAlign: 'center',
paddingVertical: StyleConstants.Spacing.S,
color: colors.primaryDefault
}}
>
{t('me.switch.new')}
</CustomText>
<ComponentInstance scrollViewRef={scrollViewRef} disableHeaderImage goBack />
</View>
<View
<ModalScrollView>
<View>
<CustomText
fontStyle='M'
style={{
marginTop: StyleConstants.Spacing.S,
paddingTop: StyleConstants.Spacing.M,
marginHorizontal: StyleConstants.Spacing.Global.PagePadding,
borderTopWidth: 1,
borderTopColor: colors.border
textAlign: 'center',
paddingVertical: StyleConstants.Spacing.S,
color: colors.primaryDefault
}}
>
<CustomText
fontStyle='M'
style={{
textAlign: 'center',
paddingVertical: StyleConstants.Spacing.S,
color: colors.primaryDefault
}}
>
{t('me.switch.existing')}
</CustomText>
<View
style={{
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
marginTop: StyleConstants.Spacing.M
}}
>
{accounts.map((account, index) => {
return <AccountButton key={index} account={account} />
})}
</View>
{t('me.switch.new')}
</CustomText>
<ComponentInstance scrollViewRef={scrollViewRef} disableHeaderImage goBack />
</View>
<View
style={{
marginTop: StyleConstants.Spacing.S,
paddingTop: StyleConstants.Spacing.M,
borderTopWidth: 1,
borderTopColor: colors.border
}}
>
<CustomText
fontStyle='M'
style={{
textAlign: 'center',
paddingVertical: StyleConstants.Spacing.S,
color: colors.primaryDefault
}}
>
{t('me.switch.existing')}
</CustomText>
<View
style={{
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
marginTop: StyleConstants.Spacing.M
}}
>
{accounts.map((account, index) => {
return <AccountButton key={index} account={account} />
})}
</View>
</ScrollView>
</KeyboardAvoidingView>
</View>
</ModalScrollView>
)
}

View File

@ -1,5 +1,6 @@
import ComponentAccount from '@components/Account'
import { HeaderLeft, HeaderRight } from '@components/Header'
import { ModalScrollView } from '@components/ModalScrollView'
import Selections from '@components/Selections'
import CustomText from '@components/Text'
import apiInstance from '@utils/api/instance'
@ -11,7 +12,7 @@ import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Platform, ScrollView, TextInput, View } from 'react-native'
import { Platform, TextInput, View } from 'react-native'
import { Switch } from 'react-native-gesture-handler'
const TabSharedReport: React.FC<TabSharedStackScreenProps<'Tab-Shared-Report'>> = ({
@ -96,7 +97,7 @@ const TabSharedReport: React.FC<TabSharedStackScreenProps<'Tab-Shared-Report'>>
}, [rulesQuery.data])
return (
<ScrollView>
<ModalScrollView>
<View
style={{
margin: StyleConstants.Spacing.Global.PagePadding,
@ -209,7 +210,7 @@ const TabSharedReport: React.FC<TabSharedStackScreenProps<'Tab-Shared-Report'>>
/>
) : null}
</View>
</ScrollView>
</ModalScrollView>
)
}