diff --git a/.yarn/patches/react-native-reanimated-zoom-npm-0.3.3-bbb8d84109.patch b/.yarn/patches/react-native-reanimated-zoom-npm-0.3.3-bbb8d84109.patch
new file mode 100644
index 00000000..ced94530
--- /dev/null
+++ b/.yarn/patches/react-native-reanimated-zoom-npm-0.3.3-bbb8d84109.patch
@@ -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()
diff --git a/ios/tooot/Info.plist b/ios/tooot/Info.plist
index a70dccd6..a7f281e3 100644
--- a/ios/tooot/Info.plist
+++ b/ios/tooot/Info.plist
@@ -35,6 +35,7 @@
tooot-share
tooot
+ https
diff --git a/package.json b/package.json
index 1a919d89..1c7747be 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "tooot",
- "version": "4.9.2",
+ "version": "4.9.3",
"description": "tooot for Mastodon",
"author": "xmflsct ",
"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"
}
}
diff --git a/src/components/Timeline/Default.tsx b/src/components/Timeline/Default.tsx
index 8fb6eb52..3c78fc74 100644
--- a/src/components/Timeline/Default.tsx
+++ b/src/components/Timeline/Default.tsx
@@ -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 = ({
>
)
- 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 = ({
{main()}
) : (
<>
-
-
- navigation.push('Tab-Shared-Toot', { toot: status })}
- onLongPress={() => {}}
- children={main()}
- />
-
-
-
- {[mShare, mStatus, mInstance].map((menu, i) => (
-
- {menu.map((group, index) => (
-
- {group.map(item => {
- switch (item.type) {
- case 'item':
- return (
-
-
- {item.icon ? (
-
- ) : null}
-
- )
- case 'sub':
- return (
- // @ts-ignore
-
-
-
- {item.trigger.icon ? (
-
- ) : null}
-
-
- {item.items.map(sub => (
-
-
- {sub.icon ? (
-
- ) : null}
-
- ))}
-
-
- )
- }
- })}
-
- ))}
-
- ))}
-
-
+ navigation.push('Tab-Shared-Toot', { toot: status })}
+ onLongPress={() => {}}
+ children={main()}
+ />
>
)}
diff --git a/src/components/Timeline/Notifications.tsx b/src/components/Timeline/Notifications.tsx
index c1030c56..ee1ddfe3 100644
--- a/src/components/Timeline/Notifications.tsx
+++ b/src/components/Timeline/Notifications.tsx
@@ -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 = ({ 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 = ({ notification, queryKey }) => {
spoilerHidden
}}
>
-
-
-
- notification.status &&
- navigation.push('Tab-Shared-Toot', { toot: notification.status })
- }
- onLongPress={() => {}}
- children={main()}
- />
-
-
-
- {[mShare, mStatus, mInstance].map((menu, i) => (
-
- {menu.map((group, index) => (
-
- {group.map(item => {
- switch (item.type) {
- case 'item':
- return (
-
-
- {item.icon ? : null}
-
- )
- case 'sub':
- return (
- // @ts-ignore
-
-
-
- {item.trigger.icon ? (
-
- ) : null}
-
-
- {item.items.map(sub => (
-
-
- {sub.icon ? (
-
- ) : null}
-
- ))}
-
-
- )
- }
- })}
-
- ))}
-
- ))}
-
-
+
+ notification.status && navigation.push('Tab-Shared-Toot', { toot: notification.status })
+ }
+ onLongPress={() => {}}
+ children={main()}
+ />
)
diff --git a/src/components/Timeline/Shared/Card/Neodb.tsx b/src/components/Timeline/Shared/Card/Neodb.tsx
index 69fe2018..0e8c453e 100644
--- a/src/components/Timeline/Shared/Card/Neodb.tsx
+++ b/src/components/Timeline/Shared/Card/Neodb.tsx
@@ -17,7 +17,15 @@ export const CardNeodb: React.FC = ({ 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 = ({ 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 ? (
- (
+
- ) : null
- const itemHeading = (value: string) => (
- setHeadingLines(nativeEvent.lines.length)}
- />
- )
- const itemDetails = (value: string) => (
-
+ onPress={() => openLink(card.url)}
+ >
+ {data.cover_image_url ? (
+
+ ) : null}
+
+
+ setHeadingLines(nativeEvent.lines.length)}
+ children={heading.filter(d => d).join(' ')}
+ />
+
+
+
+ d).join(' / ')}
+ />
+
+
)
switch (segments[0]) {
case 'movie':
return (
-
- {itemImage}
-
- {itemHeading(
- [data.title, data.orig_title, data.year ? `(${data.year})` : null]
- .filter(d => d)
- .join(' ')
- )}
-
- {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(' / ')
- )}
-
-
+
)
case 'book':
return (
-
- {itemImage}
-
- {itemHeading(data.title)}
-
- {itemDetails(
- [
- data.author?.join(' '),
- data.pages ? `${data.pages}页` : null,
- data.language,
- data.pub_house
- ]
- .filter(d => d)
- .join(' / ')
- )}
-
-
+
)
case 'tv':
return (
-
- {itemImage}
-
- {itemHeading(
- [data.title, data.orig_title, data.year ? `(${data.year})` : null]
- .filter(d => d)
- .join(' ')
- )}
-
- {itemDetails(
- [
- data.season_count ? `共${data.season_count}季` : null,
- data.area?.join(' '),
- data.genre?.join(' '),
- data.director?.join(' ')
- ]
- .filter(d => d)
- .join(' / ')
- )}
-
-
+
+ )
+ case 'game':
+ return (
+
)
default:
return null
diff --git a/src/components/Timeline/Shared/Card/index.tsx b/src/components/Timeline/Shared/Card/index.tsx
index 6d6ac8e5..5dc3012a 100644
--- a/src/components/Timeline/Shared/Card/index.tsx
+++ b/src/components/Timeline/Shared/Card/index.tsx
@@ -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
}
diff --git a/src/components/Timeline/Shared/HeaderDefault.tsx b/src/components/Timeline/Shared/HeaderDefault.tsx
index 33b9235c..a4b58d56 100644
--- a/src/components/Timeline/Shared/HeaderDefault.tsx
+++ b/src/components/Timeline/Shared/HeaderDefault.tsx
@@ -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 (
@@ -87,7 +90,7 @@ const TimelineHeaderDefault: React.FC = () => {
- {[mShare, mAccount, mStatus].map((menu, i) => (
+ {[mShare, mAccount, mStatus, mInstance].map((menu, i) => (
{menu.map((group, index) => (
diff --git a/src/i18n/be/screens/tabs.json b/src/i18n/be/screens/tabs.json
index 7df2598c..7aa5286a 100644
--- a/src/i18n/be/screens/tabs.json
+++ b/src/i18n/be/screens/tabs.json
@@ -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": ""
}
},
diff --git a/src/screens/Tabs/Me/Preferences/Filter.tsx b/src/screens/Tabs/Me/Preferences/Filter.tsx
index cf98bee7..d01d6ca5 100644
--- a/src/screens/Tabs/Me/Preferences/Filter.tsx
+++ b/src/screens/Tabs/Me/Preferences/Filter.tsx
@@ -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(() => {
diff --git a/src/screens/Tabs/Shared/Account/Information/Actions.tsx b/src/screens/Tabs/Shared/Account/Information/Actions.tsx
index a9948d9d..c312059b 100644
--- a/src/screens/Tabs/Shared/Account/Information/Actions.tsx
+++ b/src/screens/Tabs/Shared/Account/Information/Actions.tsx
@@ -37,9 +37,10 @@ const AccountInformationActions: React.FC = () => {
return (