1
0
mirror of https://github.com/tooot-app/app synced 2025-06-05 22:19:13 +02:00

Merge branch 'main' into release

This commit is contained in:
xmflsct
2023-03-19 23:36:18 +01:00
13 changed files with 203 additions and 278 deletions

View File

@ -0,0 +1,38 @@
diff --git a/src/zoom.tsx b/src/zoom.tsx
index 70ce1c8d6a43e711f06b93d1eda3b44a3ad9a659..cdc2713470f2d332b8bf3e9c97e38fd9b78281df 100644
--- a/src/zoom.tsx
+++ b/src/zoom.tsx
@@ -4,6 +4,7 @@ import Animated, {
useSharedValue,
useAnimatedStyle,
useDerivedValue,
+ withDecay,
withTiming,
cancelAnimation,
runOnJS,
@@ -120,11 +121,22 @@ export function Zoom(props: Props) {
}
}
})
- .onEnd(() => {
+ .onEnd((event) => {
if (isPinching.value || !isZoomed.value) return;
- panTranslateX.value = 0;
- panTranslateY.value = 0;
+ const maxTranslateX = (viewWidth.value / 2) * scale.value - viewWidth.value / 2;
+ const minTranslateX = -maxTranslateX;
+ translationX.value = withDecay({
+ velocity: event.velocityX,
+ clamp: [minTranslateX, maxTranslateX]
+ });
+
+ const maxTranslateY = (viewHeight.value / 2) * scale.value - viewHeight.value / 2;
+ const minTranslateY = -maxTranslateY;
+ translationY.value = withDecay({
+ velocity: event.velocityY,
+ clamp: [minTranslateY, maxTranslateY]
+ });
});
const pinch = Gesture.Pinch()

View File

@ -35,6 +35,7 @@
<array>
<string>tooot-share</string>
<string>tooot</string>
<string>https</string>
</array>
</dict>
</array>

View File

@ -1,6 +1,6 @@
{
"name": "tooot",
"version": "4.9.2",
"version": "4.9.3",
"description": "tooot for Mastodon",
"author": "xmflsct <me@xmflsct.com>",
"license": "GPL-3.0-or-later",
@ -114,6 +114,7 @@
"expo-av@^13.0.2": "patch:expo-av@npm%3A13.0.2#./.yarn/patches/expo-av-npm-13.0.2-7a651776f1.patch",
"react-native-share-menu@^6.0.0": "patch:react-native-share-menu@npm%3A6.0.0#./.yarn/patches/react-native-share-menu-npm-6.0.0-f1094c3204.patch",
"@types/react-native-share-menu@^5.0.2": "patch:@types/react-native-share-menu@npm%3A5.0.2#./.yarn/patches/@types-react-native-share-menu-npm-5.0.2-373df17ecc.patch",
"react-native-ios-context-menu@^1.15.1": "patch:react-native-ios-context-menu@npm%3A1.15.1#./.yarn/patches/react-native-ios-context-menu-npm-1.15.1-0034bfa5ba.patch"
"react-native-ios-context-menu@^1.15.1": "patch:react-native-ios-context-menu@npm%3A1.15.1#./.yarn/patches/react-native-ios-context-menu-npm-1.15.1-0034bfa5ba.patch",
"react-native-reanimated-zoom@^0.3.3": "patch:react-native-reanimated-zoom@npm%3A0.3.3#./.yarn/patches/react-native-reanimated-zoom-npm-0.3.3-bbb8d84109.patch"
}
}

View File

@ -1,6 +1,3 @@
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'
@ -19,9 +16,8 @@ import { usePreferencesQuery } from '@utils/queryHooks/preferences'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { Fragment, useRef, useState } from 'react'
import React, { useRef, useState } from 'react'
import { Pressable, StyleProp, View, ViewStyle } from 'react-native'
import * as ContextMenu from 'zeego/context-menu'
import StatusContext from './Shared/Context'
import TimelineFeedback from './Shared/Feedback'
import TimelineFiltered, { FilteredProps, shouldFilter } from './Shared/Filtered'
@ -126,15 +122,6 @@ const TimelineDefault: React.FC<Props> = ({
</>
)
const mShare = menuShare({
visibility: status.visibility,
type: 'status',
url: status.url || status.uri,
rawContent
})
const mStatus = menuStatus({ status, queryKey })
const mInstance = menuInstance({ status, queryKey })
if (!isMyAccount) {
let filterResults: FilteredProps['filterResults'] = []
const [filterRevealed, setFilterRevealed] = useState(false)
@ -183,67 +170,14 @@ const TimelineDefault: React.FC<Props> = ({
<View style={mainStyle}>{main()}</View>
) : (
<>
<ContextMenu.Root>
<ContextMenu.Trigger>
<Pressable
accessible={highlighted ? false : true}
style={mainStyle}
disabled={highlighted}
onPress={() => navigation.push('Tab-Shared-Toot', { toot: status })}
onLongPress={() => {}}
children={main()}
/>
</ContextMenu.Trigger>
<ContextMenu.Content>
{[mShare, mStatus, mInstance].map((menu, i) => (
<Fragment key={i}>
{menu.map((group, index) => (
<ContextMenu.Group key={index}>
{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>
))}
</ContextMenu.Content>
</ContextMenu.Root>
<Pressable
accessible={highlighted ? false : true}
style={mainStyle}
disabled={highlighted}
onPress={() => navigation.push('Tab-Shared-Toot', { toot: status })}
onLongPress={() => {}}
children={main()}
/>
<TimelineHeaderAndroid />
</>
)}

View File

@ -1,6 +1,3 @@
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'
@ -18,9 +15,8 @@ import { usePreferencesQuery } from '@utils/queryHooks/preferences'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import React, { Fragment, useState } from 'react'
import React, { useState } from 'react'
import { Pressable, View } from 'react-native'
import * as ContextMenu from 'zeego/context-menu'
import StatusContext from './Shared/Context'
import TimelineFiltered, { FilteredProps, shouldFilter } from './Shared/Filtered'
import TimelineFullConversation from './Shared/FullConversation'
@ -100,14 +96,6 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
)
}
const mShare = menuShare({
visibility: notification.status?.visibility,
type: 'status',
url: notification.status?.url || notification.status?.uri
})
const mStatus = menuStatus({ status: notification.status, queryKey })
const mInstance = menuInstance({ status: notification.status, queryKey })
if (!isMyAccount) {
let filterResults: FilteredProps['filterResults'] = []
const [filterRevealed, setFilterRevealed] = useState(false)
@ -143,67 +131,18 @@ const TimelineNotifications: React.FC<Props> = ({ notification, queryKey }) => {
spoilerHidden
}}
>
<ContextMenu.Root>
<ContextMenu.Trigger>
<Pressable
style={{
padding: StyleConstants.Spacing.Global.PagePadding,
backgroundColor: colors.backgroundDefault,
paddingBottom: notification.status ? 0 : StyleConstants.Spacing.Global.PagePadding
}}
onPress={() =>
notification.status &&
navigation.push('Tab-Shared-Toot', { toot: notification.status })
}
onLongPress={() => {}}
children={main()}
/>
</ContextMenu.Trigger>
<ContextMenu.Content>
{[mShare, mStatus, mInstance].map((menu, i) => (
<Fragment key={i}>
{menu.map((group, index) => (
<ContextMenu.Group key={index}>
{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>
))}
</ContextMenu.Content>
</ContextMenu.Root>
<Pressable
style={{
padding: StyleConstants.Spacing.Global.PagePadding,
backgroundColor: colors.backgroundDefault,
paddingBottom: notification.status ? 0 : StyleConstants.Spacing.Global.PagePadding
}}
onPress={() =>
notification.status && navigation.push('Tab-Shared-Toot', { toot: notification.status })
}
onLongPress={() => {}}
children={main()}
/>
<TimelineHeaderAndroid />
</StatusContext.Provider>
)

View File

@ -17,7 +17,15 @@ export const CardNeodb: React.FC<Props> = ({ card }) => {
const { colors } = useTheme()
const segments = Linking.parse(card.url).path?.split('/')
if (!segments || !(segments[0] === 'movie' || segments[0] === 'book' || segments[0] === 'tv'))
if (
!segments ||
!(
segments[0] === 'movie' ||
segments[0] === 'book' ||
(segments[0] === 'tv' && segments[1] !== 'season') ||
segments[0] === 'game'
)
)
return null
const [headingLines, setHeadingLines] = useState(3)
@ -26,121 +34,104 @@ export const CardNeodb: React.FC<Props> = ({ card }) => {
if (!data) return null
const pressableProps = {
style: {
marginTop: StyleConstants.Spacing.M,
backgroundColor: colors.shimmerDefault,
borderRadius: StyleConstants.BorderRadius,
padding: StyleConstants.Spacing.S,
flexDirection: 'row' as 'row'
},
onPress: () => openLink(card.url)
}
const contentProps = { style: { flex: 1, gap: StyleConstants.Spacing.S } }
const itemImage = data.cover_image_url ? (
<GracefullyImage
sources={{ default: { uri: data.cover_image_url } }}
dimension={{
width: StyleConstants.Font.LineHeight.M * 4,
height: StyleConstants.Font.LineHeight.M * 5
const Content = ({ heading, details }: { heading: string[]; details: string[] }) => (
<Pressable
style={{
marginTop: StyleConstants.Spacing.M,
backgroundColor: colors.shimmerDefault,
borderRadius: StyleConstants.BorderRadius,
padding: StyleConstants.Spacing.S,
flexDirection: 'row'
}}
style={{ marginRight: StyleConstants.Spacing.S }}
imageStyle={{ borderRadius: StyleConstants.BorderRadius / 2 }}
dim
/>
) : null
const itemHeading = (value: string) => (
<CustomText
fontStyle='S'
fontWeight='Bold'
style={{ color: colors.primaryDefault }}
numberOfLines={3}
children={value}
onTextLayout={({ nativeEvent }) => setHeadingLines(nativeEvent.lines.length)}
/>
)
const itemDetails = (value: string) => (
<CustomText
fontStyle='S'
style={{ color: colors.secondary }}
numberOfLines={4 - headingLines}
children={value}
/>
onPress={() => openLink(card.url)}
>
{data.cover_image_url ? (
<GracefullyImage
sources={{ default: { uri: data.cover_image_url } }}
dimension={{
width: StyleConstants.Font.LineHeight.M * 4,
height: StyleConstants.Font.LineHeight.M * 5
}}
style={{ marginRight: StyleConstants.Spacing.S }}
imageStyle={{ borderRadius: StyleConstants.BorderRadius / 2 }}
dim
/>
) : null}
<View style={{ flex: 1, gap: StyleConstants.Spacing.S, justifyContent: 'space-between' }}>
<View style={{ gap: StyleConstants.Spacing.S }}>
<CustomText
fontStyle='S'
fontWeight='Bold'
style={{ color: colors.primaryDefault }}
numberOfLines={3}
onTextLayout={({ nativeEvent }) => setHeadingLines(nativeEvent.lines.length)}
children={heading.filter(d => d).join(' ')}
/>
<Rating rating={data.rating / 2} />
</View>
<CustomText
fontStyle='S'
style={{ color: colors.secondary }}
numberOfLines={4 - headingLines}
children={details.filter(d => d).join(' / ')}
/>
</View>
</Pressable>
)
switch (segments[0]) {
case 'movie':
return (
<Pressable {...pressableProps}>
{itemImage}
<View {...contentProps}>
{itemHeading(
[data.title, data.orig_title, data.year ? `(${data.year})` : null]
.filter(d => d)
.join(' ')
)}
<Rating rating={data.rating / 2} />
{itemDetails(
[
data.duration
? parseInt(data.duration).toString() === data.duration
? `${data.duration}分钟`
: data.duration
: null,
data.area?.join(' '),
data.genre?.join(' '),
data.director?.join(' ')
]
.filter(d => d)
.join(' / ')
)}
</View>
</Pressable>
<Content
heading={[data.title, data.orig_title, data.year ? `(${data.year})` : null]}
details={[
data.duration
? parseInt(data.duration).toString() === data.duration
? `${data.duration}分钟`
: data.duration
: null,
data.area?.join(' '),
data.genre?.join(' '),
data.director?.join(' ')
]}
/>
)
case 'book':
return (
<Pressable {...pressableProps}>
{itemImage}
<View {...contentProps}>
{itemHeading(data.title)}
<Rating rating={data.rating / 2} />
{itemDetails(
[
data.author?.join(' '),
data.pages ? `${data.pages}` : null,
data.language,
data.pub_house
]
.filter(d => d)
.join(' / ')
)}
</View>
</Pressable>
<Content
heading={[data.title]}
details={[
data.author?.join(' '),
data.pages ? `${data.pages}` : null,
data.language,
data.pub_house
]}
/>
)
case 'tv':
return (
<Pressable {...pressableProps}>
{itemImage}
<View {...contentProps}>
{itemHeading(
[data.title, data.orig_title, data.year ? `(${data.year})` : null]
.filter(d => d)
.join(' ')
)}
<Rating rating={data.rating / 2} />
{itemDetails(
[
data.season_count ? `${data.season_count}` : null,
data.area?.join(' '),
data.genre?.join(' '),
data.director?.join(' ')
]
.filter(d => d)
.join(' / ')
)}
</View>
</Pressable>
<Content
heading={[data.title, data.orig_title, data.year ? `(${data.year})` : null]}
details={[
data.season_count ? `${data.season_count}` : null,
data.area?.join(' '),
data.genre?.join(' '),
data.director?.join(' ')
]}
/>
)
case 'game':
return (
<Content
heading={[data.title]}
details={[
data.genre?.join(' '),
data.developer?.join(' '),
data.platform?.join(' '),
data.release_date
]}
/>
)
default:
return null

View File

@ -4,6 +4,7 @@ import openLink from '@components/openLink'
import CustomText from '@components/Text'
import { useNavigation } from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack'
import { isDevelopment } from '@utils/helpers/checkEnvironment'
import { urlMatcher } from '@utils/helpers/urlMatcher'
import { TabLocalStackParamList } from '@utils/navigation/navigators'
import { useAccountQuery } from '@utils/queryHooks/account'
@ -17,14 +18,19 @@ import TimelineDefault from '../../Default'
import StatusContext from '../Context'
import { CardNeodb } from './Neodb'
const CARD_URL_BLACKLISTS = ['weibo.com', 'weibo.cn']
const TimelineCard: React.FC = () => {
const { status, spoilerHidden, disableDetails, inThread } = useContext(StatusContext)
if (!status || !status.card) return null
if (CARD_URL_BLACKLISTS.find(domain => status.card?.url.includes(`${domain}/`))) return null
const { i18n } = useTranslation()
if (
status.card.url.includes('://neodb.social/') &&
i18n.language.toLowerCase().startsWith('zh-hans')
(status.card.url.includes('://neodb.social/') &&
i18n.language.toLowerCase().startsWith('zh-hans')) ||
isDevelopment
) {
return <CardNeodb card={status.card} />
}

View File

@ -1,4 +1,5 @@
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'
@ -17,7 +18,8 @@ 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, highlighted } =
useContext(StatusContext)
if (!status) return null
const { colors } = useTheme()
@ -37,6 +39,7 @@ const TimelineHeaderDefault: React.FC = () => {
...(status && { status })
})
const mStatus = menuStatus({ status, queryKey })
const mInstance = highlighted ? menuInstance({ status, queryKey }) : []
return (
<View style={{ flex: 1, flexDirection: 'row' }}>
@ -87,7 +90,7 @@ const TimelineHeaderDefault: React.FC = () => {
</DropdownMenu.Trigger>
<DropdownMenu.Content>
{[mShare, mAccount, mStatus].map((menu, i) => (
{[mShare, mAccount, mStatus, mInstance].map((menu, i) => (
<Fragment key={i}>
{menu.map((group, index) => (
<DropdownMenu.Group key={index}>

View File

@ -387,12 +387,12 @@
"account": {
"actions": {
"accessibilityLabel": "Дзеянні для карыстальніка {{user}}",
"accessibilityHint": ""
"accessibilityHint": "Вы можаце ігнараваць, блакіраваць або абагуліць гэтага карыстальніка"
},
"followed_by": " падпісаны на вас",
"privateNote": "",
"moved": "",
"created_at": "",
"created_at": "Далучыўся: {{date}}",
"summary": {
"statuses_count": "{{count}} допісаў"
},
@ -467,7 +467,7 @@
"toot": {
"name": "Абмеркаванні",
"remoteFetch": {
"title": "",
"title": "Змяшчае аддаленае змесціва",
"message": ""
}
},

View File

@ -176,18 +176,14 @@ const TabMePreferencesFilter: React.FC<
...(parseInt(expiration) && {
expires_in: parseInt(expiration)
}),
...(keywords.filter(keyword => keyword.length).length
? {
keywords_attributes: keywords
.filter(keyword => keyword.length)
.map(keyword => ({ keyword, whole_word: true }))
}
: params.filter.keywords.length && {
keywords_attributes: params.filter.keywords.map(keyword => ({
...keyword,
_destroy: true
}))
})
keywords_attributes: keywords.map((keyword, index) =>
!!params.filter.keywords[index]
? {
id: params.filter.keywords[index].id,
...(keyword.length ? { keyword, whole_word: true } : { _destroy: true })
}
: { keyword, whole_word: true }
)
}
})
.then(() => {

View File

@ -37,9 +37,10 @@ const AccountInformationActions: React.FC = () => {
return (
<View style={styles.base}>
<Button
type='text'
round
type='icon'
disabled={account === undefined}
content={t('me.stacks.profile.name')}
content='edit-3'
onPress={() => navigation.navigate('Tab-Me-Profile')}
/>
<Button
@ -47,7 +48,7 @@ const AccountInformationActions: React.FC = () => {
type='icon'
disabled={account === undefined}
content='sliders'
style={{ marginLeft: StyleConstants.Spacing.S }}
style={{ marginLeft: StyleConstants.Spacing.M }}
onPress={() =>
navigation.navigate('Tab-Me-Preferences', { screen: 'Tab-Me-Preferences-Root' })
}

View File

@ -55,6 +55,9 @@ const TabSharedAccount: React.FC<TabSharedStackScreenProps<'Tab-Shared-Account'>
...(account._remote && { remote_id: account.id, remote_domain: account._remote })
}
]
useEffect(() => {
navigation.setParams({ queryKey: queryKeyDefault })
}, [dataUpdatedAt])
const mShare = menuShare({ type: 'account', url: data?.url })
const mAccount = menuAccount({ type: 'account', openChange: true, account: data })

View File

@ -9950,7 +9950,7 @@ __metadata:
languageName: node
linkType: hard
"react-native-reanimated-zoom@npm:^0.3.3":
"react-native-reanimated-zoom@npm:0.3.3":
version: 0.3.3
resolution: "react-native-reanimated-zoom@npm:0.3.3"
peerDependencies:
@ -9962,6 +9962,18 @@ __metadata:
languageName: node
linkType: hard
"react-native-reanimated-zoom@patch:react-native-reanimated-zoom@npm%3A0.3.3#./.yarn/patches/react-native-reanimated-zoom-npm-0.3.3-bbb8d84109.patch::locator=tooot%40workspace%3A.":
version: 0.3.3
resolution: "react-native-reanimated-zoom@patch:react-native-reanimated-zoom@npm%3A0.3.3#./.yarn/patches/react-native-reanimated-zoom-npm-0.3.3-bbb8d84109.patch::version=0.3.3&hash=187ed4&locator=tooot%40workspace%3A."
peerDependencies:
react: "*"
react-native: "*"
react-native-gesture-handler: "*"
react-native-reanimated: "*"
checksum: b43e7db64ad15cdb964da44b313205164ae74ebcde6baf01f3852b8cd733078b90d4ada140c78fad33ac2ba70f4db5b1d8644018f6ff885bbb998d2dd0211b05
languageName: node
linkType: hard
"react-native-reanimated@npm:^3.0.2":
version: 3.0.2
resolution: "react-native-reanimated@npm:3.0.2"