Skip to content
This repository has been archived by the owner on Oct 4, 2023. It is now read-only.

Commit

Permalink
[C-3060] Fix mobile outbound link experience (#4055)
Browse files Browse the repository at this point in the history
  • Loading branch information
dylanjeffers authored Sep 9, 2023
1 parent a72a9e1 commit 5691cc6
Show file tree
Hide file tree
Showing 25 changed files with 173 additions and 198 deletions.
1 change: 1 addition & 0 deletions packages/common/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ export * from './streaming'
export * from './dogEarUtils'
export * from './uploadConstants'
export * from './updatePlaylistArtwork'
export * from './linking'
19 changes: 19 additions & 0 deletions packages/common/src/utils/linking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const externalLinkAllowList = new Set([
'facebook.com',
'instagram.com',
'tiktok.com',
'twitter.com',
'x.com',
'audius.co',
'discord.gg'
])

export const isAllowedExternalLink = (link: string) => {
try {
let hostname = new URL(link).hostname
hostname = hostname.replace(/^www\./, '')
return externalLinkAllowList.has(hostname)
} catch (e) {
return false
}
}
17 changes: 17 additions & 0 deletions packages/common/src/utils/urlUtils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
const AUDIUS_PRESS_LINK = 'https://brand.audius.co'
const AUDIUS_MERCH_LINK = 'https://merch.audius.co/'
const AUDIUS_REMIX_CONTESTS_LINK = 'https://remix.audius.co/'
const AUDIUS_BLOG_LINK = 'https://blog.audius.co/'

export const externalAudiusLinks = [
AUDIUS_PRESS_LINK,
AUDIUS_MERCH_LINK,
AUDIUS_REMIX_CONTESTS_LINK,
AUDIUS_BLOG_LINK,
'https://help.audius.co'
]
const audiusUrlRegex =
// eslint-disable-next-line no-useless-escape
/^(?:https?:\/\/)?(?:[^@\/\n]+@)?(?:www\.)?(staging\.)?(audius\.co)(\/.+)?/gim

export const isAudiusUrl = (url: string) => new RegExp(audiusUrlRegex).test(url)
export const isInteralAudiusUrl = (url: string) =>
isAudiusUrl(url) &&
!externalAudiusLinks.some((externalLink) => externalLink.includes(url))
export const isExternalAudiusUrl = (url: string) =>
new RegExp(audiusUrlRegex).test(url)
export const getPathFromAudiusUrl = (url: string) =>
new RegExp(audiusUrlRegex).exec(url)?.[3] ?? null

Expand Down
39 changes: 15 additions & 24 deletions packages/mobile/src/components/action-drawer/ActionDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { useCallback } from 'react'
import type { Modals } from '@audius/common'
import type { TextStyle, ViewStyle } from 'react-native'
import { TouchableHighlight, View } from 'react-native'
import type { SetOptional } from 'type-fest'

import Text from 'app/components/text'
import { Text } from 'app/components/core'
import { makeStyles } from 'app/styles'
import { useThemeColors } from 'app/utils/theme'

import type { AppDrawerProps } from '../drawer/AppDrawer'
import { AppDrawer, useDrawerState } from '../drawer/AppDrawer'

export type ActionDrawerRow = {
Expand All @@ -19,19 +21,14 @@ export type ActionDrawerRow = {
isDestructive?: boolean
}

type ActionSheetModalProps = {
type ActionDrawerProps = {
modalName: Modals
rows: ActionDrawerRow[]
title?: string
renderTitle?: () => React.ReactNode
styles?: { row?: ViewStyle }
disableAutoClose?: boolean
}
} & SetOptional<AppDrawerProps, 'children'>

const useStyles = makeStyles(({ palette, typography, spacing }) => ({
container: {
paddingVertical: spacing(4)
},
row: {
height: 56,
alignItems: 'center',
Expand All @@ -57,14 +54,14 @@ const useStyles = makeStyles(({ palette, typography, spacing }) => ({
}))

// `ActionDrawer` is a drawer that presents a list of clickable rows with text
const ActionDrawer = (props: ActionSheetModalProps) => {
const ActionDrawer = (props: ActionDrawerProps) => {
const {
modalName,
rows,
title,
renderTitle,
styles: stylesProp,
disableAutoClose
disableAutoClose,
children,
...other
} = props
const styles = useStyles()
const { onClose } = useDrawerState(modalName)
Expand All @@ -85,13 +82,9 @@ const ActionDrawer = (props: ActionSheetModalProps) => {
const { neutralLight9 } = useThemeColors()

return (
<AppDrawer modalName={modalName}>
<View style={styles.container}>
{renderTitle ? (
renderTitle()
) : title ? (
<Text style={[styles.row, styles.title]}>{title}</Text>
) : null}
<AppDrawer modalName={modalName} {...other}>
<View>
{children}
{rows.map(({ text, isDestructive = false, icon, style }, index) => (
<TouchableHighlight
key={`${text}-${index}`}
Expand All @@ -103,12 +96,10 @@ const ActionDrawer = (props: ActionSheetModalProps) => {
<View style={[styles.row, stylesProp?.row]}>
{icon ? <View style={styles.actionIcon}>{icon}</View> : null}
<Text
style={[
styles.action,
isDestructive ? styles.destructiveAction : null,
style
]}
fontSize='xl'
weight='demiBold'
color={isDestructive ? 'accentRed' : 'secondary'}
style={style}
>
{text}
</Text>
Expand Down
24 changes: 18 additions & 6 deletions packages/mobile/src/components/core/Hyperlink.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import type { ComponentProps } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'

import { isAudiusUrl, useLeavingAudiusModal } from '@audius/common'
import {
getPathFromAudiusUrl,
isAllowedExternalLink,
isInteralAudiusUrl,
useLeavingAudiusModal
} from '@audius/common'
import { useLinkTo } from '@react-navigation/native'
import type { Match } from 'autolinker/dist/es2015'
import type { LayoutRectangle, TextStyle } from 'react-native'
import { Text, View } from 'react-native'
Expand Down Expand Up @@ -55,6 +61,7 @@ export const Hyperlink = (props: HyperlinkProps) => {
...other
} = props
const styles = useStyles()
const linkTo = useLinkTo()

const linkContainerRef = useRef<View>(null)
const [linkRefs, setLinkRefs] = useState<Record<number, Text>>({})
Expand Down Expand Up @@ -97,14 +104,19 @@ export const Hyperlink = (props: HyperlinkProps) => {
const { onOpen: openLeavingAudiusModal } = useLeavingAudiusModal()

const handlePress = useCallback(
(url) => {
if (!isAudiusUrl(url)) {
openLeavingAudiusModal({ link: url })
} else {
(url: string) => {
if (isInteralAudiusUrl(url)) {
const path = getPathFromAudiusUrl(url)
if (path) {
linkTo(path)
}
} else if (isAllowedExternalLink(url)) {
openLink(url)
} else {
openLeavingAudiusModal({ link: url })
}
},
[openLink, openLeavingAudiusModal]
[linkTo, openLink, openLeavingAudiusModal]
)

const renderLink = useCallback(
Expand Down
8 changes: 7 additions & 1 deletion packages/mobile/src/components/core/Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,13 @@ export const Text = (props: TextProps) => {
// Fix for demibold's weird positioning
marginTop:
weight === 'demiBold' && Platform.OS === 'ios'
? spacing(fontSize === 'large' ? 1 : fontSize === 'small' ? 0.5 : 0)
? spacing(
fontSize && ['xl', 'large'].includes(fontSize)
? 1
: fontSize === 'small'
? 0.5
: 0
)
: undefined
},
fontSize &&
Expand Down
2 changes: 1 addition & 1 deletion packages/mobile/src/components/drawer/AppDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const useDrawerState = (modalName: Modals) => {
}
}

type AppDrawerProps = SetOptional<DrawerProps, 'isOpen' | 'onClose'> & {
export type AppDrawerProps = SetOptional<DrawerProps, 'isOpen' | 'onClose'> & {
modalName: Modals
}

Expand Down
2 changes: 1 addition & 1 deletion packages/mobile/src/components/drawer/Drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export type DrawerProps = {
/**
* Header title if this drawer has a header
*/
title?: string
title?: ReactNode
/**
* Icon to display in the header next to the title (must also include title)
*/
Expand Down
41 changes: 17 additions & 24 deletions packages/mobile/src/components/drawer/DrawerHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
import type { ComponentType } from 'react'
import type { ComponentType, ReactNode } from 'react'

import type { ImageSourcePropType } from 'react-native'
import { TouchableOpacity, View, Image, Text } from 'react-native'
import { TouchableOpacity, View, Image } from 'react-native'

import IconRemove from 'app/assets/images/iconRemove.svg'
import { Text } from 'app/components/core'
import { makeStyles } from 'app/styles'
import type { SvgProps } from 'app/types/svg'
import { useColor } from 'app/utils/theme'

type DrawerHeaderProps = {
onClose: () => void
title?: string
title?: ReactNode
titleIcon?: ComponentType<SvgProps>
titleImage?: ImageSourcePropType
isFullscreen?: boolean
}

export const useStyles = makeStyles(({ palette, typography, spacing }) => ({
export const useStyles = makeStyles(({ spacing }) => ({
titleBarContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
padding: spacing(6)
padding: spacing(8)
},

dismissContainer: {
Expand All @@ -32,21 +33,11 @@ export const useStyles = makeStyles(({ palette, typography, spacing }) => ({

titleContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingTop: spacing(5)
columnGap: spacing(2)
},

titleIcon: {
marginRight: spacing(3),
titleImage: {
height: spacing(6),
width: spacing(6)
},

titleLabel: {
fontFamily: typography.fontByWeight.bold,
fontSize: typography.fontSize.large,
color: palette.neutral
}
}))

Expand All @@ -64,26 +55,28 @@ export const DrawerHeader = (props: DrawerHeaderProps) => {

return title || isFullscreen ? (
<View style={styles.titleBarContainer}>
{isFullscreen && (
{isFullscreen ? (
<TouchableOpacity
activeOpacity={0.7}
onPress={onClose}
style={styles.dismissContainer}
>
<IconRemove width={30} height={30} fill={iconRemoveColor} />
</TouchableOpacity>
)}
{title && (
) : null}
{title ? (
<View style={styles.titleContainer}>
{TitleIcon ? (
<TitleIcon style={styles.titleIcon} fill={titleIconColor} />
<TitleIcon height={22} width={22} fill={titleIconColor} />
) : null}
{titleImage ? (
<Image style={styles.titleIcon} source={titleImage} />
<Image style={styles.titleImage} source={titleImage} />
) : null}
<Text style={styles.titleLabel}>{title}</Text>
<Text fontSize='xl' weight='heavy' textTransform='uppercase'>
{title}
</Text>
</View>
)}
) : null}
</View>
) : (
<View />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ import {
feedPageLineupActions as feedActions,
feedPageActions
} from '@audius/common'
import { Text } from 'react-native'
import { useDispatch } from 'react-redux'

import ActionDrawer from 'app/components/action-drawer'
import { Text } from 'app/components/core'
import { make, track } from 'app/services/analytics'
import { makeStyles } from 'app/styles'

const { setFeedFilter } = feedPageActions

Expand All @@ -24,21 +23,9 @@ export const messages = {
filterReposts: 'Reposts'
}

const useStyles = makeStyles(({ palette, spacing, typography }) => ({
title: {
...typography.body,
color: palette.neutral,
textAlign: 'center',
marginTop: spacing(2),
marginBottom: spacing(4)
}
}))

export const FeedFilterDrawer = () => {
const dispatch = useDispatch()

const styles = useStyles()

const handleSelectFilter = useCallback(
(filter: FeedFilter) => {
dispatch(setFeedFilter(filter))
Expand Down Expand Up @@ -74,7 +61,11 @@ export const FeedFilterDrawer = () => {
return (
<ActionDrawer
modalName={MODAL_NAME}
renderTitle={() => <Text style={styles.title}>{messages.title}</Text>}
title={
<Text color='neutral' textTransform='none'>
{messages.title}
</Text>
}
rows={rows}
/>
)
Expand Down
Loading

0 comments on commit 5691cc6

Please sign in to comment.