mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
@ -1,44 +1,30 @@
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { generateAccountKey, getAccountDetails, setAccount } from '@utils/storage/actions'
|
||||
import { StorageGlobal } from '@utils/storage/global'
|
||||
import { ReadableAccountType, setAccount } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React from 'react'
|
||||
import Button from './Button'
|
||||
import haptics from './haptics'
|
||||
|
||||
interface Props {
|
||||
account: NonNullable<StorageGlobal['accounts']>[number]
|
||||
selected?: boolean
|
||||
account: ReadableAccountType
|
||||
additionalActions?: () => void
|
||||
}
|
||||
|
||||
const AccountButton: React.FC<Props> = ({ account, selected = false, additionalActions }) => {
|
||||
const AccountButton: React.FC<Props> = ({ account, additionalActions }) => {
|
||||
const navigation = useNavigation()
|
||||
const accountDetails = getAccountDetails(
|
||||
['auth.domain', 'auth.account.acct', 'auth.account.domain', 'auth.account.id'],
|
||||
account
|
||||
)
|
||||
if (!accountDetails) return null
|
||||
|
||||
return (
|
||||
<Button
|
||||
type='text'
|
||||
selected={selected}
|
||||
selected={account.active}
|
||||
style={{
|
||||
marginBottom: StyleConstants.Spacing.M,
|
||||
marginRight: StyleConstants.Spacing.M
|
||||
}}
|
||||
content={`@${accountDetails['auth.account.acct']}@${accountDetails['auth.account.domain']}${
|
||||
selected ? ' ✓' : ''
|
||||
}`}
|
||||
content={account.acct}
|
||||
onPress={() => {
|
||||
haptics('Light')
|
||||
setAccount(
|
||||
generateAccountKey({
|
||||
domain: accountDetails['auth.domain'],
|
||||
id: accountDetails['auth.account.id']
|
||||
})
|
||||
)
|
||||
setAccount(account.key)
|
||||
navigation.goBack()
|
||||
if (additionalActions) {
|
||||
additionalActions()
|
||||
|
@ -97,7 +97,7 @@ const Button: React.FC<Props> = ({
|
||||
fontSize: StyleConstants.Font.Size[size] * (size === 'L' ? 1.25 : 1),
|
||||
opacity: loading ? 0 : 1
|
||||
}}
|
||||
fontWeight={fontBold ? 'Bold' : 'Normal'}
|
||||
fontWeight={fontBold || selected ? 'Bold' : 'Normal'}
|
||||
children={content}
|
||||
testID='text'
|
||||
/>
|
||||
@ -125,7 +125,7 @@ const Button: React.FC<Props> = ({
|
||||
borderRadius: 100,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderWidth: overlay ? 0 : 1,
|
||||
borderWidth: overlay ? 0 : selected ? 1.5 : 1,
|
||||
borderColor: mainColor(),
|
||||
backgroundColor: overlay ? colors.backgroundOverlayInvert : colors.backgroundDefault,
|
||||
paddingVertical: StyleConstants.Spacing[spacing],
|
||||
|
@ -106,7 +106,7 @@ const Message = React.forwardRef<FlashMessage>((_, ref) => {
|
||||
...StyleConstants.FontStyle.S
|
||||
}}
|
||||
// @ts-ignore
|
||||
textProps={{ numberOfLines: 2 }}
|
||||
textProps={{ numberOfLines: 3 }}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
@ -192,16 +192,48 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
</ContextMenu.Trigger>
|
||||
|
||||
<ContextMenu.Content>
|
||||
{[mShare, mStatus, mInstance].map((type, i) => (
|
||||
{[mShare, mStatus, mInstance].map((menu, i) => (
|
||||
<Fragment key={i}>
|
||||
{type.map((mGroup, index) => (
|
||||
{menu.map((group, index) => (
|
||||
<ContextMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<ContextMenu.Item key={menu.key} {...menu.item}>
|
||||
<ContextMenu.ItemTitle children={menu.title} />
|
||||
<ContextMenu.ItemIcon ios={{ name: menu.icon }} />
|
||||
{group.map(item => {
|
||||
switch (item.type) {
|
||||
case 'item':
|
||||
return (
|
||||
<ContextMenu.Item key={item.key} {...item.props}>
|
||||
<ContextMenu.ItemTitle children={item.title} />
|
||||
{item.icon ? (
|
||||
<ContextMenu.ItemIcon ios={{ name: item.icon }} />
|
||||
) : null}
|
||||
</ContextMenu.Item>
|
||||
)
|
||||
case 'sub':
|
||||
return (
|
||||
// @ts-ignore
|
||||
<ContextMenu.Sub key={item.key}>
|
||||
<ContextMenu.SubTrigger
|
||||
key={item.trigger.key}
|
||||
{...item.trigger.props}
|
||||
>
|
||||
<ContextMenu.ItemTitle children={item.trigger.title} />
|
||||
{item.trigger.icon ? (
|
||||
<ContextMenu.ItemIcon ios={{ name: item.trigger.icon }} />
|
||||
) : null}
|
||||
</ContextMenu.SubTrigger>
|
||||
<ContextMenu.SubContent>
|
||||
{item.items.map(sub => (
|
||||
<ContextMenu.Item key={sub.key} {...sub.props}>
|
||||
<ContextMenu.ItemTitle children={sub.title} />
|
||||
{sub.icon ? (
|
||||
<ContextMenu.ItemIcon ios={{ name: sub.icon }} />
|
||||
) : null}
|
||||
</ContextMenu.Item>
|
||||
))}
|
||||
</ContextMenu.SubContent>
|
||||
</ContextMenu.Sub>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</ContextMenu.Group>
|
||||
))}
|
||||
</Fragment>
|
||||
|
@ -162,16 +162,43 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
|
||||
</ContextMenu.Trigger>
|
||||
|
||||
<ContextMenu.Content>
|
||||
{[mShare, mStatus, mInstance].map((type, i) => (
|
||||
{[mShare, mStatus, mInstance].map((menu, i) => (
|
||||
<Fragment key={i}>
|
||||
{type.map((mGroup, index) => (
|
||||
{menu.map((group, index) => (
|
||||
<ContextMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<ContextMenu.Item key={menu.key} {...menu.item}>
|
||||
<ContextMenu.ItemTitle children={menu.title} />
|
||||
<ContextMenu.ItemIcon ios={{ name: menu.icon }} />
|
||||
{group.map(item => {
|
||||
switch (item.type) {
|
||||
case 'item':
|
||||
return (
|
||||
<ContextMenu.Item key={item.key} {...item.props}>
|
||||
<ContextMenu.ItemTitle children={item.title} />
|
||||
{item.icon ? <ContextMenu.ItemIcon ios={{ name: item.icon }} /> : null}
|
||||
</ContextMenu.Item>
|
||||
)
|
||||
case 'sub':
|
||||
return (
|
||||
// @ts-ignore
|
||||
<ContextMenu.Sub key={item.key}>
|
||||
<ContextMenu.SubTrigger key={item.trigger.key} {...item.trigger.props}>
|
||||
<ContextMenu.ItemTitle children={item.trigger.title} />
|
||||
{item.trigger.icon ? (
|
||||
<ContextMenu.ItemIcon ios={{ name: item.trigger.icon }} />
|
||||
) : null}
|
||||
</ContextMenu.SubTrigger>
|
||||
<ContextMenu.SubContent>
|
||||
{item.items.map(sub => (
|
||||
<ContextMenu.Item key={sub.key} {...sub.props}>
|
||||
<ContextMenu.ItemTitle children={sub.title} />
|
||||
{sub.icon ? (
|
||||
<ContextMenu.ItemIcon ios={{ name: sub.icon }} />
|
||||
) : null}
|
||||
</ContextMenu.Item>
|
||||
))}
|
||||
</ContextMenu.SubContent>
|
||||
</ContextMenu.Sub>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</ContextMenu.Group>
|
||||
))}
|
||||
</Fragment>
|
||||
|
@ -28,7 +28,7 @@ const TimelineActions: React.FC = () => {
|
||||
const navigationState = useNavState()
|
||||
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>()
|
||||
const { t } = useTranslation(['common', 'componentTimeline'])
|
||||
const { colors, theme } = useTheme()
|
||||
const { colors } = useTheme()
|
||||
const iconColor = colors.secondary
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
@ -56,7 +56,6 @@ const TimelineActions: React.FC = () => {
|
||||
onError: (err: any, params) => {
|
||||
const correctParam = params as MutationVarsTimelineUpdateStatusProperty
|
||||
displayMessage({
|
||||
theme,
|
||||
type: 'error',
|
||||
message: t('common:message.error.message', {
|
||||
function: t(
|
||||
|
@ -10,8 +10,7 @@ import * as DropdownMenu from 'zeego/dropdown-menu'
|
||||
import StatusContext from './Context'
|
||||
|
||||
const TimelineHeaderAndroid: React.FC = () => {
|
||||
const { queryKey, status, disableDetails, disableOnPress, rawContent } =
|
||||
useContext(StatusContext)
|
||||
const { queryKey, status, disableDetails, disableOnPress, rawContent } = useContext(StatusContext)
|
||||
|
||||
if (Platform.OS !== 'android' || !status || disableDetails || disableOnPress) return null
|
||||
|
||||
@ -52,16 +51,48 @@ const TimelineHeaderAndroid: React.FC = () => {
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Content>
|
||||
{[mShare, mAccount, mStatus].map((type, i) => (
|
||||
{[mShare, mAccount, mStatus].map((menu, i) => (
|
||||
<Fragment key={i}>
|
||||
{type.map((mGroup, index) => (
|
||||
{menu.map((group, index) => (
|
||||
<DropdownMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon ios={{ name: menu.icon }} />
|
||||
{group.map(item => {
|
||||
switch (item.type) {
|
||||
case 'item':
|
||||
return (
|
||||
<DropdownMenu.Item key={item.key} {...item.props}>
|
||||
<DropdownMenu.ItemTitle children={item.title} />
|
||||
{item.icon ? (
|
||||
<DropdownMenu.ItemIcon ios={{ name: item.icon }} />
|
||||
) : null}
|
||||
</DropdownMenu.Item>
|
||||
)
|
||||
case 'sub':
|
||||
return (
|
||||
// @ts-ignore
|
||||
<DropdownMenu.Sub key={item.key}>
|
||||
<DropdownMenu.SubTrigger
|
||||
key={item.trigger.key}
|
||||
{...item.trigger.props}
|
||||
>
|
||||
<DropdownMenu.ItemTitle children={item.trigger.title} />
|
||||
{item.trigger.icon ? (
|
||||
<DropdownMenu.ItemIcon ios={{ name: item.trigger.icon }} />
|
||||
) : null}
|
||||
</DropdownMenu.SubTrigger>
|
||||
<DropdownMenu.SubContent>
|
||||
{item.items.map(sub => (
|
||||
<DropdownMenu.Item key={sub.key} {...sub.props}>
|
||||
<DropdownMenu.ItemTitle children={sub.title} />
|
||||
{sub.icon ? (
|
||||
<DropdownMenu.ItemIcon ios={{ name: sub.icon }} />
|
||||
) : null}
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Sub>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
</Fragment>
|
||||
|
@ -17,8 +17,7 @@ import HeaderSharedReplies from './HeaderShared/Replies'
|
||||
import HeaderSharedVisibility from './HeaderShared/Visibility'
|
||||
|
||||
const TimelineHeaderDefault: React.FC = () => {
|
||||
const { queryKey, status, disableDetails, rawContent, isRemote } =
|
||||
useContext(StatusContext)
|
||||
const { queryKey, status, disableDetails, rawContent, isRemote } = useContext(StatusContext)
|
||||
if (!status) return null
|
||||
|
||||
const { colors } = useTheme()
|
||||
@ -88,16 +87,48 @@ const TimelineHeaderDefault: React.FC = () => {
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Content>
|
||||
{[mShare, mAccount, mStatus].map((type, i) => (
|
||||
{[mShare, mAccount, mStatus].map((menu, i) => (
|
||||
<Fragment key={i}>
|
||||
{type.map((mGroup, index) => (
|
||||
{menu.map((group, index) => (
|
||||
<DropdownMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon ios={{ name: menu.icon }} />
|
||||
{group.map(item => {
|
||||
switch (item.type) {
|
||||
case 'item':
|
||||
return (
|
||||
<DropdownMenu.Item key={item.key} {...item.props}>
|
||||
<DropdownMenu.ItemTitle children={item.title} />
|
||||
{item.icon ? (
|
||||
<DropdownMenu.ItemIcon ios={{ name: item.icon }} />
|
||||
) : null}
|
||||
</DropdownMenu.Item>
|
||||
)
|
||||
case 'sub':
|
||||
return (
|
||||
// @ts-ignore
|
||||
<DropdownMenu.Sub key={item}>
|
||||
<DropdownMenu.SubTrigger
|
||||
key={item.trigger.key}
|
||||
{...item.trigger.props}
|
||||
>
|
||||
<DropdownMenu.ItemTitle children={item.trigger.title} />
|
||||
{item.trigger.icon ? (
|
||||
<DropdownMenu.ItemIcon ios={{ name: item.trigger.icon }} />
|
||||
) : null}
|
||||
</DropdownMenu.SubTrigger>
|
||||
<DropdownMenu.SubContent>
|
||||
{item.items.map(sub => (
|
||||
<DropdownMenu.Item key={sub.key} {...sub.props}>
|
||||
<DropdownMenu.ItemTitle children={sub.title} />
|
||||
{sub.icon ? (
|
||||
<DropdownMenu.ItemIcon ios={{ name: sub.icon }} />
|
||||
) : null}
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Sub>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
</Fragment>
|
||||
|
@ -88,16 +88,50 @@ const TimelineHeaderNotification: React.FC<Props> = ({ notification }) => {
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Content>
|
||||
{[mShare, mStatus, mAccount, mInstance].map((type, i) => (
|
||||
{[mShare, mStatus, mAccount, mInstance].map((menu, i) => (
|
||||
<Fragment key={i}>
|
||||
{type.map((mGroup, index) => (
|
||||
{menu.map((group, index) => (
|
||||
<DropdownMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon ios={{ name: menu.icon }} />
|
||||
{group.map(item => {
|
||||
switch (item.type) {
|
||||
case 'item':
|
||||
return (
|
||||
<DropdownMenu.Item key={item.key} {...item.props}>
|
||||
<DropdownMenu.ItemTitle children={item.title} />
|
||||
{item.icon ? (
|
||||
<DropdownMenu.ItemIcon ios={{ name: item.icon }} />
|
||||
) : null}
|
||||
</DropdownMenu.Item>
|
||||
)
|
||||
case 'sub':
|
||||
return (
|
||||
// @ts-ignore
|
||||
<DropdownMenu.Sub key={item.key}>
|
||||
<DropdownMenu.SubTrigger
|
||||
key={item.trigger.key}
|
||||
{...item.trigger.props}
|
||||
>
|
||||
<DropdownMenu.ItemTitle children={item.trigger.title} />
|
||||
{item.trigger.icon ? (
|
||||
<DropdownMenu.ItemIcon
|
||||
ios={{ name: item.trigger.icon }}
|
||||
/>
|
||||
) : null}
|
||||
</DropdownMenu.SubTrigger>
|
||||
<DropdownMenu.SubContent>
|
||||
{item.items.map(sub => (
|
||||
<DropdownMenu.Item key={sub.key} {...sub.props}>
|
||||
<DropdownMenu.ItemTitle children={sub.title} />
|
||||
{sub.icon ? (
|
||||
<DropdownMenu.ItemIcon ios={{ name: sub.icon }} />
|
||||
) : null}
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Sub>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
</Fragment>
|
||||
|
@ -3,6 +3,7 @@ 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 apiInstance from '@utils/api/instance'
|
||||
import { TabSharedStackParamList, useNavState } from '@utils/navigation/navigators'
|
||||
import { useAccountQuery } from '@utils/queryHooks/account'
|
||||
import {
|
||||
@ -14,7 +15,7 @@ import {
|
||||
MutationVarsTimelineUpdateAccountProperty,
|
||||
useTimelineMutation
|
||||
} from '@utils/queryHooks/timeline'
|
||||
import { useAccountStorage } from '@utils/storage/actions'
|
||||
import { getAccountStorage, getReadableAccounts, useAccountStorage } from '@utils/storage/actions'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert, Platform } from 'react-native'
|
||||
@ -29,13 +30,13 @@ const menuAccount = ({
|
||||
openChange: boolean
|
||||
account?: Partial<Mastodon.Account> & Pick<Mastodon.Account, 'id' | 'username' | 'acct' | 'url'>
|
||||
status?: Mastodon.Status
|
||||
}): ContextMenu[][] => {
|
||||
}): ContextMenu => {
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<TabSharedStackParamList, any, undefined>>()
|
||||
const navState = useNavState()
|
||||
const { t } = useTranslation(['common', 'componentContextMenu', 'componentRelationship'])
|
||||
|
||||
const menus: ContextMenu[][] = [[]]
|
||||
const menus: ContextMenu = [[]]
|
||||
|
||||
const [enabled, setEnabled] = useState(openChange)
|
||||
useEffect(() => {
|
||||
@ -135,8 +136,9 @@ const menuAccount = ({
|
||||
|
||||
if (!ownAccount && Platform.OS !== 'android' && type !== 'account') {
|
||||
menus[0].push({
|
||||
type: 'item',
|
||||
key: 'account-following',
|
||||
item: {
|
||||
props: {
|
||||
onSelect: () =>
|
||||
data &&
|
||||
actualAccount &&
|
||||
@ -165,8 +167,9 @@ const menuAccount = ({
|
||||
|
||||
if (!ownAccount) {
|
||||
menus[0].push({
|
||||
type: 'item',
|
||||
key: 'account-list',
|
||||
item: {
|
||||
props: {
|
||||
onSelect: () => navigation.navigate('Tab-Shared-Account-In-Lists', { account }),
|
||||
disabled: Platform.OS !== 'android' ? !data || !isFetched : false,
|
||||
destructive: false,
|
||||
@ -176,8 +179,9 @@ const menuAccount = ({
|
||||
icon: 'checklist'
|
||||
})
|
||||
menus[0].push({
|
||||
type: 'item',
|
||||
key: 'account-show-boosts',
|
||||
item: {
|
||||
props: {
|
||||
onSelect: () =>
|
||||
actualAccount &&
|
||||
relationshipMutation.mutate({
|
||||
@ -196,8 +200,9 @@ const menuAccount = ({
|
||||
icon: data?.showing_reblogs ? 'rectangle.on.rectangle.slash' : 'rectangle.on.rectangle'
|
||||
})
|
||||
menus[0].push({
|
||||
type: 'item',
|
||||
key: 'account-mute',
|
||||
item: {
|
||||
props: {
|
||||
onSelect: () =>
|
||||
actualAccount &&
|
||||
timelineMutation.mutate({
|
||||
@ -216,10 +221,89 @@ const menuAccount = ({
|
||||
icon: data?.muting ? 'eye' : 'eye.slash'
|
||||
})
|
||||
|
||||
const followAs = () => {
|
||||
if (type !== 'account') return
|
||||
const accounts = getReadableAccounts()
|
||||
menus[0].push({
|
||||
type: 'sub',
|
||||
key: 'account-follow-as',
|
||||
trigger: {
|
||||
key: 'account-follow-as',
|
||||
props: { destructive: false, disabled: false, hidden: !accounts.length },
|
||||
title: t('componentContextMenu:account.followAs.trigger'),
|
||||
icon: 'person.badge.plus'
|
||||
},
|
||||
items: accounts.map(a => ({
|
||||
key: `account-${a.key}`,
|
||||
props: {
|
||||
onSelect: async () => {
|
||||
const lookup = await apiInstance<Mastodon.Account>({
|
||||
account: a.key,
|
||||
method: 'get',
|
||||
url: 'accounts/lookup',
|
||||
params: {
|
||||
acct:
|
||||
account.acct === account.username
|
||||
? `${account.acct}@${getAccountStorage.string('auth.account.domain')}`
|
||||
: account.acct
|
||||
}
|
||||
}).then(res => res.body)
|
||||
await apiInstance({
|
||||
account: a.key,
|
||||
method: 'post',
|
||||
url: `accounts/${lookup.id}/follow`
|
||||
})
|
||||
.then(() =>
|
||||
displayMessage({
|
||||
type: 'success',
|
||||
message: t('componentContextMenu:account.followAs.succeed', {
|
||||
context: account.locked ? 'locked' : 'default',
|
||||
defaultValue: 'default',
|
||||
target: account.acct,
|
||||
source: a.acct
|
||||
})
|
||||
})
|
||||
)
|
||||
.catch(err =>
|
||||
displayMessage({
|
||||
type: 'error',
|
||||
message: t('common:message.error.message', {
|
||||
function: t('componentContextMenu:account.followAs.failed')
|
||||
}),
|
||||
...(err.status &&
|
||||
typeof err.status === 'number' &&
|
||||
err.data &&
|
||||
err.data.error &&
|
||||
typeof err.data.error === 'string' && {
|
||||
description: err.data.error
|
||||
})
|
||||
})
|
||||
)
|
||||
},
|
||||
disabled: false,
|
||||
destructive: false,
|
||||
hidden: a.active
|
||||
},
|
||||
title: a.acct
|
||||
}))
|
||||
})
|
||||
}
|
||||
followAs()
|
||||
|
||||
menus.push([
|
||||
{
|
||||
type: 'sub',
|
||||
key: 'account-block-report',
|
||||
trigger: {
|
||||
key: 'account-block-report',
|
||||
props: { destructive: true, disabled: false, hidden: false },
|
||||
title: t('componentContextMenu:account.blockReport'),
|
||||
icon: 'hand.raised'
|
||||
},
|
||||
items: [
|
||||
{
|
||||
key: 'account-block',
|
||||
item: {
|
||||
props: {
|
||||
onSelect: () =>
|
||||
Alert.alert(
|
||||
t('componentContextMenu:account.block.alert.title', {
|
||||
@ -255,7 +339,7 @@ const menuAccount = ({
|
||||
},
|
||||
{
|
||||
key: 'account-reports',
|
||||
item: {
|
||||
props: {
|
||||
onSelect: () =>
|
||||
actualAccount &&
|
||||
navigation.navigate('Tab-Shared-Report', {
|
||||
@ -269,6 +353,8 @@ const menuAccount = ({
|
||||
title: t('componentContextMenu:account.reports.action'),
|
||||
icon: 'flag'
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
|
@ -3,17 +3,17 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
import { RootStackParamList, useNavState } from '@utils/navigation/navigators'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const menuAt = ({ account }: { account: Mastodon.Account }): ContextMenu[][] => {
|
||||
const menuAt = ({ account }: { account: Mastodon.Account }): ContextMenu => {
|
||||
const { t } = useTranslation('componentContextMenu')
|
||||
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>()
|
||||
const navigationState = useNavState()
|
||||
|
||||
const menus: ContextMenu[][] = []
|
||||
|
||||
menus.push([
|
||||
return [
|
||||
[
|
||||
{
|
||||
type: 'item',
|
||||
key: 'at-direct',
|
||||
item: {
|
||||
props: {
|
||||
onSelect: () =>
|
||||
navigation.navigate('Screen-Compose', {
|
||||
type: 'conversation',
|
||||
@ -29,8 +29,9 @@ const menuAt = ({ account }: { account: Mastodon.Account }): ContextMenu[][] =>
|
||||
icon: 'envelope'
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
key: 'at-public',
|
||||
item: {
|
||||
props: {
|
||||
onSelect: () =>
|
||||
navigation.navigate('Screen-Compose', {
|
||||
type: 'conversation',
|
||||
@ -45,9 +46,8 @@ const menuAt = ({ account }: { account: Mastodon.Account }): ContextMenu[][] =>
|
||||
title: t('at.public'),
|
||||
icon: 'at'
|
||||
}
|
||||
])
|
||||
|
||||
return menus
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
export default menuAt
|
||||
|
55
src/components/contextMenu/index.d.ts
vendored
55
src/components/contextMenu/index.d.ts
vendored
@ -1,6 +1,53 @@
|
||||
type ContextMenu = {
|
||||
// type ContextMenu = (
|
||||
// | {
|
||||
// type: 'group'
|
||||
// key: string
|
||||
// items: ContextMenuItem[]
|
||||
// }
|
||||
// | {
|
||||
// type: 'sub'
|
||||
// key: string
|
||||
// trigger: {
|
||||
// key: string
|
||||
// props: {
|
||||
// disabled: boolean
|
||||
// destructive: boolean
|
||||
// hidden: boolean
|
||||
// }
|
||||
// title: string
|
||||
// icon?: string
|
||||
// }
|
||||
// items: ContextMenuItem[]
|
||||
// }
|
||||
// )[]
|
||||
|
||||
type ContextMenu = (ContextMenuItem | ContextMenuSub)[][]
|
||||
|
||||
type ContextMenuItem = {
|
||||
type: 'item'
|
||||
key: string
|
||||
item: { onSelect: () => void; disabled: boolean; destructive: boolean; hidden: boolean }
|
||||
title: string
|
||||
icon: string
|
||||
props: {
|
||||
onSelect: () => void
|
||||
disabled: boolean
|
||||
destructive: boolean
|
||||
hidden: boolean
|
||||
}
|
||||
title: string
|
||||
icon?: string
|
||||
}
|
||||
|
||||
type ContextMenuSub = {
|
||||
type: 'sub'
|
||||
key: string
|
||||
trigger: {
|
||||
key: string
|
||||
props: {
|
||||
disabled: boolean
|
||||
destructive: boolean
|
||||
hidden: boolean
|
||||
}
|
||||
title: string
|
||||
icon?: string
|
||||
}
|
||||
items: Omit<ContextMenuItem, 'type'>[]
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ const menuInstance = ({
|
||||
}: {
|
||||
status?: Mastodon.Status
|
||||
queryKey?: QueryKeyTimeline
|
||||
}): ContextMenu[][] => {
|
||||
}): ContextMenu => {
|
||||
if (!status || !queryKey) return []
|
||||
|
||||
const { t } = useTranslation(['common', 'componentContextMenu'])
|
||||
@ -30,15 +30,16 @@ const menuInstance = ({
|
||||
}
|
||||
})
|
||||
|
||||
const menus: ContextMenu[][] = []
|
||||
const menus: ContextMenu = []
|
||||
|
||||
const instance = parse(status.uri).hostname
|
||||
|
||||
if (instance !== getAccountStorage.string('auth.domain')) {
|
||||
menus.push([
|
||||
{
|
||||
type: 'item',
|
||||
key: 'instance-block',
|
||||
item: {
|
||||
props: {
|
||||
onSelect: () =>
|
||||
Alert.alert(
|
||||
t('componentContextMenu:instance.block.alert.title', { instance }),
|
||||
|
@ -15,18 +15,19 @@ const menuShare = (
|
||||
type: 'account'
|
||||
url?: string
|
||||
}
|
||||
): ContextMenu[][] => {
|
||||
): ContextMenu => {
|
||||
if (params.type === 'status' && params.visibility === 'direct') return []
|
||||
|
||||
const { t } = useTranslation('componentContextMenu')
|
||||
|
||||
const menus: ContextMenu[][] = [[]]
|
||||
const menus: ContextMenu = [[]]
|
||||
|
||||
if (params.url) {
|
||||
const url = params.url
|
||||
menus[0].push({
|
||||
type: 'item',
|
||||
key: 'share',
|
||||
item: {
|
||||
props: {
|
||||
onSelect: () => {
|
||||
switch (Platform.OS) {
|
||||
case 'ios':
|
||||
@ -47,8 +48,9 @@ const menuShare = (
|
||||
}
|
||||
if (params.type === 'status')
|
||||
menus[0].push({
|
||||
type: 'item',
|
||||
key: 'copy',
|
||||
item: {
|
||||
props: {
|
||||
onSelect: () => {
|
||||
Clipboard.setString(params.rawContent?.current.join(`\n\n`) || '')
|
||||
displayMessage({ type: 'success', message: t(`copy.succeed`) })
|
||||
|
@ -21,7 +21,7 @@ const menuStatus = ({
|
||||
}: {
|
||||
status?: Mastodon.Status
|
||||
queryKey?: QueryKeyTimeline
|
||||
}): ContextMenu[][] => {
|
||||
}): ContextMenu => {
|
||||
if (!status || !queryKey) return []
|
||||
|
||||
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList, 'Screen-Tabs'>>()
|
||||
@ -55,7 +55,7 @@ const menuStatus = ({
|
||||
}
|
||||
})
|
||||
|
||||
const menus: ContextMenu[][] = []
|
||||
const menus: ContextMenu = []
|
||||
|
||||
const [accountId] = useAccountStorage.string('auth.account.id')
|
||||
const ownAccount = accountId === status.account?.id
|
||||
@ -64,8 +64,9 @@ const menuStatus = ({
|
||||
|
||||
menus.push([
|
||||
{
|
||||
type: 'item',
|
||||
key: 'status-edit',
|
||||
item: {
|
||||
props: {
|
||||
onSelect: async () => {
|
||||
let replyToStatus: Mastodon.Status | undefined = undefined
|
||||
if (status.in_reply_to_id) {
|
||||
@ -102,8 +103,9 @@ const menuStatus = ({
|
||||
icon: 'square.and.pencil'
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
key: 'status-delete-edit',
|
||||
item: {
|
||||
props: {
|
||||
onSelect: () =>
|
||||
Alert.alert(
|
||||
t('componentContextMenu:status.deleteEdit.alert.title'),
|
||||
@ -145,8 +147,9 @@ const menuStatus = ({
|
||||
icon: 'pencil.and.outline'
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
key: 'status-delete',
|
||||
item: {
|
||||
props: {
|
||||
onSelect: () =>
|
||||
Alert.alert(
|
||||
t('componentContextMenu:status.delete.alert.title'),
|
||||
@ -176,8 +179,9 @@ const menuStatus = ({
|
||||
|
||||
menus.push([
|
||||
{
|
||||
type: 'item',
|
||||
key: 'status-mute',
|
||||
item: {
|
||||
props: {
|
||||
onSelect: () =>
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
@ -198,8 +202,9 @@ const menuStatus = ({
|
||||
icon: status.muted ? 'speaker' : 'speaker.slash'
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
key: 'status-pin',
|
||||
item: {
|
||||
props: {
|
||||
onSelect: () =>
|
||||
// Also note that reblogs cannot be pinned.
|
||||
mutation.mutate({
|
||||
|
@ -15,6 +15,13 @@
|
||||
"action_false": "Mute user",
|
||||
"action_true": "Unmute user"
|
||||
},
|
||||
"followAs": {
|
||||
"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...",
|
||||
"block": {
|
||||
"action_false": "Block user",
|
||||
"action_true": "Unblock user",
|
||||
|
@ -2,7 +2,7 @@ import AccountButton from '@components/AccountButton'
|
||||
import CustomText from '@components/Text'
|
||||
import navigationRef from '@utils/navigation/navigationRef'
|
||||
import { RootStackScreenProps } from '@utils/navigation/navigators'
|
||||
import { getGlobalStorage } from '@utils/storage/actions'
|
||||
import { getReadableAccounts } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import * as VideoThumbnails from 'expo-video-thumbnails'
|
||||
@ -92,7 +92,7 @@ const ScreenAccountSelection = ({
|
||||
const { colors } = useTheme()
|
||||
const { t } = useTranslation('screenAccountSelection')
|
||||
|
||||
const accounts = getGlobalStorage.object('accounts')
|
||||
const accounts = getReadableAccounts()
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
@ -125,11 +125,7 @@ const ScreenAccountSelection = ({
|
||||
marginTop: StyleConstants.Spacing.M
|
||||
}}
|
||||
>
|
||||
{accounts &&
|
||||
accounts
|
||||
.slice()
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.map((account, index) => {
|
||||
{accounts.map((account, index) => {
|
||||
return (
|
||||
<AccountButton
|
||||
key={index}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import AccountButton from '@components/AccountButton'
|
||||
import ComponentInstance from '@components/Instance'
|
||||
import CustomText from '@components/Text'
|
||||
import { getGlobalStorage } from '@utils/storage/actions'
|
||||
import { getReadableAccounts } from '@utils/storage/actions'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
@ -12,8 +12,7 @@ import { ScrollView } from 'react-native-gesture-handler'
|
||||
const TabMeSwitch: React.FC = () => {
|
||||
const { t } = useTranslation('screenTabs')
|
||||
const { colors } = useTheme()
|
||||
const accounts = getGlobalStorage.object('accounts')
|
||||
const accountActive = getGlobalStorage.string('account.active')
|
||||
const accounts = getReadableAccounts()
|
||||
|
||||
const scrollViewRef = useRef<ScrollView>(null)
|
||||
useEffect(() => {
|
||||
@ -71,18 +70,8 @@ const TabMeSwitch: React.FC = () => {
|
||||
marginTop: StyleConstants.Spacing.M
|
||||
}}
|
||||
>
|
||||
{accounts &&
|
||||
accounts
|
||||
.slice()
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
.map((account, index) => {
|
||||
return (
|
||||
<AccountButton
|
||||
key={index}
|
||||
account={account}
|
||||
selected={account === accountActive}
|
||||
/>
|
||||
)
|
||||
{accounts.map((account, index) => {
|
||||
return <AccountButton key={index} account={account} />
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
|
@ -69,14 +69,41 @@ const AccountInformationActions: React.FC = () => {
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Content>
|
||||
{mAt.map((mGroup, index) => (
|
||||
{mAt.map((group, index) => (
|
||||
<DropdownMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon ios={{ name: menu.icon }} />
|
||||
{group.map(item => {
|
||||
switch (item.type) {
|
||||
case 'item':
|
||||
return (
|
||||
<DropdownMenu.Item key={item.key} {...item.props}>
|
||||
<DropdownMenu.ItemTitle children={item.title} />
|
||||
{item.icon ? <DropdownMenu.ItemIcon ios={{ name: item.icon }} /> : null}
|
||||
</DropdownMenu.Item>
|
||||
)
|
||||
case 'sub':
|
||||
return (
|
||||
// @ts-ignore
|
||||
<DropdownMenu.Sub key={item.key}>
|
||||
<DropdownMenu.SubTrigger key={item.trigger.key} {...item.trigger.props}>
|
||||
<DropdownMenu.ItemTitle children={item.trigger.title} />
|
||||
{item.trigger.icon ? (
|
||||
<DropdownMenu.ItemIcon ios={{ name: item.trigger.icon }} />
|
||||
) : null}
|
||||
</DropdownMenu.SubTrigger>
|
||||
<DropdownMenu.SubContent>
|
||||
{item.items.map(sub => (
|
||||
<DropdownMenu.Item key={sub.key} {...sub.props}>
|
||||
<DropdownMenu.ItemTitle children={sub.title} />
|
||||
{sub.icon ? (
|
||||
<DropdownMenu.ItemIcon ios={{ name: sub.icon }} />
|
||||
) : null}
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Sub>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
</DropdownMenu.Content>
|
||||
|
@ -89,16 +89,48 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Content>
|
||||
{[mShare, mAccount].map((type, i) => (
|
||||
{[mShare, mAccount].map((menu, i) => (
|
||||
<Fragment key={i}>
|
||||
{type.map((mGroup, index) => (
|
||||
{menu.map((group, index) => (
|
||||
<DropdownMenu.Group key={index}>
|
||||
{mGroup.map(menu => (
|
||||
<DropdownMenu.Item key={menu.key} {...menu.item}>
|
||||
<DropdownMenu.ItemTitle children={menu.title} />
|
||||
<DropdownMenu.ItemIcon ios={{ name: menu.icon }} />
|
||||
{group.map(item => {
|
||||
switch (item.type) {
|
||||
case 'item':
|
||||
return (
|
||||
<DropdownMenu.Item key={item.key} {...item.props}>
|
||||
<DropdownMenu.ItemTitle children={item.title} />
|
||||
{item.icon ? (
|
||||
<DropdownMenu.ItemIcon ios={{ name: item.icon }} />
|
||||
) : null}
|
||||
</DropdownMenu.Item>
|
||||
)
|
||||
case 'sub':
|
||||
return (
|
||||
// @ts-ignore
|
||||
<DropdownMenu.Sub key={item.key}>
|
||||
<DropdownMenu.SubTrigger
|
||||
key={item.trigger.key}
|
||||
{...item.trigger.props}
|
||||
>
|
||||
<DropdownMenu.ItemTitle children={item.trigger.title} />
|
||||
{item.trigger.icon ? (
|
||||
<DropdownMenu.ItemIcon ios={{ name: item.trigger.icon }} />
|
||||
) : null}
|
||||
</DropdownMenu.SubTrigger>
|
||||
<DropdownMenu.SubContent>
|
||||
{item.items.map(sub => (
|
||||
<DropdownMenu.Item key={sub.key} {...sub.props}>
|
||||
<DropdownMenu.ItemTitle children={sub.title} />
|
||||
{sub.icon ? (
|
||||
<DropdownMenu.ItemIcon ios={{ name: sub.icon }} />
|
||||
) : null}
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Sub>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</DropdownMenu.Group>
|
||||
))}
|
||||
</Fragment>
|
||||
|
@ -21,14 +21,14 @@ const apiGeneral = async <T = unknown>({
|
||||
body
|
||||
}: Params): Promise<PagedResponse<T>> => {
|
||||
console.log(
|
||||
ctx.bgGreen.bold(' API general ') +
|
||||
ctx.bgMagenta.bold(' General ') +
|
||||
' ' +
|
||||
domain +
|
||||
' ' +
|
||||
method +
|
||||
ctx.green(' -> ') +
|
||||
ctx.magenta(' -> ') +
|
||||
`/${url}` +
|
||||
(params ? ctx.green(' -> ') : ''),
|
||||
(params ? ctx.magenta(' -> ') : ''),
|
||||
params ? params : ''
|
||||
)
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { getAccountDetails } from '@utils/storage/actions'
|
||||
import { StorageGlobal } from '@utils/storage/global'
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import { ctx, handleError, PagedResponse, parseHeaderLinks, userAgent } from './helpers'
|
||||
|
||||
export type Params = {
|
||||
account?: StorageGlobal['account.active']
|
||||
method: 'get' | 'post' | 'put' | 'delete' | 'patch'
|
||||
version?: 'v1' | 'v2'
|
||||
url: string
|
||||
@ -15,6 +17,7 @@ export type Params = {
|
||||
}
|
||||
|
||||
const apiInstance = async <T = unknown>({
|
||||
account,
|
||||
method,
|
||||
version = 'v1',
|
||||
url,
|
||||
@ -23,7 +26,7 @@ const apiInstance = async <T = unknown>({
|
||||
body,
|
||||
extras
|
||||
}: Params): Promise<PagedResponse<T>> => {
|
||||
const accountDetails = getAccountDetails(['auth.domain', 'auth.token'])
|
||||
const accountDetails = getAccountDetails(['auth.domain', 'auth.token'], account)
|
||||
if (!accountDetails) {
|
||||
console.warn(ctx.bgRed.white.bold(' API instance '), 'No account detail available')
|
||||
return Promise.reject()
|
||||
@ -35,9 +38,9 @@ const apiInstance = async <T = unknown>({
|
||||
}
|
||||
|
||||
console.log(
|
||||
ctx.bgGreen.bold(' API instance '),
|
||||
ctx.bgBlue.bold(' Instance '),
|
||||
accountDetails['auth.domain'],
|
||||
method + ctx.green(' -> ') + `/${url}` + (params ? ctx.green(' -> ') : ''),
|
||||
method + ctx.blue(' -> ') + `/${url}` + (params ? ctx.blue(' -> ') : ''),
|
||||
params ? params : ''
|
||||
)
|
||||
|
||||
|
@ -26,7 +26,7 @@ const apiTooot = async <T = unknown>({
|
||||
body
|
||||
}: Params): Promise<{ body: T }> => {
|
||||
console.log(
|
||||
ctx.bgGreen.bold(' API tooot ') +
|
||||
ctx.bgGreen.bold(' tooot ') +
|
||||
' ' +
|
||||
method +
|
||||
ctx.green(' -> ') +
|
||||
|
@ -6,9 +6,9 @@ const log = (type: 'log' | 'warn' | 'error', func: string, message: string) => {
|
||||
switch (type) {
|
||||
case 'log':
|
||||
console.log(
|
||||
ctx.bgBlue.white.bold(' Start up ') +
|
||||
ctx.bgGrey.white.bold(' Start up ') +
|
||||
' ' +
|
||||
ctx.bgBlueBright.black(` ${func} `) +
|
||||
ctx.bgGrey.black(` ${func} `) +
|
||||
' ' +
|
||||
message
|
||||
)
|
||||
|
@ -228,6 +228,41 @@ export const setAccount = async (account: string) => {
|
||||
queryClient.clear()
|
||||
}
|
||||
|
||||
export type ReadableAccountType = {
|
||||
acct: string
|
||||
key: string
|
||||
active: boolean
|
||||
}
|
||||
export const getReadableAccounts = (): ReadableAccountType[] => {
|
||||
const accountActive = getGlobalStorage.string('account.active')
|
||||
const accounts = getGlobalStorage.object('accounts')?.sort((a, b) => a.localeCompare(b))
|
||||
accounts?.splice(
|
||||
accounts.findIndex(a => a === accountActive),
|
||||
1
|
||||
)
|
||||
accounts?.unshift(accountActive || '')
|
||||
return (
|
||||
accounts?.map(account => {
|
||||
const details = getAccountDetails(
|
||||
['auth.account.acct', 'auth.account.domain', 'auth.domain', 'auth.account.id'],
|
||||
account
|
||||
)
|
||||
if (details) {
|
||||
return {
|
||||
acct: `@${details['auth.account.acct']}@${details['auth.account.domain']}`,
|
||||
key: generateAccountKey({
|
||||
domain: details['auth.domain'],
|
||||
id: details['auth.account.id']
|
||||
}),
|
||||
active: account === accountActive
|
||||
}
|
||||
} else {
|
||||
return { acct: '', key: '', active: false }
|
||||
}
|
||||
}) || []
|
||||
).filter(a => a.acct.length)
|
||||
}
|
||||
|
||||
export const removeAccount = async (account: string) => {
|
||||
const currAccounts: NonNullable<StorageGlobal['accounts']> = JSON.parse(
|
||||
storage.global.getString('accounts') || '[]'
|
||||
|
Reference in New Issue
Block a user