Merge branch 'main' into candidate

This commit is contained in:
xmflsct 2023-07-13 23:31:41 +02:00
commit 375023679b
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
12 changed files with 295 additions and 50 deletions

View File

@ -1 +1,4 @@
Enjoy toooting! This version includes following improvements and fixes:
- Supports mute duration
- Long press to copy toot
- Button to fetch latest on load

View File

@ -1,2 +1,5 @@
tooot-ing愉快此版本包括以下改进和修复
- 新增neodb.social演出卡片
- 新增neodb.social演出卡片
- 支持选择隐藏用户时限
- 长按复制嘟文
- 新增获取最新嘟文按钮

View File

@ -17,6 +17,7 @@ export type Props = {
loading?: boolean
disabled?: boolean
destructive?: boolean
destructiveColor?: string
onPress: () => void
} & ({ type?: undefined; content: IconName } | { type: 'text'; content: string })
@ -34,6 +35,7 @@ const HeaderRight: React.FC<Props> = ({
loading,
disabled,
destructive = false,
destructiveColor,
onPress
}) => {
const { colors } = useTheme()
@ -57,7 +59,7 @@ const HeaderRight: React.FC<Props> = ({
color: disabled
? colors.secondary
: destructive
? colors.red
? destructiveColor || colors.red
: colors.primaryDefault,
opacity: loading ? 0 : 1
}}

View File

@ -36,6 +36,7 @@ const TimelineContent: React.FC<Props> = ({ notificationOwnToot = false, setSpoi
adaptiveSize
numberOfLines={999}
color={suppressSpoiler ? colors.disabled : undefined}
selectable
/>
{inThread ? (
<CustomText
@ -62,6 +63,7 @@ const TimelineContent: React.FC<Props> = ({ notificationOwnToot = false, setSpoi
}
expandHint={t('shared.content.expandHint')}
setSpoilerExpanded={setSpoilerExpanded}
selectable
/>
</>
) : (
@ -70,6 +72,7 @@ const TimelineContent: React.FC<Props> = ({ notificationOwnToot = false, setSpoi
size={highlighted ? 'L' : 'M'}
adaptiveSize
numberOfLines={highlighted || inThread ? 999 : notificationOwnToot ? 2 : undefined}
selectable
/>
)}
</View>

View File

@ -1,3 +1,4 @@
import Icon from '@components/Icon'
import ComponentSeparator from '@components/Separator'
import CustomText from '@components/Text'
import TimelineDefault from '@components/Timeline/Default'
@ -10,12 +11,20 @@ import {
setAccountStorage,
useGlobalStorageListener
} from '@utils/storage/actions'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import { StyleConstants } from '@utils/styles/constants'
import { throttle } from 'lodash'
import React, { RefObject, useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { FlatList, FlatListProps, Platform, RefreshControl } from 'react-native'
import {
FlatList,
FlatListProps,
Platform,
Pressable,
RefreshControl,
StyleProp,
ViewStyle
} from 'react-native'
import Animated, {
Easing,
runOnJS,
@ -127,6 +136,20 @@ const Timeline: React.FC<Props> = ({
transform: [{ translateY: fetchedNoticeTop.value }]
}))
const refetchedNoticeAnimate = useAnimatedStyle(() => ({
transform: [
{
translateY: withSequence(
withTiming(0),
withDelay(
3000,
withTiming(fetchedNoticeHeight.value + 32, { easing: Easing.out(Easing.ease) })
)
)
}
]
}))
const scrollY = useSharedValue(0)
const fetchingType = useSharedValue<0 | 1 | 2>(0)
@ -169,10 +192,9 @@ const Timeline: React.FC<Props> = ({
throttle(() => {
if (readMarker) {
const currentMarker = getAccountStorage.string(readMarker) || '0'
// setAccountStorage([{ key: readMarker, value: '108425743226508521' }])
if (latestMarker.current > currentMarker) {
setAccountStorage([{ key: readMarker, value: latestMarker.current }])
} else {
// setAccountStorage([{ key: readMarker, value: '105250709762254246' }])
}
}
}, 1000 * 15),
@ -242,6 +264,18 @@ const Timeline: React.FC<Props> = ({
flRef.current?.scrollToOffset({ offset: 0, animated: false })
)
const noticeDefaults: StyleProp<Animated.AnimateStyle<StyleProp<ViewStyle>>> = {
position: 'absolute',
alignSelf: 'center',
borderRadius: 99,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: colors.backgroundDefault,
shadowColor: colors.primaryDefault,
shadowOffset: { width: 0, height: 0 },
shadowOpacity: theme === 'light' ? 0.16 : 0.24
}
return (
<>
<TimelineRefresh
@ -286,42 +320,78 @@ const Timeline: React.FC<Props> = ({
{...customProps}
/>
{!disableRefresh ? (
<Animated.View
style={[
{
position: 'absolute',
alignSelf: 'center',
top: -fetchedNoticeHeight.value - 16,
paddingVertical: StyleConstants.Spacing.S,
paddingHorizontal: StyleConstants.Spacing.M,
backgroundColor: colors.backgroundDefault,
shadowColor: colors.primaryDefault,
shadowOffset: { width: 0, height: 0 },
shadowOpacity: theme === 'light' ? 0.16 : 0.24,
borderRadius: 99,
justifyContent: 'center',
alignItems: 'center'
},
fetchedNoticeAnimate
]}
onLayout={({
nativeEvent: {
layout: { height }
}
}) => (fetchedNoticeHeight.value = height)}
>
<CustomText
fontStyle='S'
style={{ color: colors.primaryDefault }}
children={
fetchedCount !== null
? fetchedCount > 0
? t('refresh.fetched.found', { count: fetchedCount })
: t('refresh.fetched.none')
: t('refresh.fetching')
}
/>
</Animated.View>
<>
<Animated.View
style={[
{
top: -fetchedNoticeHeight.value - 16,
paddingVertical: StyleConstants.Spacing.S,
paddingHorizontal: StyleConstants.Spacing.M,
...noticeDefaults
},
fetchedNoticeAnimate
]}
onLayout={({
nativeEvent: {
layout: { height }
}
}) => (fetchedNoticeHeight.value = height)}
>
<CustomText
fontStyle='S'
style={{ color: colors.primaryDefault }}
children={
fetchedCount !== null
? fetchedCount > 0
? t('refresh.fetched.found', { count: fetchedCount })
: t('refresh.fetched.none')
: t('refresh.fetching')
}
/>
</Animated.View>
{readMarker ? (
<Animated.View
style={[
{
bottom: 16,
borderColor: colors.primaryDefault,
borderWidth: 0.5,
...noticeDefaults
},
refetchedNoticeAnimate
]}
>
<Pressable
style={{
flexDirection: 'row',
alignItems: 'center',
gap: StyleConstants.Spacing.S,
paddingVertical: StyleConstants.Spacing.S,
paddingHorizontal: StyleConstants.Spacing.M
}}
onPress={async () => {
if (readMarker) {
setAccountStorage([{ key: readMarker, value: undefined }])
}
await refetch()
setTimeout(() => flRef.current?.scrollToOffset({ offset: 0 }), 50)
}}
>
<CustomText
fontStyle='M'
style={{ color: colors.primaryDefault }}
children={t('refresh.refetch')}
/>
<Icon
name='log-in'
color={colors.primaryDefault}
size={StyleConstants.Font.Size.M}
style={{ transform: [{ rotate: '-90deg' }] }}
/>
</Pressable>
</Animated.View>
) : null}
</>
) : null}
</>
)

View File

@ -4,6 +4,7 @@ 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 { featureCheck } from '@utils/helpers/featureCheck'
import { checkIsMyAccount } from '@utils/helpers/isMyAccount'
import { TabSharedStackParamList, useNavState } from '@utils/navigation/navigators'
import { useAccountQuery } from '@utils/queryHooks/account'
@ -203,13 +204,21 @@ const menuAccount = ({
type: 'item',
key: 'account-mute',
props: {
onSelect: () =>
actualAccount &&
timelineMutation.mutate({
type: 'updateAccountProperty',
id: actualAccount.id,
payload: { property: 'mute', currentValue: data?.muting }
}),
onSelect: () => {
if (actualAccount) {
if (data?.muting !== true) {
if (featureCheck('mute_duration')) {
navigation.navigate('Tab-Shared-Mute', { account: actualAccount })
}
}
timelineMutation.mutate({
type: 'updateAccountProperty',
id: actualAccount.id,
payload: { property: 'mute', currentValue: data?.muting }
})
}
},
disabled: Platform.OS !== 'android' ? !data || !isFetched : false,
destructive: false,
hidden: false

View File

@ -417,6 +417,20 @@
"history": {
"name": "Edit History"
},
"mute": {
"name": "Mute {{acct}}",
"mute": "Mute",
"description": "Hide posts from this user and posts mentioning them, but it will still allow them to see your posts and follow you.",
"notification": "Also hide notifications from this user",
"duration": {
"heading": "For duration",
"0": "Indefinitely",
"1800": "30 minutes",
"3600": "1 hour",
"86400": "1 day",
"604800": "1 week"
}
},
"report": {
"name": "Report {{acct}}",
"report": "Report",

View File

@ -0,0 +1,130 @@
import ComponentAccount from '@components/Account'
import { HeaderLeft, HeaderRight } from '@components/Header'
import Icon from '@components/Icon'
import { displayMessage } from '@components/Message'
import { ModalScrollView } from '@components/ModalScrollView'
import Selections from '@components/Selections'
import CustomText from '@components/Text'
import { TabSharedStackScreenProps } from '@utils/navigation/navigators'
import { useTimelineMutation } from '@utils/queryHooks/timeline'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Pressable, View } from 'react-native'
const TabSharedMute: React.FC<TabSharedStackScreenProps<'Tab-Shared-Report'>> = ({
navigation,
route: {
params: { account }
}
}) => {
const { colors, theme } = useTheme()
const { t } = useTranslation(['common', 'screenTabs'])
const { mutateAsync, isLoading } = useTimelineMutation({
onSuccess: () =>
displayMessage({
type: 'success',
message: t('common:message.success.message', {
function: t('componentContextMenu:account.mute.action', {
defaultValue: 'false',
context: 'false'
})
})
})
})
const [durations, setDurations] = useState<{ selected: boolean; content: string }[]>(
(['0', '1800', '3600', '86400', '604800'] as ['0', '1800', '3600', '86400', '604800']).map(
duration => ({
selected: duration === '0',
content: t(`screenTabs:shared.mute.duration.${duration}`)
})
)
)
const [notification, setNotification] = useState(false)
useEffect(() => {
navigation.setOptions({
title: t('screenTabs:shared.mute.name', { acct: `@${account.acct}` }),
headerLeft: () => (
<HeaderLeft
type='text'
content={t('common:buttons.cancel')}
onPress={() => navigation.goBack()}
/>
),
headerRight: () => (
<HeaderRight
type='text'
content={t('screenTabs:shared.mute.mute')}
destructive
destructiveColor={colors.yellow}
onPress={async () => {
await mutateAsync({
type: 'updateAccountProperty',
id: account.id,
payload: { property: 'mute', currentValue: false }
})
navigation.pop(1)
}}
loading={isLoading}
/>
)
})
}, [theme, isLoading, durations, notification, account.id])
return (
<ModalScrollView>
<View
style={{
margin: StyleConstants.Spacing.Global.PagePadding,
borderWidth: 1,
borderColor: colors.yellow,
borderRadius: StyleConstants.BorderRadius
}}
>
<ComponentAccount account={account} props={{}} />
</View>
<View
style={{
paddingHorizontal: StyleConstants.Spacing.Global.PagePadding
}}
>
<CustomText
fontStyle='M'
style={{ color: colors.primaryDefault, marginBottom: StyleConstants.Spacing.M }}
>
{t('screenTabs:shared.mute.description')}
</CustomText>
<Selections
title={t('screenTabs:shared.mute.duration.heading')}
options={durations}
setOptions={setDurations}
/>
<Pressable
style={{ flex: 1, flexDirection: 'row', marginTop: StyleConstants.Spacing.M }}
onPress={() => setNotification(!notification)}
>
<Icon
style={{
marginTop: (StyleConstants.Font.LineHeight.M - StyleConstants.Font.Size.M) / 2,
marginRight: StyleConstants.Spacing.S
}}
name={notification ? 'check-square' : 'square'}
size={StyleConstants.Font.Size.M}
color={colors.primaryDefault}
/>
<CustomText fontStyle='M' style={{ color: colors.primaryDefault }}>
{t('screenTabs:shared.mute.notification')}
</CustomText>
</Pressable>
</View>
</ModalScrollView>
)
}
export default TabSharedMute

View File

@ -21,7 +21,6 @@ const TabSharedReport: React.FC<TabSharedStackScreenProps<'Tab-Shared-Report'>>
params: { account, status }
}
}) => {
console.log('account', account.id)
const { colors } = useTheme()
const { t } = useTranslation(['common', 'screenTabs'])

View File

@ -9,6 +9,7 @@ import TabSharedToot from '@screens/Tabs/Shared/Toot'
import TabSharedUsers from '@screens/Tabs/Shared/Users'
import React from 'react'
import TabSharedFilter from './Filter'
import TabSharedMute from './Mute'
const TabShared = ({ Stack }: { Stack: any }) => {
return (
@ -44,6 +45,12 @@ const TabShared = ({ Stack }: { Stack: any }) => {
name='Tab-Shared-History'
component={TabSharedHistory}
/>
<Stack.Screen
key='Tab-Shared-Mute'
name='Tab-Shared-Mute'
component={TabSharedMute}
options={{ presentation: 'modal' }}
/>
<Stack.Screen
key='Tab-Shared-Report'
name='Tab-Shared-Report'

View File

@ -4,6 +4,7 @@ type Features =
| 'account_follow_notify'
| 'notification_type_status'
| 'account_return_suspended'
| 'mute_duration'
| 'edit_post'
| 'deprecate_auth_follow'
| 'notification_type_update'
@ -20,6 +21,7 @@ const features: { feature: Features; version: number }[] = [
{ feature: 'account_follow_notify', version: 3.3 },
{ feature: 'notification_type_status', version: 3.3 },
{ feature: 'account_return_suspended', version: 3.3 },
{ feature: 'mute_duration', version: 3.3 },
{ feature: 'edit_post', version: 3.5 },
{ feature: 'deprecate_auth_follow', version: 3.5 },
{ feature: 'notification_type_update', version: 3.5 },

View File

@ -104,6 +104,9 @@ export type TabSharedStackParamList = {
| { source: 'hashtag'; tag_name: Mastodon.Tag['name'] }
'Tab-Shared-Hashtag': { tag_name: Mastodon.Tag['name']; queryKey?: QueryKeyTimeline }
'Tab-Shared-History': { status: Mastodon.Status; detectedLanguage: string }
'Tab-Shared-Mute': {
account: Pick<Mastodon.Account, 'id' | 'acct' | 'username' | 'url'>
}
'Tab-Shared-Report': {
account: Pick<Mastodon.Account, 'id' | 'acct' | 'username' | 'url'>
status?: Pick<Mastodon.Status, 'id' | '_remote' | 'uri'>