Open share sheet when long pressing link (#3317)
* uitextview use library w/ fixes bump bump multiple uitextview fixes * bump * Open share sheet on link long press * rm package manager field * add link warning to longpress --------- Co-authored-by: Hailey <me@haileyok.com>zio/stable
parent
4d28dcc48f
commit
9f657fbace
|
@ -1,23 +1,24 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {GestureResponderEvent} from 'react-native'
|
import {GestureResponderEvent} from 'react-native'
|
||||||
import {useLinkProps, StackActions} from '@react-navigation/native'
|
|
||||||
import {sanitizeUrl} from '@braintree/sanitize-url'
|
import {sanitizeUrl} from '@braintree/sanitize-url'
|
||||||
|
import {StackActions, useLinkProps} from '@react-navigation/native'
|
||||||
|
|
||||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
|
||||||
import {isWeb} from '#/platform/detection'
|
|
||||||
import {useTheme, web, flatten, TextStyleProp, atoms as a} from '#/alf'
|
|
||||||
import {Button, ButtonProps} from '#/components/Button'
|
|
||||||
import {AllNavigatorParams} from '#/lib/routes/types'
|
import {AllNavigatorParams} from '#/lib/routes/types'
|
||||||
|
import {shareUrl} from '#/lib/sharing'
|
||||||
import {
|
import {
|
||||||
convertBskyAppUrlIfNeeded,
|
convertBskyAppUrlIfNeeded,
|
||||||
isExternalUrl,
|
isExternalUrl,
|
||||||
linkRequiresWarning,
|
linkRequiresWarning,
|
||||||
} from '#/lib/strings/url-helpers'
|
} from '#/lib/strings/url-helpers'
|
||||||
|
import {isNative, isWeb} from '#/platform/detection'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {router} from '#/routes'
|
import {useOpenLink} from '#/state/preferences/in-app-browser'
|
||||||
import {Text, TextProps} from '#/components/Typography'
|
|
||||||
import {useOpenLink} from 'state/preferences/in-app-browser'
|
|
||||||
import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped'
|
import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped'
|
||||||
|
import {atoms as a, flatten, TextStyleProp, useTheme, web} from '#/alf'
|
||||||
|
import {Button, ButtonProps} from '#/components/Button'
|
||||||
|
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||||
|
import {Text, TextProps} from '#/components/Typography'
|
||||||
|
import {router} from '#/routes'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only available within a `Link`, since that inherits from `Button`.
|
* Only available within a `Link`, since that inherits from `Button`.
|
||||||
|
@ -60,6 +61,11 @@ type BaseLinkProps = Pick<
|
||||||
* Web-only attribute. Sets `download` attr on web.
|
* Web-only attribute. Sets `download` attr on web.
|
||||||
*/
|
*/
|
||||||
download?: string
|
download?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Native-only attribute. If true, will open the share sheet on long press.
|
||||||
|
*/
|
||||||
|
shareOnLongPress?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLink({
|
export function useLink({
|
||||||
|
@ -68,6 +74,7 @@ export function useLink({
|
||||||
action = 'push',
|
action = 'push',
|
||||||
disableMismatchWarning,
|
disableMismatchWarning,
|
||||||
onPress: outerOnPress,
|
onPress: outerOnPress,
|
||||||
|
shareOnLongPress,
|
||||||
}: BaseLinkProps & {
|
}: BaseLinkProps & {
|
||||||
displayText: string
|
displayText: string
|
||||||
}) {
|
}) {
|
||||||
|
@ -157,10 +164,34 @@ export function useLink({
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const handleLongPress = React.useCallback(() => {
|
||||||
|
const requiresWarning = Boolean(
|
||||||
|
!disableMismatchWarning &&
|
||||||
|
displayText &&
|
||||||
|
isExternal &&
|
||||||
|
linkRequiresWarning(href, displayText),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (requiresWarning) {
|
||||||
|
openModal({
|
||||||
|
name: 'link-warning',
|
||||||
|
text: displayText,
|
||||||
|
href: href,
|
||||||
|
share: true,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
shareUrl(href)
|
||||||
|
}
|
||||||
|
}, [disableMismatchWarning, displayText, href, isExternal, openModal])
|
||||||
|
|
||||||
|
const onLongPress =
|
||||||
|
isNative && isExternal && shareOnLongPress ? handleLongPress : undefined
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isExternal,
|
isExternal,
|
||||||
href,
|
href,
|
||||||
onPress,
|
onPress,
|
||||||
|
onLongPress,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,16 +260,18 @@ export function InlineLink({
|
||||||
download,
|
download,
|
||||||
selectable,
|
selectable,
|
||||||
label,
|
label,
|
||||||
|
shareOnLongPress,
|
||||||
...rest
|
...rest
|
||||||
}: InlineLinkProps) {
|
}: InlineLinkProps) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const stringChildren = typeof children === 'string'
|
const stringChildren = typeof children === 'string'
|
||||||
const {href, isExternal, onPress} = useLink({
|
const {href, isExternal, onPress, onLongPress} = useLink({
|
||||||
to,
|
to,
|
||||||
displayText: stringChildren ? children : '',
|
displayText: stringChildren ? children : '',
|
||||||
action,
|
action,
|
||||||
disableMismatchWarning,
|
disableMismatchWarning,
|
||||||
onPress: outerOnPress,
|
onPress: outerOnPress,
|
||||||
|
shareOnLongPress,
|
||||||
})
|
})
|
||||||
const {
|
const {
|
||||||
state: hovered,
|
state: hovered,
|
||||||
|
@ -270,6 +303,7 @@ export function InlineLink({
|
||||||
]}
|
]}
|
||||||
role="link"
|
role="link"
|
||||||
onPress={download ? undefined : onPress}
|
onPress={download ? undefined : onPress}
|
||||||
|
onLongPress={onLongPress}
|
||||||
onPressIn={onPressIn}
|
onPressIn={onPressIn}
|
||||||
onPressOut={onPressOut}
|
onPressOut={onPressOut}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {RichText as RichTextAPI, AppBskyRichtextFacet} from '@atproto/api'
|
import {AppBskyRichtextFacet, RichText as RichTextAPI} from '@atproto/api'
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {atoms as a, TextStyleProp, flatten, useTheme, web, native} from '#/alf'
|
import {toShortUrl} from '#/lib/strings/url-helpers'
|
||||||
import {InlineLink} from '#/components/Link'
|
|
||||||
import {Text, TextProps} from '#/components/Typography'
|
|
||||||
import {toShortUrl} from 'lib/strings/url-helpers'
|
|
||||||
import {TagMenu, useTagMenuControl} from '#/components/TagMenu'
|
|
||||||
import {isNative} from '#/platform/detection'
|
import {isNative} from '#/platform/detection'
|
||||||
|
import {atoms as a, flatten, native, TextStyleProp, useTheme, web} from '#/alf'
|
||||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||||
|
import {InlineLink} from '#/components/Link'
|
||||||
|
import {TagMenu, useTagMenuControl} from '#/components/TagMenu'
|
||||||
|
import {Text, TextProps} from '#/components/Typography'
|
||||||
|
|
||||||
const WORD_WRAP = {wordWrap: 1}
|
const WORD_WRAP = {wordWrap: 1}
|
||||||
|
|
||||||
|
@ -105,7 +105,8 @@ export function RichText({
|
||||||
to={link.uri}
|
to={link.uri}
|
||||||
style={[...styles, {pointerEvents: 'auto'}]}
|
style={[...styles, {pointerEvents: 'auto'}]}
|
||||||
// @ts-ignore TODO
|
// @ts-ignore TODO
|
||||||
dataSet={WORD_WRAP}>
|
dataSet={WORD_WRAP}
|
||||||
|
shareOnLongPress>
|
||||||
{toShortUrl(segment.text)}
|
{toShortUrl(segment.text)}
|
||||||
</InlineLink>,
|
</InlineLink>,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import {isIOS, isAndroid} from 'platform/detection'
|
import {Share} from 'react-native'
|
||||||
// import * as Sharing from 'expo-sharing'
|
// import * as Sharing from 'expo-sharing'
|
||||||
import Clipboard from '@react-native-clipboard/clipboard'
|
import Clipboard from '@react-native-clipboard/clipboard'
|
||||||
import * as Toast from '../view/com/util/Toast'
|
|
||||||
import {Share} from 'react-native'
|
import {isAndroid, isIOS} from 'platform/detection'
|
||||||
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function shares a URL using the native Share API if available, or copies it to the clipboard
|
* This function shares a URL using the native Share API if available, or copies it to the clipboard
|
||||||
|
|
|
@ -122,6 +122,7 @@ export interface LinkWarningModal {
|
||||||
name: 'link-warning'
|
name: 'link-warning'
|
||||||
text: string
|
text: string
|
||||||
href: string
|
href: string
|
||||||
|
share?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmbedConsentModal {
|
export interface EmbedConsentModal {
|
||||||
|
|
|
@ -1,22 +1,32 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {SafeAreaView, StyleSheet, View} from 'react-native'
|
import {SafeAreaView, StyleSheet, View} from 'react-native'
|
||||||
import {ScrollView} from './util'
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {Text} from '../util/text/Text'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {Button} from '../util/forms/Button'
|
|
||||||
import {s, colors} from 'lib/styles'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {isWeb} from 'platform/detection'
|
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|
||||||
import {isPossiblyAUrl, splitApexDomain} from 'lib/strings/url-helpers'
|
|
||||||
import {Trans, msg} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
|
import {shareUrl} from '#/lib/sharing'
|
||||||
|
import {isPossiblyAUrl, splitApexDomain} from '#/lib/strings/url-helpers'
|
||||||
|
import {colors, s} from '#/lib/styles'
|
||||||
|
import {isWeb} from '#/platform/detection'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {useOpenLink} from '#/state/preferences/in-app-browser'
|
import {useOpenLink} from '#/state/preferences/in-app-browser'
|
||||||
|
import {Button} from '#/view/com/util/forms/Button'
|
||||||
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
|
import {ScrollView} from './util'
|
||||||
|
|
||||||
export const snapPoints = ['50%']
|
export const snapPoints = ['50%']
|
||||||
|
|
||||||
export function Component({text, href}: {text: string; href: string}) {
|
export function Component({
|
||||||
|
text,
|
||||||
|
href,
|
||||||
|
share,
|
||||||
|
}: {
|
||||||
|
text: string
|
||||||
|
href: string
|
||||||
|
share?: boolean
|
||||||
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {closeModal} = useModalControls()
|
const {closeModal} = useModalControls()
|
||||||
const {isMobile} = useWebMediaQueries()
|
const {isMobile} = useWebMediaQueries()
|
||||||
|
@ -26,8 +36,12 @@ export function Component({text, href}: {text: string; href: string}) {
|
||||||
|
|
||||||
const onPressVisit = () => {
|
const onPressVisit = () => {
|
||||||
closeModal()
|
closeModal()
|
||||||
|
if (share) {
|
||||||
|
shareUrl(href)
|
||||||
|
} else {
|
||||||
openLink(href)
|
openLink(href)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={[s.flex1, pal.view]}>
|
<SafeAreaView style={[s.flex1, pal.view]}>
|
||||||
|
@ -72,9 +86,13 @@ export function Component({text, href}: {text: string; href: string}) {
|
||||||
testID="confirmBtn"
|
testID="confirmBtn"
|
||||||
type="primary"
|
type="primary"
|
||||||
onPress={onPressVisit}
|
onPress={onPressVisit}
|
||||||
accessibilityLabel={_(msg`Visit Site`)}
|
accessibilityLabel={share ? _(msg`Share Link`) : _(msg`Visit Site`)}
|
||||||
accessibilityHint={_(msg`Opens the linked website`)}
|
accessibilityHint={
|
||||||
label={_(msg`Visit Site`)}
|
share
|
||||||
|
? _(msg`Shares the linked website`)
|
||||||
|
: _(msg`Opens the linked website`)
|
||||||
|
}
|
||||||
|
label={share ? _(msg`Share Link`) : _(msg`Visit Site`)}
|
||||||
labelContainerStyle={{justifyContent: 'center', padding: 4}}
|
labelContainerStyle={{justifyContent: 'center', padding: 4}}
|
||||||
labelStyle={[s.f18]}
|
labelStyle={[s.f18]}
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Reference in New Issue