This commit is contained in:
xmflsct 2022-12-03 15:50:15 +01:00
parent a3a0bf523f
commit 29fd36a581
14 changed files with 223 additions and 57 deletions

View File

@ -1,5 +1,5 @@
import React from 'react'
import { StyleSheet, View } from 'react-native'
import { View } from 'react-native'
import { StyleConstants } from '@utils/styles/constants'
export interface Props {
@ -7,14 +7,16 @@ export interface Props {
}
const MenuContainer: React.FC<Props> = ({ children }) => {
return <View style={styles.base}>{children}</View>
return (
<View
style={{
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding,
marginBottom: StyleConstants.Spacing.Global.PagePadding
}}
>
{children}
</View>
)
}
const styles = StyleSheet.create({
base: {
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding,
marginBottom: StyleConstants.Spacing.Global.PagePadding
}
})
export default MenuContainer

View File

@ -5,7 +5,7 @@ import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import { ColorDefinitions } from '@utils/styles/themes'
import React, { useMemo } from 'react'
import { View } from 'react-native'
import { Text, View } from 'react-native'
import { Flow } from 'react-native-animated-spinkit'
import { State, Switch, TapGestureHandler } from 'react-native-gesture-handler'
@ -65,6 +65,7 @@ const MenuRow: React.FC<Props> = ({
>
<TapGestureHandler
onHandlerStateChange={async ({ nativeEvent }) => {
if (typeof iconBack !== 'string') return // Let icon back handles the gesture
if (nativeEvent.state === State.ACTIVE && !loading) {
if (screenReaderEnabled && switchOnValueChange) {
switchOnValueChange()
@ -79,12 +80,13 @@ const MenuRow: React.FC<Props> = ({
style={{
flex: 1,
flexDirection: 'row',
paddingTop: StyleConstants.Spacing.S
justifyContent: 'space-between',
marginTop: StyleConstants.Spacing.S
}}
>
<View
style={{
flexGrow: 3,
flex: 3,
flexDirection: 'row',
alignItems: 'center'
}}

View File

@ -28,8 +28,9 @@ const TimelineHeaderAndroid: React.FC<Props> = ({ queryKey, rootQueryKey, status
url: status.url || status.uri
})
const mAccount = menuAccount({
type: 'status',
openChange,
id: status.account.id,
account: status.account,
queryKey
})
const mStatus = menuStatus({ status, queryKey, rootQueryKey })

View File

@ -45,8 +45,9 @@ const TimelineHeaderDefault: React.FC<Props> = ({
copiableContent
})
const mAccount = menuAccount({
type: 'status',
openChange,
id: status.account.id,
account: status.account,
queryKey
})
const mStatus = menuStatus({ status, queryKey, rootQueryKey })

View File

@ -31,8 +31,9 @@ const TimelineHeaderNotification = ({ queryKey, notification }: Props) => {
url: notification.status?.url || notification.status?.uri
})
const mAccount = menuAccount({
type: 'status',
openChange,
id: notification.status?.account.id,
account: notification.status?.account,
queryKey
})
const mStatus = menuStatus({ status: notification.status, queryKey })

View File

@ -1,5 +1,8 @@
import haptics from '@components/haptics'
import { displayMessage } from '@components/Message'
import { useNavigation } from '@react-navigation/native'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { TabSharedStackParamList } from '@utils/navigation/navigators'
import {
QueryKeyRelationship,
useRelationshipMutation,
@ -19,25 +22,29 @@ import { useQueryClient } from 'react-query'
import { useSelector } from 'react-redux'
const menuAccount = ({
type,
openChange,
id,
account,
queryKey,
rootQueryKey
}: {
type: 'status' | 'account' // Where the action is coming from
openChange: boolean
id?: Mastodon.Account['id']
account?: Pick<Mastodon.Account, 'id' | 'username'>
queryKey?: QueryKeyTimeline
rootQueryKey?: QueryKeyTimeline
}): ContextMenu[][] => {
if (!id) return []
if (!account) return []
const navigation =
useNavigation<NativeStackNavigationProp<TabSharedStackParamList, any, undefined>>()
const { theme } = useTheme()
const { t } = useTranslation('componentContextMenu')
const menus: ContextMenu[][] = [[]]
const instanceAccount = useSelector(getInstanceAccount, (prev, next) => prev.id === next.id)
const ownAccount = instanceAccount?.id === id
const ownAccount = instanceAccount?.id === account.id
const [enabled, setEnabled] = useState(openChange)
useEffect(() => {
@ -45,12 +52,12 @@ const menuAccount = ({
setEnabled(true)
}
}, [openChange, enabled])
const { data, isFetching } = useRelationshipQuery({ id, options: { enabled } })
const { data, isFetching } = useRelationshipQuery({ id: account.id, options: { enabled } })
const queryClient = useQueryClient()
const timelineMutation = useTimelineMutation({
onSuccess: (_, params) => {
queryClient.refetchQueries(['Relationship', { id }])
queryClient.refetchQueries(['Relationship', { id: account.id }])
const theParams = params as MutationVarsTimelineUpdateAccountProperty
displayMessage({
theme,
@ -90,7 +97,7 @@ const menuAccount = ({
rootQueryKey && queryClient.invalidateQueries(rootQueryKey)
}
})
const queryKeyRelationship: QueryKeyRelationship = ['Relationship', { id }]
const queryKeyRelationship: QueryKeyRelationship = ['Relationship', { id: account.id }]
const relationshipMutation = useRelationshipMutation({
onSuccess: (res, { payload: { action } }) => {
haptics('Success')
@ -118,14 +125,14 @@ const menuAccount = ({
}
})
if (!ownAccount && Platform.OS !== 'android') {
if (!ownAccount && Platform.OS !== 'android' && type !== 'account') {
menus[0].push({
key: 'account-following',
item: {
onSelect: () =>
data &&
relationshipMutation.mutate({
id,
id: account.id,
type: 'outgoing',
payload: { action: 'follow', state: !data?.requested ? data.following : true }
}),
@ -146,6 +153,17 @@ const menuAccount = ({
})
}
if (!ownAccount) {
menus[0].push({
key: 'account-list',
item: {
onSelect: () => navigation.navigate('Tab-Shared-Account-In-Lists', { account }),
disabled: Platform.OS !== 'android' ? !data || isFetching : false,
destructive: false,
hidden: isFetching ? false : !data?.following
},
title: t('account.inLists'),
icon: 'checklist'
})
menus[0].push({
key: 'account-mute',
item: {
@ -153,7 +171,7 @@ const menuAccount = ({
timelineMutation.mutate({
type: 'updateAccountProperty',
queryKey,
id,
id: account.id,
payload: { property: 'mute', currentValue: data?.muting }
}),
disabled: Platform.OS !== 'android' ? !data || isFetching : false,
@ -176,7 +194,7 @@ const menuAccount = ({
timelineMutation.mutate({
type: 'updateAccountProperty',
queryKey,
id,
id: account.id,
payload: { property: 'block', currentValue: data?.blocking }
}),
disabled: Platform.OS !== 'android' ? !data || isFetching : false,
@ -195,13 +213,13 @@ const menuAccount = ({
timelineMutation.mutate({
type: 'updateAccountProperty',
queryKey,
id,
id: account.id,
payload: { property: 'reports' }
})
timelineMutation.mutate({
type: 'updateAccountProperty',
queryKey,
id,
id: account.id,
payload: { property: 'block', currentValue: false }
})
},

View File

@ -5,7 +5,8 @@
"cancel": "Cancel",
"discard": "Discard",
"continue": "Continue",
"delete": "Delete"
"delete": "Delete",
"done": "Done"
},
"customEmoji": {
"accessibilityLabel": "Custom emoji {{emoji}}"

View File

@ -6,6 +6,7 @@
"action_false": "Follow user",
"action_true": "Unfollow user"
},
"inLists": "Manage user of lists",
"mute": {
"action_false": "Mute user",
"action_true": "Unmute user"

View File

@ -320,6 +320,11 @@
},
"suspended": "Account suspended by the moderators of your server"
},
"accountInLists": {
"name": "Lists of @{{username}}",
"inLists": "In lists",
"notInLists": "Other lists"
},
"attachments": {
"name": "<0 /><1>\"s media</1>"
},

View File

@ -11,7 +11,7 @@ import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, View } from 'react-native'
import { Text, View } from 'react-native'
import { useSharedValue } from 'react-native-reanimated'
import { useIsFetching } from 'react-query'
import * as DropdownMenu from 'zeego/dropdown-menu'
@ -30,21 +30,10 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
const { colors, mode } = useTheme()
const mShare = menuShare({ type: 'account', url: account.url })
const mAccount = menuAccount({ openChange: true, id: account.id })
const mAccount = menuAccount({ type: 'account', openChange: true, account })
useEffect(() => {
navigation.setOptions({
headerRight: () => {
// const shareOnPress = contextMenuShare({
// actions,
// type: 'account',
// url: account.url
// })
// const accountOnPress = contextMenuAccount({
// actions,
// type: 'account',
// id: account.id
// })
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger>
@ -107,7 +96,7 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
const ListHeaderComponent = useMemo(() => {
return (
<>
<View style={[styles.header, { borderBottomColor: colors.border }]}>
<View style={{ borderBottomWidth: 1, borderBottomColor: colors.border }}>
<AccountHeader account={data} />
<AccountInformation account={data} />
{!data?.suspended && fetchedTimeline.current ? (
@ -129,7 +118,10 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
break
}
}}
style={styles.segmentsContainer}
style={{
marginTop: StyleConstants.Spacing.M,
marginHorizontal: StyleConstants.Spacing.Global.PagePadding
}}
/>
) : null}
{data?.suspended ? (
@ -178,14 +170,4 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
)
}
const styles = StyleSheet.create({
header: {
borderBottomWidth: 1
},
segmentsContainer: {
marginTop: StyleConstants.Spacing.M,
marginHorizontal: StyleConstants.Spacing.Global.PagePadding
}
})
export default TabSharedAccount

View File

@ -0,0 +1,119 @@
import Button from '@components/Button'
import haptics from '@components/haptics'
import { HeaderRight } from '@components/Header'
import { MenuRow } from '@components/Menu'
import CustomText from '@components/Text'
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
import { useAccountInListsQuery } from '@utils/queryHooks/account'
import { useListAccountsMutation, useListsQuery } from '@utils/queryHooks/lists'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { SectionList, Text, View } from 'react-native'
const TabSharedAccountInLists: React.FC<
TabSharedStackScreenProps<'Tab-Shared-Account-In-Lists'>
> = ({
navigation,
route: {
params: { account }
}
}) => {
const { colors } = useTheme()
const { t } = useTranslation('screenTabs')
useEffect(() => {
navigation.setOptions({
presentation: 'modal',
title: t('shared.accountInLists.name', { username: account.username }),
headerLeft: () => null,
headerRight: () => {
return (
<HeaderRight
type='text'
content={t('common:buttons.done')}
onPress={() => navigation.pop(1)}
/>
)
}
})
}, [])
const listsQuery = useListsQuery({})
const accountInListsQuery = useAccountInListsQuery({ id: account.id })
const sections = [
{ id: 'in', title: t('shared.accountInLists.inLists'), data: accountInListsQuery.data || [] },
{
id: 'out',
title: t('shared.accountInLists.notInLists'),
data:
listsQuery.data?.filter(
({ id }) => !accountInListsQuery.data?.filter(d => d.id === id).length
) || []
}
]
const listAccountsMutation = useListAccountsMutation({})
return (
<SectionList
style={{ padding: StyleConstants.Spacing.Global.PagePadding }}
sections={sections}
SectionSeparatorComponent={props => {
return props.leadingItem && props.trailingSection ? (
<View style={{ flex: 1, height: StyleConstants.Spacing.Global.PagePadding * 2 }} />
) : null
}}
renderSectionHeader={({ section: { title, data } }) =>
data.length ? (
<CustomText fontStyle='S' style={{ color: colors.secondary }} children={title} />
) : null
}
renderItem={({ index, item, section }) => (
<MenuRow
key={index}
iconFront='List'
content={
<Button
type='icon'
content={section.id === 'in' ? 'Minus' : 'Plus'}
round
disabled={accountInListsQuery.isFetching}
onPress={() => {
switch (section.id) {
case 'in':
listAccountsMutation
.mutateAsync({
type: 'delete',
payload: { id: item.id, accounts: [account.id] }
})
.then(() => {
haptics('Light')
accountInListsQuery.refetch()
})
break
case 'out':
listAccountsMutation
.mutateAsync({
type: 'add',
payload: { id: item.id, accounts: [account.id] }
})
.then(() => {
haptics('Light')
accountInListsQuery.refetch()
})
break
}
}}
/>
}
title={item.title}
/>
)}
></SectionList>
)
}
export default TabSharedAccountInLists

View File

@ -16,6 +16,7 @@ import { debounce } from 'lodash'
import React from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { Platform, TextInput, View } from 'react-native'
import TabSharedAccountInLists from './AccountInLists'
const TabShared = ({ Stack }: { Stack: ReturnType<typeof createNativeStackNavigator> }) => {
const { colors, mode } = useTheme()
@ -48,6 +49,12 @@ const TabShared = ({ Stack }: { Stack: ReturnType<typeof createNativeStackNaviga
}}
/>
<Stack.Screen
key='Tab-Shared-Account-In-Lists'
name='Tab-Shared-Account-In-Lists'
component={TabSharedAccountInLists}
/>
<Stack.Screen
key='Tab-Shared-Attachments'
name='Tab-Shared-Attachments'

View File

@ -92,6 +92,9 @@ export type TabSharedStackParamList = {
'Tab-Shared-Account': {
account: Mastodon.Account | Mastodon.Mention
}
'Tab-Shared-Account-In-Lists': {
account: Pick<Mastodon.Account, 'id' | 'username'>
}
'Tab-Shared-Attachments': { account: Mastodon.Account }
'Tab-Shared-Hashtag': {
hashtag: Mastodon.Tag['name']

View File

@ -4,7 +4,7 @@ import { QueryFunctionContext, useQuery, UseQueryOptions } from 'react-query'
export type QueryKeyAccount = ['Account', { id: Mastodon.Account['id'] }]
const queryFunction = ({ queryKey }: QueryFunctionContext<QueryKeyAccount>) => {
const accountQueryFunction = ({ queryKey }: QueryFunctionContext<QueryKeyAccount>) => {
const { id } = queryKey[1]
return apiInstance<Mastodon.Account>({
@ -20,7 +20,30 @@ const useAccountQuery = ({
options?: UseQueryOptions<Mastodon.Account, AxiosError>
}) => {
const queryKey: QueryKeyAccount = ['Account', { ...queryKeyParams }]
return useQuery(queryKey, queryFunction, options)
return useQuery(queryKey, accountQueryFunction, options)
}
export { useAccountQuery }
/* ----- */
export type QueryKeyAccountInLists = ['AccountInLists', { id: Mastodon.Account['id'] }]
const accountInListsQueryFunction = ({ queryKey }: QueryFunctionContext<QueryKeyAccount>) => {
const { id } = queryKey[1]
return apiInstance<Mastodon.List[]>({
method: 'get',
url: `accounts/${id}/lists`
}).then(res => res.body)
}
const useAccountInListsQuery = ({
options,
...queryKeyParams
}: QueryKeyAccount[1] & {
options?: UseQueryOptions<Mastodon.List[], AxiosError>
}) => {
const queryKey: QueryKeyAccount = ['Account', { ...queryKeyParams }]
return useQuery(queryKey, accountInListsQueryFunction, options)
}
export { useAccountQuery, useAccountInListsQuery }