From 653b588c29a43f15d26074098c083532e1add4b0 Mon Sep 17 00:00:00 2001 From: xmflsct Date: Thu, 26 Jan 2023 23:07:13 +0100 Subject: [PATCH] Fix #613 --- src/components/Filter.tsx | 114 +++++++++++++++++ src/components/Hashtag.tsx | 2 +- src/components/Parse/HTML.tsx | 8 +- src/components/Timeline/Shared/Poll.tsx | 4 +- src/components/contextMenu/hashtag.ts | 97 ++++++++++++++ src/components/contextMenu/status.ts | 16 +++ src/i18n/en/components/contextMenu.json | 19 ++- src/i18n/en/screens/tabs.json | 6 +- src/screens/Tabs/Me/FollowedTags.tsx | 20 +-- src/screens/Tabs/Me/Preferences/Filters.tsx | 103 ++------------- src/screens/Tabs/Shared/AccountInLists.tsx | 26 ++-- src/screens/Tabs/Shared/Filter.tsx | 132 ++++++++++++++++++++ src/screens/Tabs/Shared/Hashtag.tsx | 98 ++++++--------- src/screens/Tabs/Shared/index.tsx | 7 ++ src/utils/navigation/navigators.ts | 5 +- src/utils/queryHooks/filters.ts | 72 ++++++++++- src/utils/queryHooks/tags.ts | 29 ++--- src/utils/queryHooks/timeline.ts | 4 +- 18 files changed, 555 insertions(+), 207 deletions(-) create mode 100644 src/components/Filter.tsx create mode 100644 src/components/contextMenu/hashtag.ts create mode 100644 src/screens/Tabs/Shared/Filter.tsx diff --git a/src/components/Filter.tsx b/src/components/Filter.tsx new file mode 100644 index 00000000..6ed73a2f --- /dev/null +++ b/src/components/Filter.tsx @@ -0,0 +1,114 @@ +import { StyleConstants } from '@utils/styles/constants' +import { useTheme } from '@utils/styles/ThemeManager' +import { Fragment } from 'react' +import { Trans, useTranslation } from 'react-i18next' +import { View } from 'react-native' +import { TouchableNativeFeedback } from 'react-native-gesture-handler' +import Icon from './Icon' +import CustomText from './Text' + +export type Props = { + onPress: () => void + filter: Mastodon.Filter<'v2'> + button?: React.ReactNode +} + +export const Filter: React.FC = ({ onPress, filter, button }) => { + const { t } = useTranslation(['common', 'screenTabs']) + const { colors } = useTheme() + + return ( + + + + + + {filter.expires_at && new Date() > new Date(filter.expires_at) ? ( + + ) : null} + {filter.keywords?.length ? ( + + ) : null} + {filter.keywords?.length && filter.statuses?.length ? ( + + ) : null} + {filter.statuses?.length ? ( + + ) : null} + + + {filter.context.map((c, index) => ( + + + + + ))} + + ]} + /> + } + /> + + {button || ( + + )} + + + ) +} diff --git a/src/components/Hashtag.tsx b/src/components/Hashtag.tsx index 4f84c9e7..355e39d2 100644 --- a/src/components/Hashtag.tsx +++ b/src/components/Hashtag.tsx @@ -22,7 +22,7 @@ const ComponentHashtag: React.FC = ({ const navigation = useNavigation>() const onPress = () => { - navigation.push('Tab-Shared-Hashtag', { hashtag: hashtag.name }) + navigation.push('Tab-Shared-Hashtag', { tag_name: hashtag.name }) } const padding = StyleConstants.Spacing.Global.PagePadding diff --git a/src/components/Parse/HTML.tsx b/src/components/Parse/HTML.tsx index 449fbc09..03f7f8f4 100644 --- a/src/components/Parse/HTML.tsx +++ b/src/components/Parse/HTML.tsx @@ -69,7 +69,7 @@ const ParseHTML: React.FC = ({ const [followedTags] = useAccountStorage.object('followed_tags') - const MAX_ALLOWED_LINES = 30 + const MAX_ALLOWED_LINES = 35 const [totalLines, setTotalLines] = useState() const [expanded, setExpanded] = useState(highlighted) @@ -147,7 +147,7 @@ const ParseHTML: React.FC = ({ tag?.length && !disableDetails && !sameHashtag && - navigation.push('Tab-Shared-Hashtag', { hashtag: tag }) + navigation.push('Tab-Shared-Hashtag', { tag_name: tag }) } children={children} /> @@ -203,9 +203,7 @@ const ParseHTML: React.FC = ({ onPress={async () => { if (!disableDetails) { if (shouldBeTag) { - navigation.push('Tab-Shared-Hashtag', { - hashtag: content.substring(1) - }) + navigation.push('Tab-Shared-Hashtag', { tag_name: content.substring(1) }) } else { await openLink(href, navigation) } diff --git a/src/components/Timeline/Shared/Poll.tsx b/src/components/Timeline/Shared/Poll.tsx index 47f006b8..775cff83 100644 --- a/src/components/Timeline/Shared/Poll.tsx +++ b/src/components/Timeline/Shared/Poll.tsx @@ -137,8 +137,8 @@ const TimelinePoll: React.FC = () => { marginRight: StyleConstants.Spacing.S }} name={ - `${poll.own_votes?.includes(index) ? 'Check' : ''}${ - poll.multiple ? 'Square' : 'Circle' + `${poll.own_votes?.includes(index) ? 'check-' : ''}${ + poll.multiple ? 'square' : 'circle' }` as any } size={StyleConstants.Font.Size.M} diff --git a/src/components/contextMenu/hashtag.ts b/src/components/contextMenu/hashtag.ts new file mode 100644 index 00000000..4b9f08f5 --- /dev/null +++ b/src/components/contextMenu/hashtag.ts @@ -0,0 +1,97 @@ +import haptics from '@components/haptics' +import { displayMessage } from '@components/Message' +import { useNavigation } from '@react-navigation/native' +import { NativeStackNavigationProp } from '@react-navigation/native-stack' +import { useQueryClient } from '@tanstack/react-query' +import { featureCheck } from '@utils/helpers/featureCheck' +import { TabSharedStackParamList } from '@utils/navigation/navigators' +import { QueryKeyFollowedTags, useTagMutation, useTagQuery } from '@utils/queryHooks/tags' +import { QueryKeyTimeline } from '@utils/queryHooks/timeline' +import { useTranslation } from 'react-i18next' + +const menuHashtag = ({ + tag_name, + queryKey +}: { + tag_name: Mastodon.Tag['name'] + queryKey?: QueryKeyTimeline +}): ContextMenu => { + const navigation = + useNavigation>() + const { t } = useTranslation(['common', 'componentContextMenu']) + + const canFollowTags = featureCheck('follow_tags') + + const queryClient = useQueryClient() + + const { + data, + isFetching: tagIsFetching, + refetch: tagRefetch + } = useTagQuery({ tag_name, options: { enabled: canFollowTags } }) + const tagMutation = useTagMutation({ + onSuccess: () => { + haptics('Light') + tagRefetch() + const queryKeyFollowedTags: QueryKeyFollowedTags = ['FollowedTags'] + queryClient.invalidateQueries({ queryKey: queryKeyFollowedTags }) + }, + onError: (err: any, { to }) => { + displayMessage({ + type: 'error', + message: t('common:message.error.message', { + function: t('componentContextMenu:hashtag.follow', { + defaultValue: 'false', + context: to.toString() + }) + }), + ...(err.status && + typeof err.status === 'number' && + err.data && + err.data.error && + typeof err.data.error === 'string' && { + description: err.data.error + }) + }) + } + }) + + const menus: ContextMenu = [] + + menus.push([ + { + type: 'item', + key: 'hashtag-follow', + props: { + onSelect: () => + typeof data?.following === 'boolean' && + tagMutation.mutate({ tag_name, to: !data.following }), + disabled: tagIsFetching, + destructive: false, + hidden: !canFollowTags + }, + title: t('componentContextMenu:hashtag.follow.action', { + defaultValue: 'false', + context: (data?.following || false).toString() + }), + icon: data?.following ? 'rectangle.stack.badge.minus' : 'rectangle.stack.badge.plus' + }, + { + type: 'item', + key: 'hashtag-filter', + props: { + onSelect: () => + navigation.navigate('Tab-Shared-Filter', { source: 'hashtag', tag_name, queryKey }), + disabled: false, + destructive: false, + hidden: !canFollowTags + }, + title: t('componentContextMenu:hashtag.filter.action'), + icon: 'line.3.horizontal.decrease' + } + ]) + + return menus +} + +export default menuHashtag diff --git a/src/components/contextMenu/status.ts b/src/components/contextMenu/status.ts index 5641d38e..8e8a964f 100644 --- a/src/components/contextMenu/status.ts +++ b/src/components/contextMenu/status.ts @@ -231,6 +231,22 @@ const menuStatus = ({ context: (status.pinned || false).toString() }), icon: status.pinned ? 'pin.slash' : 'pin' + }, + { + type: 'item', + key: 'status-filter', + props: { + // @ts-ignore + onSelect: () => navigation.navigate('Tab-Shared-Filter', { source: 'status', status, queryKey }), + disabled: false, + destructive: false, + hidden: !('filtered' in status) + }, + title: t('componentContextMenu:status.filter.action', { + defaultValue: 'false', + context: (!!status.filtered?.length).toString() + }), + icon: status.pinned ? 'rectangle.badge.checkmark' : 'rectangle.badge.xmark' } ]) diff --git a/src/i18n/en/components/contextMenu.json b/src/i18n/en/components/contextMenu.json index f9a4ad28..4494f906 100644 --- a/src/i18n/en/components/contextMenu.json +++ b/src/i18n/en/components/contextMenu.json @@ -6,7 +6,7 @@ "action_false": "Follow user", "action_true": "Unfollow user" }, - "inLists": "Lists containing user", + "inLists": "Lists containing user ...", "showBoosts": { "action_false": "Show user's boosts", "action_true": "Hide users's boosts" @@ -16,12 +16,12 @@ "action_true": "Unmute user" }, "followAs": { - "trigger": "Follow as...", + "trigger": "Follow as ...", "succeed_default": "Now following @{{target}} with @{{source}}", "succeed_locked": "Sent follow request to @{{target}} with {{source}}, pending approval", "failed": "Follow as" }, - "blockReport": "Block and report...", + "blockReport": "Block and report", "block": { "action_false": "Block user", "action_true": "Unblock user", @@ -54,6 +54,15 @@ } } }, + "hashtag": { + "follow": { + "action_false": "Follow", + "action_true": "Unfollow", + }, + "filter": { + "action": "Filter hashtag ..." + } + }, "share": { "status": { "action": "Share toot" @@ -88,6 +97,10 @@ "pin": { "action_false": "Pin toot", "action_true": "Unpin toot" + }, + "filter": { + "action_false": "Filter toot ...", + "action_true": "Manage filters ..." } } } \ No newline at end of file diff --git a/src/i18n/en/screens/tabs.json b/src/i18n/en/screens/tabs.json index 124aa8b4..3c27bd0b 100644 --- a/src/i18n/en/screens/tabs.json +++ b/src/i18n/en/screens/tabs.json @@ -399,9 +399,9 @@ "attachments": { "name": "<0 /><1>'s media" }, - "hashtag": { - "follow": "Follow", - "unfollow": "Unfollow" + "filter": { + "name": "Add to filter", + "existed": "Existed in these filters" }, "history": { "name": "Edit History" diff --git a/src/screens/Tabs/Me/FollowedTags.tsx b/src/screens/Tabs/Me/FollowedTags.tsx index c97c9c99..928a8d37 100644 --- a/src/screens/Tabs/Me/FollowedTags.tsx +++ b/src/screens/Tabs/Me/FollowedTags.tsx @@ -4,7 +4,7 @@ import ComponentHashtag from '@components/Hashtag' import { displayMessage } from '@components/Message' import ComponentSeparator from '@components/Separator' import { TabMeStackScreenProps } from '@utils/navigation/navigators' -import { useFollowedTagsQuery, useTagsMutation } from '@utils/queryHooks/tags' +import { useFollowedTagsQuery, useTagMutation } from '@utils/queryHooks/tags' import { flattenPages } from '@utils/queryHooks/utils' import React, { useEffect } from 'react' import { useTranslation } from 'react-i18next' @@ -13,7 +13,7 @@ import { FlatList } from 'react-native-gesture-handler' const TabMeFollowedTags: React.FC> = ({ navigation }) => { - const { t } = useTranslation(['common', 'screenTabs']) + const { t } = useTranslation(['common', 'screenTabs', 'componentContextMenu']) const { data, fetchNextPage, refetch } = useFollowedTagsQuery() const flattenData = flattenPages(data) @@ -24,7 +24,7 @@ const TabMeFollowedTags: React.FC> } }, [flattenData.length]) - const mutation = useTagsMutation({ + const mutation = useTagMutation({ onSuccess: () => { haptics('Light') refetch() @@ -33,9 +33,10 @@ const TabMeFollowedTags: React.FC> displayMessage({ type: 'error', message: t('common:message.error.message', { - function: to - ? t('screenTabs:shared.hashtag.follow') - : t('screenTabs:shared.hashtag.unfollow') + function: t('componentContextMenu:hashtag.follow.action', { + defaultValue: 'false', + context: to.toString() + }) }), ...(err.status && typeof err.status === 'number' && @@ -58,8 +59,11 @@ const TabMeFollowedTags: React.FC> children={