mirror of
https://github.com/tooot-app/app
synced 2025-04-25 07:28:41 +02:00
Fix #600
This commit is contained in:
parent
fb7111d771
commit
39ab9059d9
@ -1,3 +1,2 @@
|
|||||||
Enjoy toooting! This version includes following improvements and fixes:
|
Enjoy toooting! This version includes following improvements and fixes:
|
||||||
- Fixed wrongly update notification
|
- Allowing adding more context of reports
|
||||||
- Fix some random crashes
|
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
toooting愉快!此版本包括以下改进和修复:
|
toooting愉快!此版本包括以下改进和修复:
|
||||||
- 修复错误的升级通知
|
- 可添加举报细节
|
||||||
- 修复部分应用崩溃
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tooot",
|
"name": "tooot",
|
||||||
"version": "4.7.2",
|
"version": "4.7.3",
|
||||||
"description": "tooot for Mastodon",
|
"description": "tooot for Mastodon",
|
||||||
"author": "xmflsct <me@xmflsct.com>",
|
"author": "xmflsct <me@xmflsct.com>",
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
|
5
src/@types/mastodon.d.ts
vendored
5
src/@types/mastodon.d.ts
vendored
@ -470,6 +470,11 @@ declare namespace Mastodon {
|
|||||||
updated_at: string
|
updated_at: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Rule = {
|
||||||
|
id: string
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
|
||||||
type Status = {
|
type Status = {
|
||||||
// Base
|
// Base
|
||||||
id: string
|
id: string
|
||||||
|
@ -42,11 +42,11 @@ const ComponentAccount: React.FC<PropsWithChildren & Props> = ({ account, props,
|
|||||||
style={{
|
style={{
|
||||||
width: StyleConstants.Avatar.S,
|
width: StyleConstants.Avatar.S,
|
||||||
height: StyleConstants.Avatar.S,
|
height: StyleConstants.Avatar.S,
|
||||||
borderRadius: 6,
|
borderRadius: 8,
|
||||||
marginRight: StyleConstants.Spacing.S
|
marginRight: StyleConstants.Spacing.S
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<View>
|
<View style={{ flex: 1 }}>
|
||||||
<CustomText numberOfLines={1}>
|
<CustomText numberOfLines={1}>
|
||||||
<ParseEmojis
|
<ParseEmojis
|
||||||
content={account.display_name || account.username}
|
content={account.display_name || account.username}
|
||||||
|
@ -28,7 +28,7 @@ const EmojisList = () => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const { emojisState, emojisDispatch } = useContext(EmojisContext)
|
const { emojisState, emojisDispatch } = useContext(EmojisContext)
|
||||||
const { colors, mode } = useTheme()
|
const { colors } = useTheme()
|
||||||
|
|
||||||
const addEmoji = (shortcode: string) => {
|
const addEmoji = (shortcode: string) => {
|
||||||
if (emojisState.targetIndex === -1) {
|
if (emojisState.targetIndex === -1) {
|
||||||
@ -158,7 +158,6 @@ const EmojisList = () => {
|
|||||||
onChangeText={setSearch}
|
onChangeText={setSearch}
|
||||||
autoCapitalize='none'
|
autoCapitalize='none'
|
||||||
clearButtonMode='always'
|
clearButtonMode='always'
|
||||||
keyboardAppearance={mode}
|
|
||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
/>
|
/>
|
||||||
|
@ -18,6 +18,7 @@ export interface Props {
|
|||||||
|
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
destructive?: boolean
|
||||||
|
|
||||||
onPress: () => void
|
onPress: () => void
|
||||||
}
|
}
|
||||||
@ -34,6 +35,7 @@ const HeaderRight: React.FC<Props> = ({
|
|||||||
background = false,
|
background = false,
|
||||||
loading,
|
loading,
|
||||||
disabled,
|
disabled,
|
||||||
|
destructive = false,
|
||||||
onPress
|
onPress
|
||||||
}) => {
|
}) => {
|
||||||
const { colors, theme } = useTheme()
|
const { colors, theme } = useTheme()
|
||||||
@ -41,10 +43,7 @@ const HeaderRight: React.FC<Props> = ({
|
|||||||
const loadingSpinkit = useMemo(
|
const loadingSpinkit = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<View style={{ position: 'absolute' }}>
|
<View style={{ position: 'absolute' }}>
|
||||||
<Flow
|
<Flow size={StyleConstants.Font.Size.M * 1.25} color={colors.secondary} />
|
||||||
size={StyleConstants.Font.Size.M * 1.25}
|
|
||||||
color={colors.secondary}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
),
|
),
|
||||||
[theme]
|
[theme]
|
||||||
@ -59,7 +58,7 @@ const HeaderRight: React.FC<Props> = ({
|
|||||||
name={content}
|
name={content}
|
||||||
style={{ opacity: loading ? 0 : 1 }}
|
style={{ opacity: loading ? 0 : 1 }}
|
||||||
size={StyleConstants.Spacing.M * 1.25}
|
size={StyleConstants.Spacing.M * 1.25}
|
||||||
color={disabled ? colors.secondary : colors.primaryDefault}
|
color={disabled ? colors.secondary : destructive ? colors.red : colors.primaryDefault}
|
||||||
/>
|
/>
|
||||||
{loading && loadingSpinkit}
|
{loading && loadingSpinkit}
|
||||||
</>
|
</>
|
||||||
@ -69,8 +68,13 @@ const HeaderRight: React.FC<Props> = ({
|
|||||||
<>
|
<>
|
||||||
<CustomText
|
<CustomText
|
||||||
fontStyle='M'
|
fontStyle='M'
|
||||||
|
fontWeight={destructive ? 'Bold' : 'Normal'}
|
||||||
style={{
|
style={{
|
||||||
color: disabled ? colors.secondary : colors.primaryDefault,
|
color: disabled
|
||||||
|
? colors.secondary
|
||||||
|
: destructive
|
||||||
|
? colors.red
|
||||||
|
: colors.primaryDefault,
|
||||||
opacity: loading ? 0 : 1
|
opacity: loading ? 0 : 1
|
||||||
}}
|
}}
|
||||||
children={content}
|
children={content}
|
||||||
@ -94,14 +98,10 @@ const HeaderRight: React.FC<Props> = ({
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: background
|
backgroundColor: background ? colors.backgroundOverlayDefault : undefined,
|
||||||
? colors.backgroundOverlayDefault
|
|
||||||
: undefined,
|
|
||||||
minHeight: 44,
|
minHeight: 44,
|
||||||
minWidth: 44,
|
minWidth: 44,
|
||||||
marginRight: native
|
marginRight: native ? -StyleConstants.Spacing.S : StyleConstants.Spacing.S,
|
||||||
? -StyleConstants.Spacing.S
|
|
||||||
: StyleConstants.Spacing.S,
|
|
||||||
...(type === 'icon' && {
|
...(type === 'icon' && {
|
||||||
borderRadius: 100
|
borderRadius: 100
|
||||||
}),
|
}),
|
||||||
|
@ -85,7 +85,6 @@ const ComponentInput = forwardRef(
|
|||||||
multiline,
|
multiline,
|
||||||
numberOfLines: Platform.OS === 'android' ? 5 : undefined
|
numberOfLines: Platform.OS === 'android' ? 5 : undefined
|
||||||
})}
|
})}
|
||||||
keyboardAppearance={mode}
|
|
||||||
textAlignVertical='top'
|
textAlignVertical='top'
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
@ -93,7 +93,7 @@ const ParseEmojis = React.memo(
|
|||||||
</CustomText>
|
</CustomText>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
(prev, next) => prev.content === next.content
|
(prev, next) => prev.content === next.content && prev.style?.color === next.style?.color
|
||||||
)
|
)
|
||||||
|
|
||||||
export default ParseEmojis
|
export default ParseEmojis
|
||||||
|
@ -11,9 +11,15 @@ export interface Props {
|
|||||||
multiple?: boolean
|
multiple?: boolean
|
||||||
options: { selected: boolean; content: string }[]
|
options: { selected: boolean; content: string }[]
|
||||||
setOptions: React.Dispatch<React.SetStateAction<{ selected: boolean; content: string }[]>>
|
setOptions: React.Dispatch<React.SetStateAction<{ selected: boolean; content: string }[]>>
|
||||||
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Selections: React.FC<Props> = ({ multiple = false, options, setOptions }) => {
|
const Selections: React.FC<Props> = ({
|
||||||
|
multiple = false,
|
||||||
|
options,
|
||||||
|
setOptions,
|
||||||
|
disabled = false
|
||||||
|
}) => {
|
||||||
const { colors } = useTheme()
|
const { colors } = useTheme()
|
||||||
|
|
||||||
const isSelected = (index: number): string =>
|
const isSelected = (index: number): string =>
|
||||||
@ -22,10 +28,11 @@ const Selections: React.FC<Props> = ({ multiple = false, options, setOptions })
|
|||||||
: `${multiple ? 'Square' : 'Circle'}`
|
: `${multiple ? 'Square' : 'Circle'}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<View>
|
||||||
{options.map((option, index) => (
|
{options.map((option, index) => (
|
||||||
<Pressable
|
<Pressable
|
||||||
key={index}
|
key={index}
|
||||||
|
disabled={disabled}
|
||||||
style={{ flex: 1, paddingVertical: StyleConstants.Spacing.S }}
|
style={{ flex: 1, paddingVertical: StyleConstants.Spacing.S }}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
if (multiple) {
|
if (multiple) {
|
||||||
@ -56,15 +63,18 @@ const Selections: React.FC<Props> = ({ multiple = false, options, setOptions })
|
|||||||
}}
|
}}
|
||||||
name={isSelected(index)}
|
name={isSelected(index)}
|
||||||
size={StyleConstants.Font.Size.M}
|
size={StyleConstants.Font.Size.M}
|
||||||
color={colors.primaryDefault}
|
color={disabled ? colors.disabled : colors.primaryDefault}
|
||||||
/>
|
/>
|
||||||
<CustomText style={{ flex: 1 }}>
|
<CustomText style={{ flex: 1 }}>
|
||||||
<ParseEmojis content={option.content} />
|
<ParseEmojis
|
||||||
|
content={option.content}
|
||||||
|
style={{ color: disabled ? colors.disabled : colors.primaryDefault }}
|
||||||
|
/>
|
||||||
</CustomText>
|
</CustomText>
|
||||||
</View>
|
</View>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
))}
|
))}
|
||||||
</>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,8 +5,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useContext } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Platform, StyleSheet, View } from 'react-native'
|
import { Platform } from 'react-native'
|
||||||
import { Path, Svg } from 'react-native-svg'
|
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { isRtlLang } from 'rtl-detect'
|
import { isRtlLang } from 'rtl-detect'
|
||||||
import StatusContext from './Context'
|
import StatusContext from './Context'
|
||||||
@ -45,7 +44,14 @@ const TimelineContent: React.FC<Props> = ({ notificationOwnToot = false, setSpoi
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{inThread ? (
|
{inThread ? (
|
||||||
<CustomText fontStyle='S' style={{ textAlign: 'center', color: colors.secondary, paddingVertical: StyleConstants.Spacing.XS }}>
|
<CustomText
|
||||||
|
fontStyle='S'
|
||||||
|
style={{
|
||||||
|
textAlign: 'center',
|
||||||
|
color: colors.secondary,
|
||||||
|
paddingVertical: StyleConstants.Spacing.XS
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t('shared.content.expandHint')}
|
{t('shared.content.expandHint')}
|
||||||
</CustomText>
|
</CustomText>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -28,6 +28,7 @@ const TimelineHeaderAndroid: React.FC = () => {
|
|||||||
type: 'status',
|
type: 'status',
|
||||||
openChange,
|
openChange,
|
||||||
account: status.account,
|
account: status.account,
|
||||||
|
...(status && { status }),
|
||||||
queryKey
|
queryKey
|
||||||
})
|
})
|
||||||
const mStatus = menuStatus({ status, queryKey, rootQueryKey })
|
const mStatus = menuStatus({ status, queryKey, rootQueryKey })
|
||||||
|
@ -34,6 +34,7 @@ const TimelineHeaderDefault: React.FC = () => {
|
|||||||
type: 'status',
|
type: 'status',
|
||||||
openChange,
|
openChange,
|
||||||
account: status.account,
|
account: status.account,
|
||||||
|
...(status && { status }),
|
||||||
queryKey
|
queryKey
|
||||||
})
|
})
|
||||||
const mStatus = menuStatus({ status, queryKey, rootQueryKey })
|
const mStatus = menuStatus({ status, queryKey, rootQueryKey })
|
||||||
|
@ -42,6 +42,7 @@ const TimelineHeaderNotification: React.FC<Props> = ({ notification }) => {
|
|||||||
type: 'status',
|
type: 'status',
|
||||||
openChange,
|
openChange,
|
||||||
account: status?.account,
|
account: status?.account,
|
||||||
|
...(status && { status }),
|
||||||
queryKey
|
queryKey
|
||||||
})
|
})
|
||||||
const mStatus = menuStatus({ status, queryKey })
|
const mStatus = menuStatus({ status, queryKey })
|
||||||
|
@ -24,12 +24,14 @@ const menuAccount = ({
|
|||||||
type,
|
type,
|
||||||
openChange,
|
openChange,
|
||||||
account,
|
account,
|
||||||
|
status,
|
||||||
queryKey,
|
queryKey,
|
||||||
rootQueryKey
|
rootQueryKey
|
||||||
}: {
|
}: {
|
||||||
type: 'status' | 'account' // Where the action is coming from
|
type: 'status' | 'account' // Where the action is coming from
|
||||||
openChange: boolean
|
openChange: boolean
|
||||||
account?: Pick<Mastodon.Account, 'id' | 'username'>
|
account?: Mastodon.Account
|
||||||
|
status?: Mastodon.Status
|
||||||
queryKey?: QueryKeyTimeline
|
queryKey?: QueryKeyTimeline
|
||||||
rootQueryKey?: QueryKeyTimeline
|
rootQueryKey?: QueryKeyTimeline
|
||||||
}): ContextMenu[][] => {
|
}): ContextMenu[][] => {
|
||||||
@ -214,34 +216,7 @@ const menuAccount = ({
|
|||||||
{
|
{
|
||||||
key: 'account-reports',
|
key: 'account-reports',
|
||||||
item: {
|
item: {
|
||||||
onSelect: () =>
|
onSelect: () => navigation.navigate('Tab-Shared-Report', { account, status }),
|
||||||
Alert.alert(
|
|
||||||
t('account.reports.alert.title', { username: account.username }),
|
|
||||||
undefined,
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: t('common:buttons.confirm'),
|
|
||||||
style: 'destructive',
|
|
||||||
onPress: () => {
|
|
||||||
timelineMutation.mutate({
|
|
||||||
type: 'updateAccountProperty',
|
|
||||||
queryKey,
|
|
||||||
id: account.id,
|
|
||||||
payload: { property: 'reports' }
|
|
||||||
})
|
|
||||||
timelineMutation.mutate({
|
|
||||||
type: 'updateAccountProperty',
|
|
||||||
queryKey,
|
|
||||||
id: account.id,
|
|
||||||
payload: { property: 'block', currentValue: false }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: t('common:buttons.cancel')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
),
|
|
||||||
disabled: false,
|
disabled: false,
|
||||||
destructive: true,
|
destructive: true,
|
||||||
hidden: false
|
hidden: false
|
||||||
|
@ -357,6 +357,25 @@
|
|||||||
"history": {
|
"history": {
|
||||||
"name": "Edit History"
|
"name": "Edit History"
|
||||||
},
|
},
|
||||||
|
"report": {
|
||||||
|
"name": "Report {{acct}}",
|
||||||
|
"report": "Report",
|
||||||
|
"forward": {
|
||||||
|
"heading": "Anonymously forward to remote server {{instance}}"
|
||||||
|
},
|
||||||
|
"reasons": {
|
||||||
|
"heading": "What is going on with this account?",
|
||||||
|
"spam": "It is spam",
|
||||||
|
"other": "It is something else",
|
||||||
|
"violation": "It violates server rules"
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
"heading": "Anything else you want to add?"
|
||||||
|
},
|
||||||
|
"violatedRules": {
|
||||||
|
"heading": "Violated server rules"
|
||||||
|
}
|
||||||
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"header": {
|
"header": {
|
||||||
"prefix": "Searching",
|
"prefix": "Searching",
|
||||||
|
218
src/screens/Tabs/Shared/Report.tsx
Normal file
218
src/screens/Tabs/Shared/Report.tsx
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
import apiInstance from '@api/instance'
|
||||||
|
import ComponentAccount from '@components/Account'
|
||||||
|
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||||
|
import Selections from '@components/Selections'
|
||||||
|
import CustomText from '@components/Text'
|
||||||
|
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
|
||||||
|
import { useRulesQuery } from '@utils/queryHooks/reports'
|
||||||
|
import { getInstanceUri } from '@utils/slices/instancesSlice'
|
||||||
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Platform, ScrollView, TextInput, View } from 'react-native'
|
||||||
|
import { Switch } from 'react-native-gesture-handler'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
|
const TabSharedReport: React.FC<TabSharedStackScreenProps<'Tab-Shared-Report'>> = ({
|
||||||
|
navigation,
|
||||||
|
route: {
|
||||||
|
params: { account, status }
|
||||||
|
}
|
||||||
|
}) => {
|
||||||
|
const { colors } = useTheme()
|
||||||
|
const { t } = useTranslation('screenTabs')
|
||||||
|
|
||||||
|
const [categories, setCategories] = useState<
|
||||||
|
{ selected: boolean; content: string; type: 'spam' | 'other' | 'violation' }[]
|
||||||
|
>([
|
||||||
|
{ selected: true, content: t('shared.report.reasons.spam'), type: 'spam' },
|
||||||
|
{ selected: false, content: t('shared.report.reasons.other'), type: 'other' },
|
||||||
|
{ selected: false, content: t('shared.report.reasons.violation'), type: 'violation' }
|
||||||
|
])
|
||||||
|
const [rules, setRules] = useState<{ selected: boolean; content: string; id: string }[]>([])
|
||||||
|
const [forward, setForward] = useState<boolean>(true)
|
||||||
|
const [comment, setComment] = useState('')
|
||||||
|
|
||||||
|
const [isReporting, setIsReporting] = useState(false)
|
||||||
|
useEffect(() => {
|
||||||
|
navigation.setOptions({
|
||||||
|
title: t('shared.report.name', { acct: `@${account.acct}` }),
|
||||||
|
headerLeft: () => (
|
||||||
|
<HeaderLeft
|
||||||
|
type='text'
|
||||||
|
content={t('common:buttons.cancel')}
|
||||||
|
onPress={() => navigation.goBack()}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
headerRight: () => (
|
||||||
|
<HeaderRight
|
||||||
|
type='text'
|
||||||
|
content={t('shared.report.report')}
|
||||||
|
destructive
|
||||||
|
onPress={() => {
|
||||||
|
const body = new FormData()
|
||||||
|
body.append('account_id', account.id)
|
||||||
|
status && body.append('status_ids[]', status.id)
|
||||||
|
comment.length && body.append('comment', comment)
|
||||||
|
body.append('forward', forward.toString())
|
||||||
|
body.append('category', categories.find(category => category.selected)?.type || 'other')
|
||||||
|
rules.filter(rule => rule.selected).forEach(rule => body.append('rule_ids[]', rule.id))
|
||||||
|
|
||||||
|
apiInstance({ method: 'post', url: 'reports', body })
|
||||||
|
.then(() => {
|
||||||
|
setIsReporting(false)
|
||||||
|
navigation.pop(1)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setIsReporting(false)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
loading={isReporting}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}, [isReporting, comment, forward, categories, rules])
|
||||||
|
|
||||||
|
const instanceUri = useSelector(getInstanceUri)
|
||||||
|
const localInstance = account?.acct.includes('@')
|
||||||
|
? account?.acct.includes(`@${instanceUri}`)
|
||||||
|
: true
|
||||||
|
|
||||||
|
const rulesQuery = useRulesQuery()
|
||||||
|
useEffect(() => {
|
||||||
|
if (rulesQuery.data) {
|
||||||
|
setRules(rulesQuery.data.map(rule => ({ ...rule, selected: false, content: rule.text })))
|
||||||
|
}
|
||||||
|
}, [rulesQuery.data])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollView>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
margin: StyleConstants.Spacing.Global.PagePadding,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: colors.red,
|
||||||
|
borderRadius: 8
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ComponentAccount account={account} props={{}} />
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding,
|
||||||
|
marginTop: StyleConstants.Spacing.M
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!localInstance ? (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: StyleConstants.Spacing.L
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CustomText
|
||||||
|
fontStyle='M'
|
||||||
|
style={{ color: colors.primaryDefault, paddingRight: StyleConstants.Spacing.M }}
|
||||||
|
numberOfLines={2}
|
||||||
|
>
|
||||||
|
{t('shared.report.forward.heading', { instance: account.acct.match(/@(.*)/)?.[1] })}
|
||||||
|
</CustomText>
|
||||||
|
<Switch
|
||||||
|
value={forward}
|
||||||
|
onValueChange={setForward}
|
||||||
|
trackColor={{ true: colors.blue, false: colors.disabled }}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<CustomText
|
||||||
|
fontStyle='M'
|
||||||
|
style={{ color: colors.primaryDefault, marginBottom: StyleConstants.Spacing.S }}
|
||||||
|
>
|
||||||
|
{t('shared.report.reasons.heading')}
|
||||||
|
</CustomText>
|
||||||
|
<View style={{ marginLeft: StyleConstants.Spacing.M }}>
|
||||||
|
<Selections options={categories} setOptions={setCategories} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{categories[1].selected || comment.length ? (
|
||||||
|
<>
|
||||||
|
<CustomText
|
||||||
|
fontStyle='M'
|
||||||
|
style={{
|
||||||
|
color: colors.primaryDefault,
|
||||||
|
marginTop: StyleConstants.Spacing.M,
|
||||||
|
marginBottom: StyleConstants.Spacing.XS
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('shared.report.comment.heading')}
|
||||||
|
</CustomText>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
borderWidth: 1,
|
||||||
|
marginVertical: StyleConstants.Spacing.S,
|
||||||
|
padding: StyleConstants.Spacing.S,
|
||||||
|
borderColor: colors.border,
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'stretch'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
fontSize: StyleConstants.Font.Size.M,
|
||||||
|
color: colors.primaryDefault,
|
||||||
|
minHeight:
|
||||||
|
Platform.OS === 'ios' ? StyleConstants.Font.LineHeight.M * 5 : undefined
|
||||||
|
}}
|
||||||
|
value={comment}
|
||||||
|
onChangeText={setComment}
|
||||||
|
multiline={true}
|
||||||
|
numberOfLines={Platform.OS === 'android' ? 5 : undefined}
|
||||||
|
textAlignVertical='top'
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View style={{ flexDirection: 'row', alignSelf: 'flex-end' }}>
|
||||||
|
<CustomText
|
||||||
|
fontStyle='S'
|
||||||
|
style={{ paddingLeft: StyleConstants.Spacing.XS, color: colors.secondary }}
|
||||||
|
>
|
||||||
|
{comment.length} / 1000
|
||||||
|
</CustomText>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{rules.length ? (
|
||||||
|
<>
|
||||||
|
<CustomText
|
||||||
|
fontStyle='M'
|
||||||
|
style={{
|
||||||
|
color: categories[2].selected ? colors.primaryDefault : colors.disabled,
|
||||||
|
marginTop: StyleConstants.Spacing.M,
|
||||||
|
marginBottom: StyleConstants.Spacing.S
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('shared.report.violatedRules.heading')}
|
||||||
|
</CustomText>
|
||||||
|
<View style={{ marginLeft: StyleConstants.Spacing.M }}>
|
||||||
|
<Selections
|
||||||
|
disabled={!categories[2].selected}
|
||||||
|
multiple
|
||||||
|
options={rules}
|
||||||
|
setOptions={setRules}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TabSharedReport
|
@ -1,13 +1,14 @@
|
|||||||
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
||||||
import TabSharedAccount from '@screens/Tabs/Shared/Account'
|
import TabSharedAccount from '@screens/Tabs/Shared/Account'
|
||||||
|
import TabSharedAccountInLists from '@screens/Tabs/Shared/AccountInLists'
|
||||||
import TabSharedAttachments from '@screens/Tabs/Shared/Attachments'
|
import TabSharedAttachments from '@screens/Tabs/Shared/Attachments'
|
||||||
import TabSharedHashtag from '@screens/Tabs/Shared/Hashtag'
|
import TabSharedHashtag from '@screens/Tabs/Shared/Hashtag'
|
||||||
import TabSharedHistory from '@screens/Tabs/Shared/History'
|
import TabSharedHistory from '@screens/Tabs/Shared/History'
|
||||||
|
import TabSharedReport from '@screens/Tabs/Shared/Report'
|
||||||
import TabSharedSearch from '@screens/Tabs/Shared/Search'
|
import TabSharedSearch from '@screens/Tabs/Shared/Search'
|
||||||
import TabSharedToot from '@screens/Tabs/Shared/Toot'
|
import TabSharedToot from '@screens/Tabs/Shared/Toot'
|
||||||
import TabSharedUsers from '@screens/Tabs/Shared/Users'
|
import TabSharedUsers from '@screens/Tabs/Shared/Users'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import TabSharedAccountInLists from './AccountInLists'
|
|
||||||
|
|
||||||
const TabShared = ({ Stack }: { Stack: ReturnType<typeof createNativeStackNavigator> }) => {
|
const TabShared = ({ Stack }: { Stack: ReturnType<typeof createNativeStackNavigator> }) => {
|
||||||
return (
|
return (
|
||||||
@ -37,6 +38,12 @@ const TabShared = ({ Stack }: { Stack: ReturnType<typeof createNativeStackNaviga
|
|||||||
name='Tab-Shared-History'
|
name='Tab-Shared-History'
|
||||||
component={TabSharedHistory}
|
component={TabSharedHistory}
|
||||||
/>
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
key='Tab-Shared-Report'
|
||||||
|
name='Tab-Shared-Report'
|
||||||
|
component={TabSharedReport}
|
||||||
|
options={{ presentation: 'modal' }}
|
||||||
|
/>
|
||||||
<Stack.Screen key='Tab-Shared-Search' name='Tab-Shared-Search' component={TabSharedSearch} />
|
<Stack.Screen key='Tab-Shared-Search' name='Tab-Shared-Search' component={TabSharedSearch} />
|
||||||
<Stack.Screen key='Tab-Shared-Toot' name='Tab-Shared-Toot' component={TabSharedToot} />
|
<Stack.Screen key='Tab-Shared-Toot' name='Tab-Shared-Toot' component={TabSharedToot} />
|
||||||
<Stack.Screen key='Tab-Shared-Users' name='Tab-Shared-Users' component={TabSharedUsers} />
|
<Stack.Screen key='Tab-Shared-Users' name='Tab-Shared-Users' component={TabSharedUsers} />
|
||||||
|
@ -92,7 +92,7 @@ export type ScreenTabsScreenProps<T extends keyof ScreenTabsStackParamList> = Bo
|
|||||||
|
|
||||||
export type TabSharedStackParamList = {
|
export type TabSharedStackParamList = {
|
||||||
'Tab-Shared-Account': {
|
'Tab-Shared-Account': {
|
||||||
account: Mastodon.Account | Mastodon.Mention
|
account: Partial<Mastodon.Account> & Pick<Mastodon.Account, 'id'>
|
||||||
}
|
}
|
||||||
'Tab-Shared-Account-In-Lists': {
|
'Tab-Shared-Account-In-Lists': {
|
||||||
account: Pick<Mastodon.Account, 'id' | 'username'>
|
account: Pick<Mastodon.Account, 'id' | 'username'>
|
||||||
@ -105,6 +105,7 @@ export type TabSharedStackParamList = {
|
|||||||
id: Mastodon.Status['id']
|
id: Mastodon.Status['id']
|
||||||
detectedLanguage: string
|
detectedLanguage: string
|
||||||
}
|
}
|
||||||
|
'Tab-Shared-Report': { account: Mastodon.Account; status?: Pick<Mastodon.Status, 'id'> }
|
||||||
'Tab-Shared-Search': undefined
|
'Tab-Shared-Search': undefined
|
||||||
'Tab-Shared-Toot': {
|
'Tab-Shared-Toot': {
|
||||||
toot: Mastodon.Status
|
toot: Mastodon.Status
|
||||||
|
18
src/utils/queryHooks/reports.ts
Normal file
18
src/utils/queryHooks/reports.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import apiInstance from '@api/instance'
|
||||||
|
import { AxiosError } from 'axios'
|
||||||
|
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
|
||||||
|
|
||||||
|
export type QueryKeyRules = ['Rules']
|
||||||
|
|
||||||
|
const queryFunction = () =>
|
||||||
|
apiInstance<Mastodon.Rule[]>({
|
||||||
|
method: 'get',
|
||||||
|
url: 'instance/rules'
|
||||||
|
}).then(res => res.body)
|
||||||
|
|
||||||
|
const useRulesQuery = (params?: { options?: UseQueryOptions<Mastodon.Rule[], AxiosError> }) => {
|
||||||
|
const queryKey: QueryKeyRules = ['Rules']
|
||||||
|
return useQuery(queryKey, queryFunction, params?.options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { useRulesQuery }
|
Loading…
x
Reference in New Issue
Block a user