mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Moving to using zeego
This commit is contained in:
@ -1,175 +0,0 @@
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { useRelationshipQuery } from '@utils/queryHooks/relationship'
|
||||
import {
|
||||
MutationVarsTimelineUpdateAccountProperty,
|
||||
QueryKeyTimeline,
|
||||
useTimelineMutation
|
||||
} from '@utils/queryHooks/timeline'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Platform } from 'react-native'
|
||||
import { ContextMenuAction } from 'react-native-context-menu-view'
|
||||
import { useQueryClient } from 'react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
export interface Props {
|
||||
actions: ContextMenuAction[]
|
||||
type: 'status' | 'account' // Do not need to fetch relationship in timeline
|
||||
queryKey?: QueryKeyTimeline
|
||||
rootQueryKey?: QueryKeyTimeline
|
||||
id: Mastodon.Account['id']
|
||||
}
|
||||
|
||||
const contextMenuAccount = ({ actions, type, queryKey, rootQueryKey, id: accountId }: Props) => {
|
||||
const { theme } = useTheme()
|
||||
const { t } = useTranslation('componentContextMenu')
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const mutation = useTimelineMutation({
|
||||
onSuccess: (_, params) => {
|
||||
queryClient.refetchQueries(['Relationship', { id: accountId }])
|
||||
const theParams = params as MutationVarsTimelineUpdateAccountProperty
|
||||
displayMessage({
|
||||
theme,
|
||||
type: 'success',
|
||||
message: t('common:message.success.message', {
|
||||
function: t(`account.${theParams.payload.property}.action`, {
|
||||
...(theParams.payload.property !== 'reports' && {
|
||||
context: (theParams.payload.currentValue || false).toString()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
onError: (err: any, params) => {
|
||||
const theParams = params as MutationVarsTimelineUpdateAccountProperty
|
||||
displayMessage({
|
||||
theme,
|
||||
type: 'error',
|
||||
message: t('common:message.error.message', {
|
||||
function: t(`account.${theParams.payload.property}.action`, {
|
||||
...(theParams.payload.property !== 'reports' && {
|
||||
context: (theParams.payload.currentValue || false).toString()
|
||||
})
|
||||
})
|
||||
}),
|
||||
...(err.status &&
|
||||
typeof err.status === 'number' &&
|
||||
err.data &&
|
||||
err.data.error &&
|
||||
typeof err.data.error === 'string' && {
|
||||
description: err.data.error
|
||||
})
|
||||
})
|
||||
},
|
||||
onSettled: () => {
|
||||
queryKey && queryClient.invalidateQueries(queryKey)
|
||||
rootQueryKey && queryClient.invalidateQueries(rootQueryKey)
|
||||
}
|
||||
})
|
||||
|
||||
const instanceAccount = useSelector(getInstanceAccount, (prev, next) => prev.id === next.id)
|
||||
const ownAccount = instanceAccount?.id === accountId
|
||||
|
||||
const { data: relationship } = useRelationshipQuery({
|
||||
id: accountId,
|
||||
options: { enabled: type === 'account' }
|
||||
})
|
||||
|
||||
if (!ownAccount) {
|
||||
actions.push({
|
||||
id: 'account-mute',
|
||||
title: t('account.mute.action', {
|
||||
context: (relationship?.muting || false).toString()
|
||||
}),
|
||||
systemIcon: 'eye.slash'
|
||||
})
|
||||
switch (Platform.OS) {
|
||||
case 'ios':
|
||||
actions.push({
|
||||
id: 'account',
|
||||
title: t('account.title'),
|
||||
actions: [
|
||||
{
|
||||
id: 'account-block',
|
||||
title: t('account.block.action', {
|
||||
context: (relationship?.blocking || false).toString()
|
||||
}),
|
||||
systemIcon: 'xmark.circle',
|
||||
destructive: true
|
||||
},
|
||||
{
|
||||
id: 'account-reports',
|
||||
title: t('account.reports.action'),
|
||||
systemIcon: 'flag',
|
||||
destructive: true
|
||||
}
|
||||
]
|
||||
})
|
||||
break
|
||||
default:
|
||||
actions.push(
|
||||
{
|
||||
id: 'account-block',
|
||||
title: t('account.block.action', {
|
||||
context: (relationship?.blocking || false).toString()
|
||||
}),
|
||||
systemIcon: 'xmark.circle',
|
||||
destructive: true
|
||||
},
|
||||
{
|
||||
id: 'account-reports',
|
||||
title: t('account.reports.action'),
|
||||
systemIcon: 'flag',
|
||||
destructive: true
|
||||
}
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return (index: number) => {
|
||||
if (typeof index !== 'number' || !actions[index]) {
|
||||
return // For Android
|
||||
}
|
||||
if (actions[index].id === 'account-mute') {
|
||||
mutation.mutate({
|
||||
type: 'updateAccountProperty',
|
||||
queryKey,
|
||||
id: accountId,
|
||||
payload: { property: 'mute', currentValue: relationship?.muting }
|
||||
})
|
||||
}
|
||||
if (
|
||||
actions[index].id === 'account-block' ||
|
||||
(actions[index].id === 'account' && actions[index].actions?.[0].id === 'account-block')
|
||||
) {
|
||||
mutation.mutate({
|
||||
type: 'updateAccountProperty',
|
||||
queryKey,
|
||||
id: accountId,
|
||||
payload: { property: 'block', currentValue: relationship?.blocking }
|
||||
})
|
||||
}
|
||||
if (
|
||||
actions[index].id === 'account-reports' ||
|
||||
(actions[index].id === 'account' && actions[index].actions?.[0].id === 'account-reports')
|
||||
) {
|
||||
mutation.mutate({
|
||||
type: 'updateAccountProperty',
|
||||
queryKey,
|
||||
id: accountId,
|
||||
payload: { property: 'reports' }
|
||||
})
|
||||
mutation.mutate({
|
||||
type: 'updateAccountProperty',
|
||||
queryKey,
|
||||
id: accountId,
|
||||
payload: { property: 'block', currentValue: false }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default contextMenuAccount
|
@ -3,24 +3,23 @@ import { QueryKeyTimeline, useTimelineMutation } from '@utils/queryHooks/timelin
|
||||
import { getInstanceUrl } from '@utils/slices/instancesSlice'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert, Platform } from 'react-native'
|
||||
import { ContextMenuAction } from 'react-native-context-menu-view'
|
||||
import { Alert } from 'react-native'
|
||||
import { useQueryClient } from 'react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
export interface Props {
|
||||
actions: ContextMenuAction[]
|
||||
status: Mastodon.Status
|
||||
queryKey: QueryKeyTimeline
|
||||
const menuInstance = ({
|
||||
status,
|
||||
queryKey,
|
||||
rootQueryKey
|
||||
}: {
|
||||
status?: Mastodon.Status
|
||||
queryKey?: QueryKeyTimeline
|
||||
rootQueryKey?: QueryKeyTimeline
|
||||
}
|
||||
}): ContextMenu[][] => {
|
||||
if (!status || !queryKey) return []
|
||||
|
||||
const contextMenuInstance = ({ actions, status, queryKey, rootQueryKey }: Props) => {
|
||||
const { t } = useTranslation('componentContextMenu')
|
||||
const { theme } = useTheme()
|
||||
|
||||
const currentInstance = useSelector(getInstanceUrl)
|
||||
const instance = status?.uri && status.uri.split(new RegExp(/\/\/(.*?)\//))[1]
|
||||
const { t } = useTranslation('componentContextMenu')
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const mutation = useTimelineMutation({
|
||||
@ -37,61 +36,48 @@ const contextMenuInstance = ({ actions, status, queryKey, rootQueryKey }: Props)
|
||||
}
|
||||
})
|
||||
|
||||
const menus: ContextMenu[][] = []
|
||||
|
||||
const currentInstance = useSelector(getInstanceUrl)
|
||||
const instance = status.uri && status.uri.split(new RegExp(/\/\/(.*?)\//))[1]
|
||||
|
||||
if (currentInstance !== instance && instance) {
|
||||
switch (Platform.OS) {
|
||||
case 'ios':
|
||||
actions.push({
|
||||
id: 'instance',
|
||||
title: t('instance.title'),
|
||||
actions: [
|
||||
{
|
||||
id: 'instance-block',
|
||||
title: t('instance.block.action', { instance }),
|
||||
destructive: true
|
||||
}
|
||||
]
|
||||
})
|
||||
break
|
||||
default:
|
||||
actions.push({
|
||||
id: 'instance-block',
|
||||
title: t('instance.block.action', { instance }),
|
||||
destructive: true
|
||||
})
|
||||
break
|
||||
}
|
||||
menus.push([
|
||||
{
|
||||
key: 'instance-block',
|
||||
item: {
|
||||
onSelect: () =>
|
||||
Alert.alert(
|
||||
t('instance.block.alert.title', { instance }),
|
||||
t('instance.block.alert.message'),
|
||||
[
|
||||
{
|
||||
text: t('instance.block.alert.buttons.confirm'),
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
mutation.mutate({
|
||||
type: 'domainBlock',
|
||||
queryKey,
|
||||
domain: instance
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
text: t('common:buttons.cancel')
|
||||
}
|
||||
]
|
||||
),
|
||||
disabled: false,
|
||||
destructive: true,
|
||||
hidden: false
|
||||
},
|
||||
title: t('instance.block.action', { instance }),
|
||||
icon: ''
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
return (index: number) => {
|
||||
if (typeof index !== 'number' || !actions[index]) {
|
||||
return // For Android
|
||||
}
|
||||
if (
|
||||
actions[index].id === 'instance-block' ||
|
||||
(actions[index].id === 'instance' && actions[index].actions?.[0].id === 'instance-block')
|
||||
) {
|
||||
Alert.alert(
|
||||
t('instance.block.alert.title', { instance }),
|
||||
t('instance.block.alert.message'),
|
||||
[
|
||||
{
|
||||
text: t('instance.block.alert.buttons.confirm'),
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
mutation.mutate({
|
||||
type: 'domainBlock',
|
||||
queryKey,
|
||||
domain: instance
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
text: t('common:buttons.cancel')
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
return menus
|
||||
}
|
||||
|
||||
export default contextMenuInstance
|
||||
export default menuInstance
|
||||
|
@ -3,59 +3,74 @@ import Clipboard from '@react-native-clipboard/clipboard'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Platform, Share } from 'react-native'
|
||||
import { ContextMenuAction } from 'react-native-context-menu-view'
|
||||
|
||||
export interface Props {
|
||||
copiableContent?: React.MutableRefObject<{
|
||||
content?: string | undefined
|
||||
complete: boolean
|
||||
}>
|
||||
actions: ContextMenuAction[]
|
||||
type: 'status' | 'account'
|
||||
url: string
|
||||
}
|
||||
const menuShare = (
|
||||
params:
|
||||
| {
|
||||
visibility?: Mastodon.Status['visibility']
|
||||
copiableContent?: React.MutableRefObject<{
|
||||
content?: string | undefined
|
||||
complete: boolean
|
||||
}>
|
||||
type: 'status'
|
||||
url?: string
|
||||
}
|
||||
| {
|
||||
type: 'account'
|
||||
url: string
|
||||
}
|
||||
): ContextMenu[][] => {
|
||||
if (params.type === 'status' && params.visibility === 'direct') return []
|
||||
|
||||
const contextMenuShare = ({ copiableContent, actions, type, url }: Props) => {
|
||||
const { theme } = useTheme()
|
||||
const { t } = useTranslation('componentContextMenu')
|
||||
|
||||
actions.push({
|
||||
id: 'share',
|
||||
title: t(`share.${type}.action`),
|
||||
systemIcon: 'square.and.arrow.up'
|
||||
})
|
||||
Platform.OS !== 'android' &&
|
||||
type === 'status' &&
|
||||
actions.push({
|
||||
id: 'copy',
|
||||
title: t(`copy.action`),
|
||||
systemIcon: 'doc.on.doc',
|
||||
disabled: !copiableContent?.current.content?.length
|
||||
const menus: ContextMenu[][] = [[]]
|
||||
|
||||
if (params.url) {
|
||||
const url = params.url
|
||||
menus[0].push({
|
||||
key: 'share',
|
||||
item: {
|
||||
onSelect: () => {
|
||||
switch (Platform.OS) {
|
||||
case 'ios':
|
||||
Share.share({ url })
|
||||
break
|
||||
case 'android':
|
||||
Share.share({ message: url })
|
||||
break
|
||||
}
|
||||
},
|
||||
disabled: false,
|
||||
destructive: false,
|
||||
hidden: false
|
||||
},
|
||||
title: t(`share.${params.type}.action`),
|
||||
icon: 'square.and.arrow.up'
|
||||
})
|
||||
}
|
||||
if (params.type === 'status' && Platform.OS === 'ios')
|
||||
menus[0].push({
|
||||
key: 'copy',
|
||||
item: {
|
||||
onSelect: () => {
|
||||
Clipboard.setString(params.copiableContent?.current.content || '')
|
||||
displayMessage({
|
||||
theme,
|
||||
type: 'success',
|
||||
message: t(`copy.succeed`)
|
||||
})
|
||||
},
|
||||
disabled: false,
|
||||
destructive: false,
|
||||
hidden: !params.copiableContent?.current.content?.length
|
||||
},
|
||||
title: t('copy.action'),
|
||||
icon: 'doc.on.doc'
|
||||
})
|
||||
|
||||
return (index: number) => {
|
||||
if (typeof index !== 'number' || !actions[index]) {
|
||||
return // For Android
|
||||
}
|
||||
if (actions[index].id === 'copy') {
|
||||
Clipboard.setString(copiableContent?.current.content || '')
|
||||
displayMessage({
|
||||
theme,
|
||||
type: 'success',
|
||||
message: t(`copy.succeed`)
|
||||
})
|
||||
}
|
||||
if (actions[index].id === 'share') {
|
||||
switch (Platform.OS) {
|
||||
case 'ios':
|
||||
Share.share({ url })
|
||||
break
|
||||
case 'android':
|
||||
Share.share({ message: url })
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return menus
|
||||
}
|
||||
|
||||
export default contextMenuShare
|
||||
export default menuShare
|
||||
|
@ -12,18 +12,20 @@ import { checkInstanceFeature, getInstanceAccount } from '@utils/slices/instance
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert } from 'react-native'
|
||||
import { ContextMenuAction } from 'react-native-context-menu-view'
|
||||
import { useQueryClient } from 'react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
export interface Props {
|
||||
actions: ContextMenuAction[]
|
||||
status: Mastodon.Status
|
||||
queryKey: QueryKeyTimeline
|
||||
const menuStatus = ({
|
||||
status,
|
||||
queryKey,
|
||||
rootQueryKey
|
||||
}: {
|
||||
status?: Mastodon.Status
|
||||
queryKey?: QueryKeyTimeline
|
||||
rootQueryKey?: QueryKeyTimeline
|
||||
}
|
||||
}): ContextMenu[][] => {
|
||||
if (!status || !queryKey) return []
|
||||
|
||||
const contextMenuStatus = ({ actions, status, queryKey, rootQueryKey }: Props) => {
|
||||
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList, 'Screen-Tabs'>>()
|
||||
const { theme } = useTheme()
|
||||
const { t } = useTranslation('componentContextMenu')
|
||||
@ -53,85 +55,19 @@ const contextMenuStatus = ({ actions, status, queryKey, rootQueryKey }: Props) =
|
||||
}
|
||||
})
|
||||
|
||||
const menus: ContextMenu[][] = []
|
||||
|
||||
const instanceAccount = useSelector(getInstanceAccount, (prev, next) => prev.id === next.id)
|
||||
const ownAccount = instanceAccount?.id === status?.account?.id
|
||||
const ownAccount = instanceAccount?.id === status.account?.id
|
||||
|
||||
const canEditPost = useSelector(checkInstanceFeature('edit_post'))
|
||||
|
||||
if (ownAccount) {
|
||||
const accountMenuItems: ContextMenuAction[] = [
|
||||
menus.push([
|
||||
{
|
||||
id: 'status-delete',
|
||||
title: t('status.delete.action'),
|
||||
systemIcon: 'trash',
|
||||
destructive: true
|
||||
},
|
||||
{
|
||||
id: 'status-delete-edit',
|
||||
title: t('status.deleteEdit.action'),
|
||||
systemIcon: 'pencil.and.outline',
|
||||
destructive: true
|
||||
},
|
||||
{
|
||||
id: 'status-mute',
|
||||
title: t('status.mute.action', {
|
||||
context: (status.muted || false).toString()
|
||||
}),
|
||||
systemIcon: status.muted ? 'speaker' : 'speaker.slash'
|
||||
}
|
||||
]
|
||||
|
||||
const canEditPost = useSelector(checkInstanceFeature('edit_post'))
|
||||
if (canEditPost) {
|
||||
accountMenuItems.unshift({
|
||||
id: 'status-edit',
|
||||
title: t('status.edit.action'),
|
||||
systemIcon: 'square.and.pencil'
|
||||
})
|
||||
}
|
||||
|
||||
if (status.visibility === 'public' || status.visibility === 'unlisted') {
|
||||
accountMenuItems.push({
|
||||
id: 'status-pin',
|
||||
title: t('status.pin.action', {
|
||||
context: (status.pinned || false).toString()
|
||||
}),
|
||||
systemIcon: status.pinned ? 'pin.slash' : 'pin'
|
||||
})
|
||||
}
|
||||
|
||||
actions.push(...accountMenuItems)
|
||||
}
|
||||
|
||||
return async (index: number) => {
|
||||
if (typeof index !== 'number' || !actions[index]) {
|
||||
return // For Android
|
||||
}
|
||||
if (actions[index].id === 'status-delete') {
|
||||
Alert.alert(t('status.delete.alert.title'), t('status.delete.alert.message'), [
|
||||
{
|
||||
text: t('status.delete.alert.buttons.confirm'),
|
||||
style: 'destructive',
|
||||
onPress: async () => {
|
||||
mutation.mutate({
|
||||
type: 'deleteItem',
|
||||
source: 'statuses',
|
||||
queryKey,
|
||||
rootQueryKey,
|
||||
id: status.id
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
text: t('common:buttons.cancel'),
|
||||
style: 'default'
|
||||
}
|
||||
])
|
||||
}
|
||||
if (actions[index].id === 'status-delete-edit') {
|
||||
Alert.alert(t('status.deleteEdit.alert.title'), t('status.deleteEdit.alert.message'), [
|
||||
{
|
||||
text: t('status.deleteEdit.alert.buttons.confirm'),
|
||||
style: 'destructive',
|
||||
onPress: async () => {
|
||||
key: 'status-edit',
|
||||
item: {
|
||||
onSelect: async () => {
|
||||
let replyToStatus: Mastodon.Status | undefined = undefined
|
||||
if (status.in_reply_to_id) {
|
||||
replyToStatus = await apiInstance<Mastodon.Status>({
|
||||
@ -139,87 +75,166 @@ const contextMenuStatus = ({ actions, status, queryKey, rootQueryKey }: Props) =
|
||||
url: `statuses/${status.in_reply_to_id}`
|
||||
}).then(res => res.body)
|
||||
}
|
||||
mutation
|
||||
.mutateAsync({
|
||||
type: 'deleteItem',
|
||||
source: 'statuses',
|
||||
apiInstance<{
|
||||
id: Mastodon.Status['id']
|
||||
text: NonNullable<Mastodon.Status['text']>
|
||||
spoiler_text: Mastodon.Status['spoiler_text']
|
||||
}>({
|
||||
method: 'get',
|
||||
url: `statuses/${status.id}/source`
|
||||
}).then(res => {
|
||||
navigation.navigate('Screen-Compose', {
|
||||
type: 'edit',
|
||||
incomingStatus: {
|
||||
...status,
|
||||
text: res.body.text,
|
||||
spoiler_text: res.body.spoiler_text
|
||||
},
|
||||
...(replyToStatus && { replyToStatus }),
|
||||
queryKey,
|
||||
id: status.id
|
||||
rootQueryKey
|
||||
})
|
||||
.then(res => {
|
||||
navigation.navigate('Screen-Compose', {
|
||||
type: 'deleteEdit',
|
||||
incomingStatus: res.body as Mastodon.Status,
|
||||
...(replyToStatus && { replyToStatus }),
|
||||
queryKey
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
text: t('common:buttons.cancel')
|
||||
}
|
||||
])
|
||||
}
|
||||
if (actions[index].id === 'status-mute') {
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
rootQueryKey,
|
||||
id: status.id,
|
||||
payload: {
|
||||
property: 'muted',
|
||||
currentValue: status.muted,
|
||||
propertyCount: undefined,
|
||||
countValue: undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
if (actions[index].id === 'status-edit') {
|
||||
let replyToStatus: Mastodon.Status | undefined = undefined
|
||||
if (status.in_reply_to_id) {
|
||||
replyToStatus = await apiInstance<Mastodon.Status>({
|
||||
method: 'get',
|
||||
url: `statuses/${status.in_reply_to_id}`
|
||||
}).then(res => res.body)
|
||||
}
|
||||
apiInstance<{
|
||||
id: Mastodon.Status['id']
|
||||
text: NonNullable<Mastodon.Status['text']>
|
||||
spoiler_text: Mastodon.Status['spoiler_text']
|
||||
}>({
|
||||
method: 'get',
|
||||
url: `statuses/${status.id}/source`
|
||||
}).then(res => {
|
||||
navigation.navigate('Screen-Compose', {
|
||||
type: 'edit',
|
||||
incomingStatus: {
|
||||
...status,
|
||||
text: res.body.text,
|
||||
spoiler_text: res.body.spoiler_text
|
||||
})
|
||||
},
|
||||
...(replyToStatus && { replyToStatus }),
|
||||
queryKey,
|
||||
rootQueryKey
|
||||
})
|
||||
})
|
||||
}
|
||||
if (actions[index].id === 'status-pin') {
|
||||
// Also note that reblogs cannot be pinned.
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
rootQueryKey,
|
||||
id: status.id,
|
||||
payload: {
|
||||
property: 'pinned',
|
||||
currentValue: status.pinned,
|
||||
propertyCount: undefined,
|
||||
countValue: undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
disabled: false,
|
||||
destructive: false,
|
||||
hidden: !canEditPost
|
||||
},
|
||||
title: t('status.edit.action'),
|
||||
icon: 'square.and.pencil'
|
||||
},
|
||||
{
|
||||
key: 'status-delete-edit',
|
||||
item: {
|
||||
onSelect: () =>
|
||||
Alert.alert(t('status.deleteEdit.alert.title'), t('status.deleteEdit.alert.message'), [
|
||||
{
|
||||
text: t('status.deleteEdit.alert.buttons.confirm'),
|
||||
style: 'destructive',
|
||||
onPress: async () => {
|
||||
let replyToStatus: Mastodon.Status | undefined = undefined
|
||||
if (status.in_reply_to_id) {
|
||||
replyToStatus = await apiInstance<Mastodon.Status>({
|
||||
method: 'get',
|
||||
url: `statuses/${status.in_reply_to_id}`
|
||||
}).then(res => res.body)
|
||||
}
|
||||
mutation
|
||||
.mutateAsync({
|
||||
type: 'deleteItem',
|
||||
source: 'statuses',
|
||||
queryKey,
|
||||
id: status.id
|
||||
})
|
||||
.then(res => {
|
||||
navigation.navigate('Screen-Compose', {
|
||||
type: 'deleteEdit',
|
||||
incomingStatus: res.body as Mastodon.Status,
|
||||
...(replyToStatus && { replyToStatus }),
|
||||
queryKey
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
text: t('common:buttons.cancel')
|
||||
}
|
||||
]),
|
||||
disabled: false,
|
||||
destructive: true,
|
||||
hidden: false
|
||||
},
|
||||
title: t('status.deleteEdit.action'),
|
||||
icon: 'pencil.and.outline'
|
||||
},
|
||||
{
|
||||
key: 'status-delete',
|
||||
item: {
|
||||
onSelect: () =>
|
||||
Alert.alert(t('status.delete.alert.title'), t('status.delete.alert.message'), [
|
||||
{
|
||||
text: t('status.delete.alert.buttons.confirm'),
|
||||
style: 'destructive',
|
||||
onPress: async () => {
|
||||
mutation.mutate({
|
||||
type: 'deleteItem',
|
||||
source: 'statuses',
|
||||
queryKey,
|
||||
rootQueryKey,
|
||||
id: status.id
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
text: t('common:buttons.cancel'),
|
||||
style: 'default'
|
||||
}
|
||||
]),
|
||||
disabled: false,
|
||||
destructive: true,
|
||||
hidden: false
|
||||
},
|
||||
title: t('status.delete.action'),
|
||||
icon: 'trash'
|
||||
}
|
||||
])
|
||||
|
||||
menus.push([
|
||||
{
|
||||
key: 'status-mute',
|
||||
item: {
|
||||
onSelect: () =>
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
rootQueryKey,
|
||||
id: status.id,
|
||||
payload: {
|
||||
property: 'muted',
|
||||
currentValue: status.muted,
|
||||
propertyCount: undefined,
|
||||
countValue: undefined
|
||||
}
|
||||
}),
|
||||
disabled: false,
|
||||
destructive: false,
|
||||
hidden: false
|
||||
},
|
||||
title: t('status.mute.action', {
|
||||
context: (status.muted || false).toString()
|
||||
}),
|
||||
icon: status.muted ? 'speaker' : 'speaker.slash'
|
||||
},
|
||||
{
|
||||
key: 'status-pin',
|
||||
item: {
|
||||
onSelect: () =>
|
||||
// Also note that reblogs cannot be pinned.
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
rootQueryKey,
|
||||
id: status.id,
|
||||
payload: {
|
||||
property: 'pinned',
|
||||
currentValue: status.pinned,
|
||||
propertyCount: undefined,
|
||||
countValue: undefined
|
||||
}
|
||||
}),
|
||||
disabled: false,
|
||||
destructive: false,
|
||||
hidden: status.visibility !== 'public' && status.visibility !== 'unlisted'
|
||||
},
|
||||
title: t('status.pin.action', {
|
||||
context: (status.pinned || false).toString()
|
||||
}),
|
||||
icon: status.pinned ? 'pin.slash' : 'pin'
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
return menus
|
||||
}
|
||||
|
||||
export default contextMenuStatus
|
||||
export default menuStatus
|
||||
|
@ -1,10 +1,12 @@
|
||||
import menuInstance from '@components/contextMenu/instance'
|
||||
import menuShare from '@components/contextMenu/share'
|
||||
import menuStatus from '@components/contextMenu/status'
|
||||
import TimelineActioned from '@components/Timeline/Shared/Actioned'
|
||||
import TimelineActions from '@components/Timeline/Shared/Actions'
|
||||
import TimelineAttachment from '@components/Timeline/Shared/Attachment'
|
||||
import TimelineAvatar from '@components/Timeline/Shared/Avatar'
|
||||
import TimelineCard from '@components/Timeline/Shared/Card'
|
||||
import TimelineContent from '@components/Timeline/Shared/Content'
|
||||
// @ts-ignore
|
||||
import TimelineHeaderDefault from '@components/Timeline/Shared/HeaderDefault'
|
||||
import TimelinePoll from '@components/Timeline/Shared/Poll'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
@ -18,10 +20,11 @@ import { uniqBy } from 'lodash'
|
||||
import React, { useRef } from 'react'
|
||||
import { Pressable, StyleProp, View, ViewStyle } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import TimelineContextMenu from './Shared/ContextMenu'
|
||||
import * as ContextMenu from 'zeego/context-menu'
|
||||
import TimelineFeedback from './Shared/Feedback'
|
||||
import TimelineFiltered, { shouldFilter } from './Shared/Filtered'
|
||||
import TimelineFullConversation from './Shared/FullConversation'
|
||||
import TimelineHeaderAndroid from './Shared/HeaderAndroid'
|
||||
import TimelineTranslate from './Shared/Translate'
|
||||
|
||||
export interface Props {
|
||||
@ -89,8 +92,10 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
/>
|
||||
<TimelineHeaderDefault
|
||||
queryKey={disableOnPress ? undefined : queryKey}
|
||||
rootQueryKey={rootQueryKey}
|
||||
status={actualStatus}
|
||||
highlighted={highlighted}
|
||||
copiableContent={copiableContent}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@ -149,24 +154,71 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
</>
|
||||
)
|
||||
|
||||
const mShare = menuShare({
|
||||
visibility: actualStatus.visibility,
|
||||
type: 'status',
|
||||
url: actualStatus.url || actualStatus.uri,
|
||||
copiableContent
|
||||
})
|
||||
const mStatus = menuStatus({ status: actualStatus, queryKey, rootQueryKey })
|
||||
const mInstance = menuInstance({ status: actualStatus, queryKey, rootQueryKey })
|
||||
|
||||
return disableOnPress ? (
|
||||
<View style={mainStyle}>{main()}</View>
|
||||
) : (
|
||||
<TimelineContextMenu
|
||||
copiableContent={copiableContent}
|
||||
status={actualStatus}
|
||||
queryKey={queryKey}
|
||||
rootQueryKey={rootQueryKey}
|
||||
>
|
||||
<Pressable
|
||||
accessible={highlighted ? false : true}
|
||||
style={mainStyle}
|
||||
onPress={onPress}
|
||||
onLongPress={() => {}}
|
||||
>
|
||||
{main()}
|
||||
</Pressable>
|
||||
</TimelineContextMenu>
|
||||
<>
|
||||
<ContextMenu.Root>
|
||||
<ContextMenu.Trigger>
|
||||
<Pressable
|
||||
accessible={highlighted ? false : true}
|
||||
style={mainStyle}
|
||||
onPress={onPress}
|
||||
onLongPress={() => {}}
|
||||
children={main()}
|
||||
/>
|
||||
</ContextMenu.Trigger>
|
||||
|
||||
<ContextMenu.Content>
|
||||
{mShare.map((mGroup, index) => (
|
||||
<ContextMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<ContextMenu.Item key={menu.key} {...menu.item}>
|
||||
<ContextMenu.ItemTitle children={menu.title} />
|
||||
<ContextMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</ContextMenu.Item>
|
||||
))}
|
||||
</ContextMenu.Group>
|
||||
))}
|
||||
|
||||
{mStatus.map((mGroup, index) => (
|
||||
<ContextMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<ContextMenu.Item key={menu.key} {...menu.item}>
|
||||
<ContextMenu.ItemTitle children={menu.title} />
|
||||
<ContextMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</ContextMenu.Item>
|
||||
))}
|
||||
</ContextMenu.Group>
|
||||
))}
|
||||
|
||||
{mInstance.map((mGroup, index) => (
|
||||
<ContextMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<ContextMenu.Item key={menu.key} {...menu.item}>
|
||||
<ContextMenu.ItemTitle children={menu.title} />
|
||||
<ContextMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</ContextMenu.Item>
|
||||
))}
|
||||
</ContextMenu.Group>
|
||||
))}
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Root>
|
||||
<TimelineHeaderAndroid
|
||||
queryKey={disableOnPress ? undefined : queryKey}
|
||||
rootQueryKey={rootQueryKey}
|
||||
status={actualStatus}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
import menuInstance from '@components/contextMenu/instance'
|
||||
import menuShare from '@components/contextMenu/share'
|
||||
import menuStatus from '@components/contextMenu/status'
|
||||
import TimelineActioned from '@components/Timeline/Shared/Actioned'
|
||||
import TimelineActions from '@components/Timeline/Shared/Actions'
|
||||
import TimelineAttachment from '@components/Timeline/Shared/Attachment'
|
||||
import TimelineAvatar from '@components/Timeline/Shared/Avatar'
|
||||
import TimelineCard from '@components/Timeline/Shared/Card'
|
||||
import TimelineContent from '@components/Timeline/Shared/Content'
|
||||
// @ts-ignore
|
||||
import TimelineHeaderNotification from '@components/Timeline/Shared/HeaderNotification'
|
||||
import TimelinePoll from '@components/Timeline/Shared/Poll'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
@ -16,11 +18,12 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { uniqBy } from 'lodash'
|
||||
import React, { useCallback, useRef } from 'react'
|
||||
import { Platform, Pressable, View } from 'react-native'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import { useSelector } from 'react-redux'
|
||||
import TimelineContextMenu from './Shared/ContextMenu'
|
||||
import * as ContextMenu from 'zeego/context-menu'
|
||||
import TimelineFiltered, { shouldFilter } from './Shared/Filtered'
|
||||
import TimelineFullConversation from './Shared/FullConversation'
|
||||
import TimelineHeaderAndroid from './Shared/HeaderAndroid'
|
||||
|
||||
export interface Props {
|
||||
notification: Mastodon.Notification
|
||||
@ -136,36 +139,68 @@ const TimelineNotifications: React.FC<Props> = ({
|
||||
)
|
||||
}
|
||||
|
||||
return Platform.OS === 'android' ? (
|
||||
<Pressable
|
||||
style={{
|
||||
padding: StyleConstants.Spacing.Global.PagePadding,
|
||||
backgroundColor: colors.backgroundDefault,
|
||||
paddingBottom: notification.status ? 0 : StyleConstants.Spacing.Global.PagePadding
|
||||
}}
|
||||
onPress={onPress}
|
||||
onLongPress={() => {}}
|
||||
>
|
||||
{main()}
|
||||
</Pressable>
|
||||
) : (
|
||||
<TimelineContextMenu
|
||||
copiableContent={copiableContent}
|
||||
status={notification.status}
|
||||
queryKey={queryKey}
|
||||
>
|
||||
<Pressable
|
||||
style={{
|
||||
padding: StyleConstants.Spacing.Global.PagePadding,
|
||||
backgroundColor: colors.backgroundDefault,
|
||||
paddingBottom: notification.status ? 0 : StyleConstants.Spacing.Global.PagePadding
|
||||
}}
|
||||
onPress={onPress}
|
||||
onLongPress={() => {}}
|
||||
>
|
||||
{main()}
|
||||
</Pressable>
|
||||
</TimelineContextMenu>
|
||||
const mShare = menuShare({
|
||||
visibility: notification.status?.visibility,
|
||||
type: 'status',
|
||||
url: notification.status?.url || notification.status?.uri,
|
||||
copiableContent
|
||||
})
|
||||
const mStatus = menuStatus({ status: notification.status, queryKey })
|
||||
const mInstance = menuInstance({ status: notification.status, queryKey })
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContextMenu.Root>
|
||||
<ContextMenu.Trigger>
|
||||
<Pressable
|
||||
style={{
|
||||
padding: StyleConstants.Spacing.Global.PagePadding,
|
||||
backgroundColor: colors.backgroundDefault,
|
||||
paddingBottom: notification.status ? 0 : StyleConstants.Spacing.Global.PagePadding
|
||||
}}
|
||||
onPress={onPress}
|
||||
onLongPress={() => {}}
|
||||
children={main()}
|
||||
/>
|
||||
</ContextMenu.Trigger>
|
||||
|
||||
<ContextMenu.Content>
|
||||
{mShare.map((mGroup, index) => (
|
||||
<ContextMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<ContextMenu.Item key={menu.key} {...menu.item}>
|
||||
<ContextMenu.ItemTitle children={menu.title} />
|
||||
<ContextMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</ContextMenu.Item>
|
||||
))}
|
||||
</ContextMenu.Group>
|
||||
))}
|
||||
|
||||
{mStatus.map((mGroup, index) => (
|
||||
<ContextMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<ContextMenu.Item key={menu.key} {...menu.item}>
|
||||
<ContextMenu.ItemTitle children={menu.title} />
|
||||
<ContextMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</ContextMenu.Item>
|
||||
))}
|
||||
</ContextMenu.Group>
|
||||
))}
|
||||
|
||||
{mInstance.map((mGroup, index) => (
|
||||
<ContextMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<ContextMenu.Item key={menu.key} {...menu.item}>
|
||||
<ContextMenu.ItemTitle children={menu.title} />
|
||||
<ContextMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</ContextMenu.Item>
|
||||
))}
|
||||
</ContextMenu.Group>
|
||||
))}
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Root>
|
||||
<TimelineHeaderAndroid queryKey={queryKey} status={notification.status} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,84 +0,0 @@
|
||||
import contextMenuAccount from '@components/ContextMenu/account'
|
||||
import contextMenuInstance from '@components/ContextMenu/instance'
|
||||
import contextMenuShare from '@components/ContextMenu/share'
|
||||
import contextMenuStatus from '@components/ContextMenu/status'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import React from 'react'
|
||||
import { createContext } from 'react'
|
||||
import { Platform } from 'react-native'
|
||||
import ContextMenu, { ContextMenuAction, ContextMenuProps } from 'react-native-context-menu-view'
|
||||
|
||||
export interface Props {
|
||||
copiableContent: React.MutableRefObject<{
|
||||
content: string
|
||||
complete: boolean
|
||||
}>
|
||||
status?: Mastodon.Status
|
||||
queryKey?: QueryKeyTimeline
|
||||
rootQueryKey?: QueryKeyTimeline
|
||||
}
|
||||
|
||||
export const ContextMenuContext = createContext<ContextMenuAction[]>([])
|
||||
|
||||
const TimelineContextMenu: React.FC<Props & ContextMenuProps> = ({
|
||||
children,
|
||||
copiableContent,
|
||||
status,
|
||||
queryKey,
|
||||
rootQueryKey,
|
||||
...props
|
||||
}) => {
|
||||
if (!status || !queryKey || Platform.OS === 'android') {
|
||||
return <>{children}</>
|
||||
}
|
||||
|
||||
const actions: ContextMenuAction[] = []
|
||||
|
||||
const shareOnPress =
|
||||
status.visibility !== 'direct'
|
||||
? contextMenuShare({
|
||||
copiableContent,
|
||||
actions,
|
||||
type: 'status',
|
||||
url: status.url || status.uri
|
||||
})
|
||||
: null
|
||||
const statusOnPress = contextMenuStatus({
|
||||
actions,
|
||||
status,
|
||||
queryKey,
|
||||
rootQueryKey
|
||||
})
|
||||
const accountOnPress = status?.account?.id
|
||||
? contextMenuAccount({
|
||||
actions,
|
||||
type: 'status',
|
||||
queryKey,
|
||||
rootQueryKey,
|
||||
id: status.account.id
|
||||
})
|
||||
: null
|
||||
const instanceOnPress = contextMenuInstance({
|
||||
actions,
|
||||
status,
|
||||
queryKey,
|
||||
rootQueryKey
|
||||
})
|
||||
|
||||
return (
|
||||
<ContextMenuContext.Provider value={actions}>
|
||||
<ContextMenu
|
||||
actions={actions}
|
||||
onPress={({ nativeEvent: { index } }) => {
|
||||
for (const on of [shareOnPress, statusOnPress, accountOnPress, instanceOnPress]) {
|
||||
on && on(index)
|
||||
}
|
||||
}}
|
||||
children={children}
|
||||
{...props}
|
||||
/>
|
||||
</ContextMenuContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineContextMenu
|
103
src/components/Timeline/Shared/HeaderAndroid.tsx
Normal file
103
src/components/Timeline/Shared/HeaderAndroid.tsx
Normal file
@ -0,0 +1,103 @@
|
||||
import menuAccount from '@components/contextMenu/account'
|
||||
import menuInstance from '@components/contextMenu/instance'
|
||||
import menuShare from '@components/contextMenu/share'
|
||||
import menuStatus from '@components/contextMenu/status'
|
||||
import Icon from '@components/Icon'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useState } from 'react'
|
||||
import { Platform, View } from 'react-native'
|
||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||
|
||||
export interface Props {
|
||||
queryKey?: QueryKeyTimeline
|
||||
rootQueryKey?: QueryKeyTimeline
|
||||
status?: Mastodon.Status
|
||||
}
|
||||
|
||||
const TimelineHeaderAndroid: React.FC<Props> = ({ queryKey, rootQueryKey, status }) => {
|
||||
if (Platform.OS !== 'android' || !status) return null
|
||||
|
||||
const { colors } = useTheme()
|
||||
|
||||
const [openChange, setOpenChange] = useState(false)
|
||||
const mShare = menuShare({
|
||||
visibility: status.visibility,
|
||||
type: 'status',
|
||||
url: status.url || status.uri
|
||||
})
|
||||
const mAccount = menuAccount({
|
||||
openChange,
|
||||
id: status.account.id,
|
||||
queryKey
|
||||
})
|
||||
const mStatus = menuStatus({ status, queryKey, rootQueryKey })
|
||||
const mInstance = menuInstance({ status, queryKey, rootQueryKey })
|
||||
|
||||
return (
|
||||
<View style={{ position: 'absolute', top: 0, right: 0 }}>
|
||||
{queryKey ? (
|
||||
<DropdownMenu.Root onOpenChange={setOpenChange}>
|
||||
<DropdownMenu.Trigger>
|
||||
<View style={{ padding: StyleConstants.Spacing.L }}>
|
||||
<Icon
|
||||
name='MoreHorizontal'
|
||||
color={colors.secondary}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
/>
|
||||
</View>
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Content>
|
||||
{mShare.map((mGroup, index) => (
|
||||
<DropdownMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
|
||||
{mAccount.map((mGroup, index) => (
|
||||
<DropdownMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
|
||||
{mStatus.map((mGroup, index) => (
|
||||
<DropdownMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
|
||||
{mInstance.map((mGroup, index) => (
|
||||
<DropdownMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
) : null}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineHeaderAndroid
|
@ -1,114 +0,0 @@
|
||||
import contextMenuAccount from '@components/ContextMenu/account'
|
||||
import contextMenuInstance from '@components/ContextMenu/instance'
|
||||
import contextMenuShare from '@components/ContextMenu/share'
|
||||
import contextMenuStatus from '@components/ContextMenu/status'
|
||||
import Icon from '@components/Icon'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import { ContextMenuAction } from 'react-native-context-menu-view'
|
||||
import HeaderSharedAccount from './HeaderShared/Account'
|
||||
import HeaderSharedApplication from './HeaderShared/Application'
|
||||
import HeaderSharedCreated from './HeaderShared/Created'
|
||||
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
||||
|
||||
export interface Props {
|
||||
queryKey?: QueryKeyTimeline
|
||||
status: Mastodon.Status
|
||||
highlighted: boolean
|
||||
}
|
||||
|
||||
const TimelineHeaderDefault = ({ queryKey, status, highlighted }: Props) => {
|
||||
if (!queryKey) return null
|
||||
|
||||
const { t } = useTranslation('componentContextMenu')
|
||||
const { colors } = useTheme()
|
||||
|
||||
const actions: ContextMenuAction[] = []
|
||||
|
||||
const shareOnPress =
|
||||
status.visibility !== 'direct'
|
||||
? contextMenuShare({
|
||||
actions,
|
||||
type: 'status',
|
||||
url: status.url || status.uri
|
||||
})
|
||||
: null
|
||||
const statusOnPress = contextMenuStatus({
|
||||
actions,
|
||||
status,
|
||||
queryKey
|
||||
})
|
||||
const accountOnPress = contextMenuAccount({
|
||||
actions,
|
||||
type: 'status',
|
||||
queryKey,
|
||||
id: status.account.id
|
||||
})
|
||||
const instanceOnPress = contextMenuInstance({
|
||||
actions,
|
||||
status,
|
||||
queryKey
|
||||
})
|
||||
|
||||
const { showActionSheetWithOptions } = useActionSheet()
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||
<View style={{ flex: 7 }}>
|
||||
<HeaderSharedAccount account={status.account} />
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginTop: StyleConstants.Spacing.XS,
|
||||
marginBottom: StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
<HeaderSharedCreated
|
||||
created_at={status.created_at}
|
||||
edited_at={status.edited_at}
|
||||
highlighted={highlighted}
|
||||
/>
|
||||
<HeaderSharedVisibility visibility={status.visibility} />
|
||||
<HeaderSharedMuted muted={status.muted} />
|
||||
<HeaderSharedApplication application={status.application} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{queryKey ? (
|
||||
<Pressable
|
||||
accessibilityHint={t('accessibilityHint')}
|
||||
style={{ flex: 1, flexBasis: StyleConstants.Font.Size.L }}
|
||||
onPress={() =>
|
||||
showActionSheetWithOptions(
|
||||
{
|
||||
options: actions.map(action => action.title),
|
||||
cancelButtonIndex: 999,
|
||||
destructiveButtonIndex: actions
|
||||
.map((action, index) => (action.destructive ? index : 999))
|
||||
.filter(num => num !== 999)
|
||||
},
|
||||
index => {
|
||||
if (index !== undefined) {
|
||||
for (const on of [shareOnPress, statusOnPress, accountOnPress, instanceOnPress]) {
|
||||
on && on(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
>
|
||||
<Icon name='MoreHorizontal' color={colors.secondary} size={StyleConstants.Font.Size.L} />
|
||||
</Pressable>
|
||||
) : null}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineHeaderDefault
|
@ -1,75 +0,0 @@
|
||||
import Icon from '@components/Icon'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import ContextMenu from 'react-native-context-menu-view'
|
||||
import { ContextMenuContext } from './ContextMenu'
|
||||
import HeaderSharedAccount from './HeaderShared/Account'
|
||||
import HeaderSharedApplication from './HeaderShared/Application'
|
||||
import HeaderSharedCreated from './HeaderShared/Created'
|
||||
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
||||
|
||||
export interface Props {
|
||||
queryKey?: QueryKeyTimeline
|
||||
status: Mastodon.Status
|
||||
highlighted: boolean
|
||||
}
|
||||
|
||||
const TimelineHeaderDefault = ({ queryKey, status, highlighted }: Props) => {
|
||||
const { t } = useTranslation('componentContextMenu')
|
||||
const { colors } = useTheme()
|
||||
|
||||
const contextMenuContext = useContext(ContextMenuContext)
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||
<View style={{ flex: 7 }}>
|
||||
<HeaderSharedAccount account={status.account} />
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginTop: StyleConstants.Spacing.XS,
|
||||
marginBottom: StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
<HeaderSharedCreated
|
||||
created_at={status.created_at}
|
||||
edited_at={status.edited_at}
|
||||
highlighted={highlighted}
|
||||
/>
|
||||
<HeaderSharedVisibility visibility={status.visibility} />
|
||||
<HeaderSharedMuted muted={status.muted} />
|
||||
<HeaderSharedApplication application={status.application} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{queryKey ? (
|
||||
<Pressable
|
||||
accessibilityHint={t('accessibilityHint')}
|
||||
style={{ flex: 1, flexBasis: StyleConstants.Font.Size.L }}
|
||||
>
|
||||
<ContextMenu
|
||||
style={{ flex: 1, alignItems: 'center' }}
|
||||
dropdownMenuMode
|
||||
actions={contextMenuContext}
|
||||
onPress={() => {}}
|
||||
children={
|
||||
<Icon
|
||||
name='MoreHorizontal'
|
||||
color={colors.secondary}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Pressable>
|
||||
) : null}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineHeaderDefault
|
144
src/components/Timeline/Shared/HeaderDefault.tsx
Normal file
144
src/components/Timeline/Shared/HeaderDefault.tsx
Normal file
@ -0,0 +1,144 @@
|
||||
import menuAccount from '@components/contextMenu/account'
|
||||
import menuInstance from '@components/contextMenu/instance'
|
||||
import menuShare from '@components/contextMenu/share'
|
||||
import menuStatus from '@components/contextMenu/status'
|
||||
import Icon from '@components/Icon'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Platform, Pressable, View } from 'react-native'
|
||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||
import HeaderSharedAccount from './HeaderShared/Account'
|
||||
import HeaderSharedApplication from './HeaderShared/Application'
|
||||
import HeaderSharedCreated from './HeaderShared/Created'
|
||||
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
||||
|
||||
export interface Props {
|
||||
queryKey?: QueryKeyTimeline
|
||||
rootQueryKey?: QueryKeyTimeline
|
||||
status: Mastodon.Status
|
||||
highlighted: boolean
|
||||
copiableContent: React.MutableRefObject<{
|
||||
content: string
|
||||
complete: boolean
|
||||
}>
|
||||
}
|
||||
|
||||
const TimelineHeaderDefault: React.FC<Props> = ({
|
||||
queryKey,
|
||||
rootQueryKey,
|
||||
status,
|
||||
highlighted,
|
||||
copiableContent
|
||||
}) => {
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('componentContextMenu')
|
||||
|
||||
const [openChange, setOpenChange] = useState(false)
|
||||
const mShare = menuShare({
|
||||
visibility: status.visibility,
|
||||
type: 'status',
|
||||
url: status.url || status.uri,
|
||||
copiableContent
|
||||
})
|
||||
const mAccount = menuAccount({
|
||||
openChange,
|
||||
id: status.account.id,
|
||||
queryKey
|
||||
})
|
||||
const mStatus = menuStatus({ status, queryKey, rootQueryKey })
|
||||
const mInstance = menuInstance({ status, queryKey, rootQueryKey })
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||
<View style={{ flex: 7 }}>
|
||||
<HeaderSharedAccount account={status.account} />
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginTop: StyleConstants.Spacing.XS,
|
||||
marginBottom: StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
<HeaderSharedCreated
|
||||
created_at={status.created_at}
|
||||
edited_at={status.edited_at}
|
||||
highlighted={highlighted}
|
||||
/>
|
||||
<HeaderSharedVisibility visibility={status.visibility} />
|
||||
<HeaderSharedMuted muted={status.muted} />
|
||||
<HeaderSharedApplication application={status.application} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{Platform.OS !== 'android' && queryKey ? (
|
||||
<Pressable
|
||||
accessibilityHint={t('accessibilityHint')}
|
||||
style={{ flex: 1, alignItems: 'center' }}
|
||||
>
|
||||
<DropdownMenu.Root onOpenChange={setOpenChange}>
|
||||
<DropdownMenu.Trigger>
|
||||
<Icon
|
||||
name='MoreHorizontal'
|
||||
color={colors.secondary}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
/>
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Content>
|
||||
{mShare.map((mGroup, index) => (
|
||||
<DropdownMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
|
||||
{mAccount.map((mGroup, index) => (
|
||||
<DropdownMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
|
||||
{mStatus.map((mGroup, index) => (
|
||||
<DropdownMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
|
||||
{mInstance.map((mGroup, index) => (
|
||||
<DropdownMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</Pressable>
|
||||
) : null}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineHeaderDefault
|
@ -1,157 +0,0 @@
|
||||
import contextMenuAccount from '@components/ContextMenu/account'
|
||||
import contextMenuInstance from '@components/ContextMenu/instance'
|
||||
import contextMenuShare from '@components/ContextMenu/share'
|
||||
import contextMenuStatus from '@components/ContextMenu/status'
|
||||
import Icon from '@components/Icon'
|
||||
import { RelationshipIncoming, RelationshipOutgoing } from '@components/Relationship'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useMemo } from 'react'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import { ContextMenuAction } from 'react-native-context-menu-view'
|
||||
import HeaderSharedAccount from './HeaderShared/Account'
|
||||
import HeaderSharedApplication from './HeaderShared/Application'
|
||||
import HeaderSharedCreated from './HeaderShared/Created'
|
||||
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
||||
|
||||
export interface Props {
|
||||
queryKey: QueryKeyTimeline
|
||||
notification: Mastodon.Notification
|
||||
}
|
||||
|
||||
const TimelineHeaderNotification = ({ queryKey, notification }: Props) => {
|
||||
const { colors } = useTheme()
|
||||
|
||||
const contextMenuActions: ContextMenuAction[] = []
|
||||
const status = notification.status
|
||||
const shareOnPress =
|
||||
status && status?.visibility !== 'direct'
|
||||
? contextMenuShare({
|
||||
actions: contextMenuActions,
|
||||
type: 'status',
|
||||
url: status.url || status.uri
|
||||
})
|
||||
: null
|
||||
const statusOnPress =
|
||||
status &&
|
||||
contextMenuStatus({
|
||||
actions: contextMenuActions,
|
||||
status: status,
|
||||
queryKey
|
||||
})
|
||||
const accountOnPress =
|
||||
status &&
|
||||
contextMenuAccount({
|
||||
actions: contextMenuActions,
|
||||
type: 'status',
|
||||
queryKey,
|
||||
id: status.account.id
|
||||
})
|
||||
const instanceOnPress =
|
||||
status &&
|
||||
contextMenuInstance({
|
||||
actions: contextMenuActions,
|
||||
status: status,
|
||||
queryKey
|
||||
})
|
||||
|
||||
const { showActionSheetWithOptions } = useActionSheet()
|
||||
|
||||
const actions = useMemo(() => {
|
||||
switch (notification.type) {
|
||||
case 'follow':
|
||||
return <RelationshipOutgoing id={notification.account.id} />
|
||||
case 'follow_request':
|
||||
return <RelationshipIncoming id={notification.account.id} />
|
||||
default:
|
||||
if (notification.status) {
|
||||
return (
|
||||
<Pressable
|
||||
style={{ flex: 1, flexBasis: StyleConstants.Font.Size.L }}
|
||||
onPress={() =>
|
||||
showActionSheetWithOptions(
|
||||
{
|
||||
options: contextMenuActions.map(action => action.title),
|
||||
cancelButtonIndex: 999,
|
||||
destructiveButtonIndex: contextMenuActions
|
||||
.map((action, index) => (action.destructive ? index : 999))
|
||||
.filter(num => num !== 999)
|
||||
},
|
||||
index => {
|
||||
if (index !== undefined) {
|
||||
for (const on of [
|
||||
shareOnPress,
|
||||
statusOnPress,
|
||||
accountOnPress,
|
||||
instanceOnPress
|
||||
]) {
|
||||
on && on(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
children={
|
||||
<Icon
|
||||
name='MoreHorizontal'
|
||||
color={colors.secondary}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [notification.type])
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||
<View
|
||||
style={{
|
||||
flex: notification.type === 'follow' || notification.type === 'follow_request' ? 1 : 4
|
||||
}}
|
||||
>
|
||||
<HeaderSharedAccount
|
||||
account={notification.status ? notification.status.account : notification.account}
|
||||
{...((notification.type === 'follow' || notification.type === 'follow_request') && {
|
||||
withoutName: true
|
||||
})}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginTop: StyleConstants.Spacing.XS,
|
||||
marginBottom: StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
<HeaderSharedCreated
|
||||
created_at={notification.status?.created_at || notification.created_at}
|
||||
edited_at={notification.status?.edited_at}
|
||||
/>
|
||||
{notification.status?.visibility ? (
|
||||
<HeaderSharedVisibility visibility={notification.status.visibility} />
|
||||
) : null}
|
||||
<HeaderSharedMuted muted={notification.status?.muted} />
|
||||
<HeaderSharedApplication application={notification.status?.application} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={[
|
||||
{ marginLeft: StyleConstants.Spacing.M },
|
||||
notification.type === 'follow' || notification.type === 'follow_request'
|
||||
? { flexShrink: 1 }
|
||||
: { flex: 1 }
|
||||
]}
|
||||
>
|
||||
{actions}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineHeaderNotification
|
@ -1,105 +0,0 @@
|
||||
import Icon from '@components/Icon'
|
||||
import { RelationshipIncoming, RelationshipOutgoing } from '@components/Relationship'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useContext, useMemo } from 'react'
|
||||
import { Pressable, View } from 'react-native'
|
||||
import ContextMenu from 'react-native-context-menu-view'
|
||||
import { ContextMenuContext } from './ContextMenu'
|
||||
import HeaderSharedAccount from './HeaderShared/Account'
|
||||
import HeaderSharedApplication from './HeaderShared/Application'
|
||||
import HeaderSharedCreated from './HeaderShared/Created'
|
||||
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
||||
|
||||
export interface Props {
|
||||
queryKey: QueryKeyTimeline
|
||||
notification: Mastodon.Notification
|
||||
}
|
||||
|
||||
const TimelineHeaderNotification = ({ notification }: Props) => {
|
||||
const { colors } = useTheme()
|
||||
|
||||
const contextMenuContext = useContext(ContextMenuContext)
|
||||
|
||||
const actions = useMemo(() => {
|
||||
switch (notification.type) {
|
||||
case 'follow':
|
||||
return <RelationshipOutgoing id={notification.account.id} />
|
||||
case 'follow_request':
|
||||
return <RelationshipIncoming id={notification.account.id} />
|
||||
default:
|
||||
if (notification.status) {
|
||||
return (
|
||||
<Pressable
|
||||
style={{ flex: 1, flexBasis: StyleConstants.Font.Size.L }}
|
||||
children={
|
||||
<ContextMenu
|
||||
style={{ flex: 1, alignItems: 'center' }}
|
||||
dropdownMenuMode
|
||||
actions={contextMenuContext}
|
||||
onPress={() => {}}
|
||||
children={
|
||||
<Icon
|
||||
name='MoreHorizontal'
|
||||
color={colors.secondary}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [notification.type])
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||
<View
|
||||
style={{
|
||||
flex: notification.type === 'follow' || notification.type === 'follow_request' ? 1 : 4
|
||||
}}
|
||||
>
|
||||
<HeaderSharedAccount
|
||||
account={notification.status ? notification.status.account : notification.account}
|
||||
{...((notification.type === 'follow' || notification.type === 'follow_request') && {
|
||||
withoutName: true
|
||||
})}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginTop: StyleConstants.Spacing.XS,
|
||||
marginBottom: StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
<HeaderSharedCreated
|
||||
created_at={notification.status?.created_at || notification.created_at}
|
||||
edited_at={notification.status?.edited_at}
|
||||
/>
|
||||
{notification.status?.visibility ? (
|
||||
<HeaderSharedVisibility visibility={notification.status.visibility} />
|
||||
) : null}
|
||||
<HeaderSharedMuted muted={notification.status?.muted} />
|
||||
<HeaderSharedApplication application={notification.status?.application} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={[
|
||||
{ marginLeft: StyleConstants.Spacing.M },
|
||||
notification.type === 'follow' || notification.type === 'follow_request'
|
||||
? { flexShrink: 1 }
|
||||
: { flex: 1 }
|
||||
]}
|
||||
>
|
||||
{actions}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineHeaderNotification
|
163
src/components/Timeline/Shared/HeaderNotification.tsx
Normal file
163
src/components/Timeline/Shared/HeaderNotification.tsx
Normal file
@ -0,0 +1,163 @@
|
||||
import menuAccount from '@components/contextMenu/account'
|
||||
import menuInstance from '@components/contextMenu/instance'
|
||||
import menuShare from '@components/contextMenu/share'
|
||||
import menuStatus from '@components/contextMenu/status'
|
||||
import Icon from '@components/Icon'
|
||||
import { RelationshipIncoming, RelationshipOutgoing } from '@components/Relationship'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useState } from 'react'
|
||||
import { Platform, Pressable, View } from 'react-native'
|
||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||
import HeaderSharedAccount from './HeaderShared/Account'
|
||||
import HeaderSharedApplication from './HeaderShared/Application'
|
||||
import HeaderSharedCreated from './HeaderShared/Created'
|
||||
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
||||
|
||||
export interface Props {
|
||||
queryKey: QueryKeyTimeline
|
||||
notification: Mastodon.Notification
|
||||
}
|
||||
|
||||
const TimelineHeaderNotification = ({ queryKey, notification }: Props) => {
|
||||
const { colors } = useTheme()
|
||||
|
||||
const [openChange, setOpenChange] = useState(false)
|
||||
const mShare = menuShare({
|
||||
visibility: notification.status?.visibility,
|
||||
type: 'status',
|
||||
url: notification.status?.url || notification.status?.uri
|
||||
})
|
||||
const mAccount = menuAccount({
|
||||
openChange,
|
||||
id: notification.status?.account.id,
|
||||
queryKey
|
||||
})
|
||||
const mStatus = menuStatus({ status: notification.status, queryKey })
|
||||
const mInstance = menuInstance({ status: notification.status, queryKey })
|
||||
|
||||
const actions = () => {
|
||||
switch (notification.type) {
|
||||
case 'follow':
|
||||
return <RelationshipOutgoing id={notification.account.id} />
|
||||
case 'follow_request':
|
||||
return <RelationshipIncoming id={notification.account.id} />
|
||||
default:
|
||||
if (notification.status) {
|
||||
return (
|
||||
<Pressable
|
||||
style={{ flex: 1, alignItems: 'center' }}
|
||||
children={
|
||||
<DropdownMenu.Root onOpenChange={setOpenChange}>
|
||||
<DropdownMenu.Trigger>
|
||||
<Icon
|
||||
name='MoreHorizontal'
|
||||
color={colors.secondary}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
/>
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Content>
|
||||
{mShare.map((mGroup, index) => (
|
||||
<DropdownMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
|
||||
{mAccount.map((mGroup, index) => (
|
||||
<DropdownMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
|
||||
{mStatus.map((mGroup, index) => (
|
||||
<DropdownMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
|
||||
{mInstance.map((mGroup, index) => (
|
||||
<DropdownMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon iosIconName={menu.icon} />
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||
<View
|
||||
style={{
|
||||
flex: notification.type === 'follow' || notification.type === 'follow_request' ? 1 : 4
|
||||
}}
|
||||
>
|
||||
<HeaderSharedAccount
|
||||
account={notification.status ? notification.status.account : notification.account}
|
||||
{...((notification.type === 'follow' || notification.type === 'follow_request') && {
|
||||
withoutName: true
|
||||
})}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginTop: StyleConstants.Spacing.XS,
|
||||
marginBottom: StyleConstants.Spacing.S
|
||||
}}
|
||||
>
|
||||
<HeaderSharedCreated
|
||||
created_at={notification.status?.created_at || notification.created_at}
|
||||
edited_at={notification.status?.edited_at}
|
||||
/>
|
||||
{notification.status?.visibility ? (
|
||||
<HeaderSharedVisibility visibility={notification.status.visibility} />
|
||||
) : null}
|
||||
<HeaderSharedMuted muted={notification.status?.muted} />
|
||||
<HeaderSharedApplication application={notification.status?.application} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{Platform.OS !== 'android' ? (
|
||||
<View
|
||||
style={[
|
||||
{ marginLeft: StyleConstants.Spacing.M },
|
||||
notification.type === 'follow' || notification.type === 'follow_request'
|
||||
? { flexShrink: 1 }
|
||||
: { flex: 1 }
|
||||
]}
|
||||
children={actions()}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineHeaderNotification
|
220
src/components/contextMenu/account.tsx
Normal file
220
src/components/contextMenu/account.tsx
Normal file
@ -0,0 +1,220 @@
|
||||
import haptics from '@components/haptics'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import {
|
||||
QueryKeyRelationship,
|
||||
useRelationshipMutation,
|
||||
useRelationshipQuery
|
||||
} from '@utils/queryHooks/relationship'
|
||||
import {
|
||||
MutationVarsTimelineUpdateAccountProperty,
|
||||
QueryKeyTimeline,
|
||||
useTimelineMutation
|
||||
} from '@utils/queryHooks/timeline'
|
||||
import { getInstanceAccount } from '@utils/slices/instancesSlice'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Platform } from 'react-native'
|
||||
import { useQueryClient } from 'react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
const menuAccount = ({
|
||||
openChange,
|
||||
id,
|
||||
queryKey,
|
||||
rootQueryKey
|
||||
}: {
|
||||
openChange: boolean
|
||||
id?: Mastodon.Account['id']
|
||||
queryKey?: QueryKeyTimeline
|
||||
rootQueryKey?: QueryKeyTimeline
|
||||
}): ContextMenu[][] => {
|
||||
if (!id) return []
|
||||
|
||||
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 [enabled, setEnabled] = useState(openChange)
|
||||
useEffect(() => {
|
||||
if (!ownAccount && enabled === false && openChange === true) {
|
||||
setEnabled(true)
|
||||
}
|
||||
}, [openChange, enabled])
|
||||
const { data, isFetching } = useRelationshipQuery({ id, options: { enabled } })
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
const timelineMutation = useTimelineMutation({
|
||||
onSuccess: (_, params) => {
|
||||
queryClient.refetchQueries(['Relationship', { id }])
|
||||
const theParams = params as MutationVarsTimelineUpdateAccountProperty
|
||||
displayMessage({
|
||||
theme,
|
||||
type: 'success',
|
||||
message: t('common:message.success.message', {
|
||||
function: t(`account.${theParams.payload.property}.action`, {
|
||||
...(theParams.payload.property !== 'reports' && {
|
||||
context: (theParams.payload.currentValue || false).toString()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
onError: (err: any, params) => {
|
||||
const theParams = params as MutationVarsTimelineUpdateAccountProperty
|
||||
displayMessage({
|
||||
theme,
|
||||
type: 'error',
|
||||
message: t('common:message.error.message', {
|
||||
function: t(`account.${theParams.payload.property}.action`, {
|
||||
...(theParams.payload.property !== 'reports' && {
|
||||
context: (theParams.payload.currentValue || false).toString()
|
||||
})
|
||||
})
|
||||
}),
|
||||
...(err.status &&
|
||||
typeof err.status === 'number' &&
|
||||
err.data &&
|
||||
err.data.error &&
|
||||
typeof err.data.error === 'string' && {
|
||||
description: err.data.error
|
||||
})
|
||||
})
|
||||
},
|
||||
onSettled: () => {
|
||||
queryKey && queryClient.invalidateQueries(queryKey)
|
||||
rootQueryKey && queryClient.invalidateQueries(rootQueryKey)
|
||||
}
|
||||
})
|
||||
const queryKeyRelationship: QueryKeyRelationship = ['Relationship', { id }]
|
||||
const relationshipMutation = useRelationshipMutation({
|
||||
onSuccess: (res, { payload: { action } }) => {
|
||||
haptics('Success')
|
||||
queryClient.setQueryData<Mastodon.Relationship[]>(queryKeyRelationship, [res])
|
||||
if (action === 'block') {
|
||||
const queryKey: QueryKeyTimeline = ['Timeline', { page: 'Following' }]
|
||||
queryClient.invalidateQueries(queryKey)
|
||||
}
|
||||
},
|
||||
onError: (err: any, { payload: { action } }) => {
|
||||
displayMessage({
|
||||
theme,
|
||||
type: 'error',
|
||||
message: t('common:message.error.message', {
|
||||
function: t(`${action}.function`)
|
||||
}),
|
||||
...(err.status &&
|
||||
typeof err.status === 'number' &&
|
||||
err.data &&
|
||||
err.data.error &&
|
||||
typeof err.data.error === 'string' && {
|
||||
description: err.data.error
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (!ownAccount && Platform.OS !== 'android') {
|
||||
menus[0].push({
|
||||
key: 'account-following',
|
||||
item: {
|
||||
onSelect: () =>
|
||||
data &&
|
||||
relationshipMutation.mutate({
|
||||
id,
|
||||
type: 'outgoing',
|
||||
payload: { action: 'follow', state: !data?.requested ? data.following : true }
|
||||
}),
|
||||
disabled: !data || isFetching,
|
||||
destructive: false,
|
||||
hidden: false
|
||||
},
|
||||
title: !data?.requested
|
||||
? t('account.following.action', {
|
||||
context: (data?.following || false).toString()
|
||||
})
|
||||
: t('componentRelationship:button.requested'),
|
||||
icon: !data?.requested
|
||||
? data?.following
|
||||
? 'person.badge.minus'
|
||||
: 'person.badge.plus'
|
||||
: 'person.badge.minus'
|
||||
})
|
||||
}
|
||||
if (!ownAccount) {
|
||||
menus[0].push({
|
||||
key: 'account-mute',
|
||||
item: {
|
||||
onSelect: () =>
|
||||
timelineMutation.mutate({
|
||||
type: 'updateAccountProperty',
|
||||
queryKey,
|
||||
id,
|
||||
payload: { property: 'mute', currentValue: data?.muting }
|
||||
}),
|
||||
disabled: Platform.OS !== 'android' ? !data || isFetching : false,
|
||||
destructive: false,
|
||||
hidden: false
|
||||
},
|
||||
title: t('account.mute.action', {
|
||||
context: (data?.muting || false).toString()
|
||||
}),
|
||||
icon: data?.muting ? 'eye' : 'eye.slash'
|
||||
})
|
||||
}
|
||||
|
||||
!ownAccount &&
|
||||
menus.push([
|
||||
{
|
||||
key: 'account-block',
|
||||
item: {
|
||||
onSelect: () =>
|
||||
timelineMutation.mutate({
|
||||
type: 'updateAccountProperty',
|
||||
queryKey,
|
||||
id,
|
||||
payload: { property: 'block', currentValue: data?.blocking }
|
||||
}),
|
||||
disabled: Platform.OS !== 'android' ? !data || isFetching : false,
|
||||
destructive: !data?.blocking,
|
||||
hidden: false
|
||||
},
|
||||
title: t('account.block.action', {
|
||||
context: (data?.blocking || false).toString()
|
||||
}),
|
||||
icon: data?.blocking ? 'checkmark.circle' : 'xmark.circle'
|
||||
},
|
||||
{
|
||||
key: 'account-reports',
|
||||
item: {
|
||||
onSelect: () => {
|
||||
timelineMutation.mutate({
|
||||
type: 'updateAccountProperty',
|
||||
queryKey,
|
||||
id,
|
||||
payload: { property: 'reports' }
|
||||
})
|
||||
timelineMutation.mutate({
|
||||
type: 'updateAccountProperty',
|
||||
queryKey,
|
||||
id,
|
||||
payload: { property: 'block', currentValue: false }
|
||||
})
|
||||
},
|
||||
disabled: false,
|
||||
destructive: true,
|
||||
hidden: false
|
||||
},
|
||||
title: t('account.reports.action'),
|
||||
icon: 'flag'
|
||||
}
|
||||
])
|
||||
|
||||
return menus
|
||||
}
|
||||
|
||||
export default menuAccount
|
6
src/components/contextMenu/index.d.ts
vendored
Normal file
6
src/components/contextMenu/index.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
type ContextMenu = {
|
||||
key: string
|
||||
item: { onSelect: () => void; disabled: boolean; destructive: boolean; hidden: boolean }
|
||||
title: string
|
||||
icon: string
|
||||
}
|
Reference in New Issue
Block a user