mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Option to change how to handle links
This commit is contained in:
@ -2,12 +2,12 @@ import React, { useCallback, useState } from 'react'
|
||||
import { Pressable, Text, View } from 'react-native'
|
||||
import HTMLView from 'react-native-htmlview'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
|
||||
import Emojis from '@components/Timelines/Timeline/Shared/Emojis'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { LinearGradient } from 'expo-linear-gradient'
|
||||
import openLink from '@root/utils/openLink'
|
||||
|
||||
// Prevent going to the same hashtag multiple times
|
||||
const renderNode = ({
|
||||
@ -82,11 +82,7 @@ const renderNode = ({
|
||||
color: theme.link,
|
||||
fontSize: StyleConstants.Font.Size[size]
|
||||
}}
|
||||
onPress={() => {
|
||||
navigation.navigate('Screen-Shared-Webview', {
|
||||
uri: href
|
||||
})
|
||||
}}
|
||||
onPress={async () => await openLink(href)}
|
||||
>
|
||||
<Feather
|
||||
name='external-link'
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import React from 'react'
|
||||
import { Image, Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import openLink from '@root/utils/openLink'
|
||||
|
||||
export interface Props {
|
||||
card: Mastodon.Card
|
||||
@ -10,17 +10,11 @@ export interface Props {
|
||||
|
||||
const TimelineCard: React.FC<Props> = ({ card }) => {
|
||||
const { theme } = useTheme()
|
||||
const navigation = useNavigation()
|
||||
const onPress = useCallback(() => {
|
||||
navigation.navigate('Screen-Shared-Webview', {
|
||||
uri: card.url
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={[styles.card, { borderColor: theme.border }]}
|
||||
onPress={onPress}
|
||||
onPress={async () => await openLink(card.url)}
|
||||
>
|
||||
{card.image && (
|
||||
<View style={styles.left}>
|
||||
|
@ -1,8 +1,6 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
|
||||
import Emojis from '@components/Timelines/Timeline/Shared/Emojis'
|
||||
import relativeTime from '@utils/relativeTime'
|
||||
import { getLocalUrl } from '@utils/slices/instancesSlice'
|
||||
@ -13,6 +11,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
import HeaderDefaultActionsAccount from '@components/Timelines/Timeline/Shared/HeaderDefault/ActionsAccount'
|
||||
import HeaderDefaultActionsStatus from '@components/Timelines/Timeline/Shared/HeaderDefault/ActionsStatus'
|
||||
import HeaderDefaultActionsDomain from '@components/Timelines/Timeline/Shared/HeaderDefault/ActionsDomain'
|
||||
import openLink from '@root/utils/openLink'
|
||||
|
||||
export interface Props {
|
||||
queryKey?: QueryKey.Timeline
|
||||
@ -33,7 +32,6 @@ const TimelineHeaderDefault: React.FC<Props> = ({
|
||||
const account = status.account.acct
|
||||
const { theme } = useTheme()
|
||||
|
||||
const navigation = useNavigation()
|
||||
const localDomain = useSelector(getLocalUrl)
|
||||
const [since, setSince] = useState(relativeTime(status.created_at))
|
||||
const [modalVisible, setBottomSheetVisible] = useState(false)
|
||||
@ -49,12 +47,12 @@ const TimelineHeaderDefault: React.FC<Props> = ({
|
||||
}, [since])
|
||||
|
||||
const onPressAction = useCallback(() => setBottomSheetVisible(true), [])
|
||||
const onPressApplication = useCallback(() => {
|
||||
status.application!.website &&
|
||||
navigation.navigate('Screen-Shared-Webview', {
|
||||
uri: status.application!.website
|
||||
})
|
||||
}, [])
|
||||
const onPressApplication = useCallback(
|
||||
async () =>
|
||||
status.application!.website &&
|
||||
(await openLink(status.application!.website)),
|
||||
[]
|
||||
)
|
||||
|
||||
const pressableAction = useMemo(
|
||||
() => (
|
||||
|
@ -8,7 +8,6 @@ import {
|
||||
} from 'react-native'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
|
||||
import Emojis from '@components/Timelines/Timeline/Shared/Emojis'
|
||||
import relativeTime from '@utils/relativeTime'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
@ -17,6 +16,7 @@ import { useQuery } from 'react-query'
|
||||
import { relationshipFetch } from '@utils/fetches/relationshipFetch'
|
||||
import client from '@api/client'
|
||||
import { toast } from '@components/toast'
|
||||
import openLink from '@root/utils/openLink'
|
||||
|
||||
export interface Props {
|
||||
notification: Mastodon.Notification
|
||||
@ -49,11 +49,12 @@ const TimelineHeaderNotification: React.FC<Props> = ({ notification }) => {
|
||||
}, 1000)
|
||||
}, [since])
|
||||
|
||||
const applicationOnPress = useCallback(() => {
|
||||
navigation.navigate('Screen-Shared-Webview', {
|
||||
uri: notification.status?.application!.website
|
||||
})
|
||||
}, [])
|
||||
const applicationOnPress = useCallback(
|
||||
async () =>
|
||||
notification.status?.application.website &&
|
||||
(await openLink(notification.status.application.website)),
|
||||
[]
|
||||
)
|
||||
|
||||
const relationshipOnPress = useCallback(() => {
|
||||
client({
|
||||
|
@ -18,6 +18,14 @@ export default {
|
||||
cancel: '$t(common:buttons.cancel)'
|
||||
}
|
||||
},
|
||||
browser: {
|
||||
heading: '打开链接',
|
||||
options: {
|
||||
internal: '应用内',
|
||||
external: '系统浏览器',
|
||||
cancel: '$t(common:buttons.cancel)'
|
||||
}
|
||||
},
|
||||
copyrights: {
|
||||
heading: '版权信息'
|
||||
},
|
||||
|
@ -5,8 +5,10 @@ import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import {
|
||||
changeBrowser,
|
||||
changeLanguage,
|
||||
changeTheme,
|
||||
getSettingsBrowser,
|
||||
getSettingsLanguage,
|
||||
getSettingsTheme
|
||||
} from '@utils/slices/settingsSlice'
|
||||
@ -18,6 +20,7 @@ const ScreenMeSettings: React.FC = () => {
|
||||
const { setTheme, theme } = useTheme()
|
||||
const settingsLanguage = useSelector(getSettingsLanguage)
|
||||
const settingsTheme = useSelector(getSettingsTheme)
|
||||
const settingsBrowser = useSelector(getSettingsBrowser)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
return (
|
||||
@ -85,6 +88,33 @@ const ScreenMeSettings: React.FC = () => {
|
||||
)
|
||||
}
|
||||
/>
|
||||
<MenuRow
|
||||
title={t('content.browser.heading')}
|
||||
content={t(`content.browser.options.${settingsBrowser}`)}
|
||||
iconBack='chevron-right'
|
||||
onPress={() =>
|
||||
ActionSheetIOS.showActionSheetWithOptions(
|
||||
{
|
||||
options: [
|
||||
t('content.browser.options.internal'),
|
||||
t('content.browser.options.external'),
|
||||
t('content.browser.options.cancel')
|
||||
],
|
||||
cancelButtonIndex: 2
|
||||
},
|
||||
buttonIndex => {
|
||||
switch (buttonIndex) {
|
||||
case 0:
|
||||
dispatch(changeBrowser('internal'))
|
||||
break
|
||||
case 1:
|
||||
dispatch(changeBrowser('external'))
|
||||
break
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
/>
|
||||
</MenuContainer>
|
||||
<MenuContainer>
|
||||
<MenuRow
|
||||
|
@ -1,104 +0,0 @@
|
||||
import React, { useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ActionSheetIOS } from 'react-native'
|
||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
import { WebView } from 'react-native-webview'
|
||||
import BottomSheet from '@components/BottomSheet'
|
||||
|
||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
|
||||
const Stack = createNativeStackNavigator()
|
||||
|
||||
export interface Props {
|
||||
route: {
|
||||
params: {
|
||||
uri: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ScreenSharedWebview: React.FC<Props> = ({
|
||||
route: {
|
||||
params: { uri }
|
||||
}
|
||||
}) => {
|
||||
const { t } = useTranslation('sharedWebview')
|
||||
const [title, setTitle] = useState<string>(t('heading.loading'))
|
||||
const [bottomSheet, showBottomSheet] = useState(false)
|
||||
const webview = useRef<WebView>(null)
|
||||
|
||||
return (
|
||||
<Stack.Navigator>
|
||||
<Stack.Screen
|
||||
name='Screen-Shared-Webview-Root'
|
||||
options={({ navigation }) => ({
|
||||
title,
|
||||
headerLeft: () => (
|
||||
<HeaderLeft
|
||||
icon='chevron-down'
|
||||
onPress={() => navigation.goBack()}
|
||||
/>
|
||||
),
|
||||
headerRight: () => (
|
||||
<HeaderRight
|
||||
icon='more-horizontal'
|
||||
onPress={() => showBottomSheet(true)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
>
|
||||
{() => (
|
||||
<>
|
||||
<WebView
|
||||
allowsBackForwardNavigationGestures
|
||||
ref={webview}
|
||||
source={{ uri }}
|
||||
decelerationRate='normal'
|
||||
onLoad={({ nativeEvent }) => setTitle(nativeEvent.title)}
|
||||
onError={() => setTitle(t('heading.error'))}
|
||||
/>
|
||||
<BottomSheet
|
||||
visible={bottomSheet}
|
||||
handleDismiss={() => showBottomSheet(false)}
|
||||
>
|
||||
<MenuContainer>
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
ActionSheetIOS.showShareActionSheetWithOptions(
|
||||
{
|
||||
url: uri,
|
||||
excludedActivityTypes: [
|
||||
'com.apple.UIKit.activity.Mail',
|
||||
'com.apple.UIKit.activity.Print',
|
||||
'com.apple.UIKit.activity.SaveToCameraRoll',
|
||||
'com.apple.UIKit.activity.OpenInIBooks'
|
||||
]
|
||||
},
|
||||
() => {},
|
||||
() => {
|
||||
showBottomSheet(false)
|
||||
}
|
||||
)
|
||||
}}
|
||||
iconFront='share'
|
||||
title={'分享链接'}
|
||||
/>
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
showBottomSheet(false)
|
||||
webview.current?.reload()
|
||||
}}
|
||||
iconFront='refresh-cw'
|
||||
title='刷新'
|
||||
/>
|
||||
</MenuContainer>
|
||||
</BottomSheet>
|
||||
</>
|
||||
)}
|
||||
</Stack.Screen>
|
||||
</Stack.Navigator>
|
||||
)
|
||||
}
|
||||
|
||||
export default ScreenSharedWebview
|
@ -3,7 +3,6 @@ import React from 'react'
|
||||
import ScreenSharedAccount from '@screens/Shared/Account'
|
||||
import ScreenSharedHashtag from '@screens/Shared/Hashtag'
|
||||
import ScreenSharedToot from '@screens/Shared/Toot'
|
||||
import ScreenSharedWebview from '@screens/Shared/Webview'
|
||||
import Compose from '@screens/Shared/Compose'
|
||||
import ComposeEditAttachment from '@screens/Shared/Compose/EditAttachment'
|
||||
import ScreenSharedSearch from '@screens/Shared/Search'
|
||||
@ -56,14 +55,6 @@ const sharedScreens = (Stack: any) => {
|
||||
)
|
||||
})}
|
||||
/>,
|
||||
<Stack.Screen
|
||||
key='Screen-Shared-Webview'
|
||||
name='Screen-Shared-Webview'
|
||||
component={ScreenSharedWebview}
|
||||
options={() => ({
|
||||
stackPresentation: 'modal'
|
||||
})}
|
||||
/>,
|
||||
<Stack.Screen
|
||||
key='Screen-Shared-Compose'
|
||||
name='Screen-Shared-Compose'
|
||||
|
17
src/utils/openLink.ts
Normal file
17
src/utils/openLink.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { store } from '@root/store'
|
||||
import * as Linking from 'expo-linking'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
import { getSettingsBrowser } from './slices/settingsSlice'
|
||||
|
||||
const openLink = async (url: string) => {
|
||||
switch (getSettingsBrowser(store.getState())) {
|
||||
case 'internal':
|
||||
await WebBrowser.openBrowserAsync(url)
|
||||
break
|
||||
case 'external':
|
||||
await Linking.openURL(url)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
export default openLink
|
@ -6,11 +6,13 @@ import { RootState } from '@root/store'
|
||||
export type SettingsState = {
|
||||
language: 'zh' | 'en' | undefined
|
||||
theme: 'light' | 'dark' | 'auto'
|
||||
browser: 'internal' | 'external'
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
language: undefined,
|
||||
theme: 'auto'
|
||||
theme: 'auto',
|
||||
browser: 'internal'
|
||||
}
|
||||
|
||||
// export const updateLocal = createAsyncThunk(
|
||||
@ -70,6 +72,12 @@ const settingsSlice = createSlice({
|
||||
action: PayloadAction<NonNullable<SettingsState['theme']>>
|
||||
) => {
|
||||
state.theme = action.payload
|
||||
},
|
||||
changeBrowser: (
|
||||
state,
|
||||
action: PayloadAction<NonNullable<SettingsState['browser']>>
|
||||
) => {
|
||||
state.browser = action.payload
|
||||
}
|
||||
}
|
||||
// extraReducers: builder => {
|
||||
@ -81,6 +89,11 @@ const settingsSlice = createSlice({
|
||||
|
||||
export const getSettingsLanguage = (state: RootState) => state.settings.language
|
||||
export const getSettingsTheme = (state: RootState) => state.settings.theme
|
||||
export const getSettingsBrowser = (state: RootState) => state.settings.browser
|
||||
|
||||
export const { changeLanguage, changeTheme } = settingsSlice.actions
|
||||
export const {
|
||||
changeLanguage,
|
||||
changeTheme,
|
||||
changeBrowser
|
||||
} = settingsSlice.actions
|
||||
export default settingsSlice.reducer
|
||||
|
Reference in New Issue
Block a user