Improve Device Detection For Better Responsiveness (#1512)

* Refactor `useOnMainScroll` function to use responsive device detection

- Replace static `isDesktopWeb` with `useWebMediaQueries` hook to enable dynamic device type detection.
- Create `useDeviceLimits` hook to dynamically determine `DY_LIMIT_UP` and `DY_LIMIT_DOWN` based on device type.
- Update dependency arrays for the `useCallback` hooks to include new dynamic variables.

* Refactor styles to be responsive to device type

- Create `useStyles` hook that generates styles object based on device type detected from `useWebMediaQueries`.
- Replace static styles object with dynamic styles object generated from `useStyles` hook in components.
- This allows `paddingLeft` values for 'ul' and 'ol' styles to adapt to device type dynamically.
- This allows `maxWidth` values for 'metaItem'' styles to adapt to device type dynamically.

* Remove `isDesktopWeb` in favor of `useWebMediaQueries().isDesktop`

* Refactor `SplashScreen` component for responsive design

* Revision based on review results

* Fix isNative check

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
zio/stable
Bryan Lee 2023-09-29 03:47:34 +08:00 committed by GitHub
parent 2e5f73ff61
commit 2aae37d67b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 289 additions and 244 deletions

View File

@ -2,12 +2,18 @@ import {useState, useCallback, useRef} from 'react'
import {NativeSyntheticEvent, NativeScrollEvent} from 'react-native'
import {RootStoreModel} from 'state/index'
import {s} from 'lib/styles'
import {isDesktopWeb} from 'platform/detection'
import {useWebMediaQueries} from './useWebMediaQueries'
const DY_LIMIT_UP = isDesktopWeb ? 30 : 10
const DY_LIMIT_DOWN = isDesktopWeb ? 150 : 10
const Y_LIMIT = 10
const useDeviceLimits = () => {
const {isDesktop} = useWebMediaQueries()
return {
dyLimitUp: isDesktop ? 30 : 10,
dyLimitDown: isDesktop ? 150 : 10,
}
}
export type OnScrollCb = (
event: NativeSyntheticEvent<NativeScrollEvent>,
) => void
@ -18,6 +24,8 @@ export function useOnMainScroll(
): [OnScrollCb, boolean, ResetCb] {
let lastY = useRef(0)
let [isScrolledDown, setIsScrolledDown] = useState(false)
const {dyLimitUp, dyLimitDown} = useDeviceLimits()
return [
useCallback(
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
@ -25,15 +33,11 @@ export function useOnMainScroll(
const dy = y - (lastY.current || 0)
lastY.current = y
if (
!store.shell.minimalShellMode &&
dy > DY_LIMIT_DOWN &&
y > Y_LIMIT
) {
if (!store.shell.minimalShellMode && dy > dyLimitDown && y > Y_LIMIT) {
store.shell.setMinimalShellMode(true)
} else if (
store.shell.minimalShellMode &&
(dy < DY_LIMIT_UP * -1 || y <= Y_LIMIT)
(dy < dyLimitUp * -1 || y <= Y_LIMIT)
) {
store.shell.setMinimalShellMode(false)
}
@ -50,7 +54,7 @@ export function useOnMainScroll(
setIsScrolledDown(false)
}
},
[store, isScrolledDown],
[store.shell, dyLimitDown, dyLimitUp, isScrolledDown],
),
isScrolledDown,
useCallback(() => {

View File

@ -12,7 +12,6 @@ export const isMobileWeb =
isWeb &&
// @ts-ignore we know window exists -prf
global.window.matchMedia(isMobileWebMediaQuery)?.matches
export const isDesktopWeb = isWeb && !isMobileWeb
export const deviceLocales = dedupArray(
getLocales?.().map?.(locale => locale.languageCode),

View File

@ -6,7 +6,8 @@ import {ErrorBoundary} from 'view/com/util/ErrorBoundary'
import {s, colors} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {CenteredView} from '../util/Views'
import {isMobileWeb} from 'platform/detection'
import {isWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
export const SplashScreen = ({
onPressSignin,
@ -16,6 +17,9 @@ export const SplashScreen = ({
onPressCreateAccount: () => void
}) => {
const pal = usePalette('default')
const {isTabletOrMobile} = useWebMediaQueries()
const styles = useStyles()
const isMobileWeb = isWeb && isTabletOrMobile
return (
<CenteredView style={[styles.container, pal.view]}>
@ -55,13 +59,14 @@ export const SplashScreen = ({
</View>
</ErrorBoundary>
</View>
<Footer />
<Footer styles={styles} />
</CenteredView>
)
}
function Footer() {
function Footer({styles}: {styles: ReturnType<typeof useStyles>}) {
const pal = usePalette('default')
return (
<View style={[styles.footer, pal.view, pal.border]}>
<TextLink
@ -82,78 +87,81 @@ function Footer() {
</View>
)
}
const styles = StyleSheet.create({
container: {
height: '100%',
},
containerInner: {
height: '100%',
justifyContent: 'center',
// @ts-ignore web only
paddingBottom: '20vh',
paddingHorizontal: 20,
},
containerInnerMobile: {
paddingBottom: 50,
},
title: {
textAlign: 'center',
color: colors.blue3,
fontSize: 68,
fontWeight: 'bold',
paddingBottom: 10,
},
titleMobile: {
textAlign: 'center',
color: colors.blue3,
fontSize: 58,
fontWeight: 'bold',
},
subtitle: {
textAlign: 'center',
color: colors.gray5,
fontSize: 52,
fontWeight: 'bold',
paddingBottom: 30,
},
subtitleMobile: {
textAlign: 'center',
color: colors.gray5,
fontSize: 42,
fontWeight: 'bold',
paddingBottom: 30,
},
btns: {
flexDirection: isMobileWeb ? 'column' : 'row',
gap: 20,
justifyContent: 'center',
paddingBottom: 40,
},
btn: {
borderRadius: 30,
paddingHorizontal: 24,
paddingVertical: 12,
minWidth: 220,
},
btnLabel: {
textAlign: 'center',
fontSize: 18,
},
notice: {
paddingHorizontal: 40,
textAlign: 'center',
},
footer: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
padding: 20,
borderTopWidth: 1,
flexDirection: 'row',
},
footerLink: {
marginRight: 20,
},
})
const useStyles = () => {
const {isTabletOrMobile} = useWebMediaQueries()
const isMobileWeb = isWeb && isTabletOrMobile
return StyleSheet.create({
container: {
height: '100%',
},
containerInner: {
height: '100%',
justifyContent: 'center',
// @ts-ignore web only
paddingBottom: '20vh',
paddingHorizontal: 20,
},
containerInnerMobile: {
paddingBottom: 50,
},
title: {
textAlign: 'center',
color: colors.blue3,
fontSize: 68,
fontWeight: 'bold',
paddingBottom: 10,
},
titleMobile: {
textAlign: 'center',
color: colors.blue3,
fontSize: 58,
fontWeight: 'bold',
},
subtitle: {
textAlign: 'center',
color: colors.gray5,
fontSize: 52,
fontWeight: 'bold',
paddingBottom: 30,
},
subtitleMobile: {
textAlign: 'center',
color: colors.gray5,
fontSize: 42,
fontWeight: 'bold',
paddingBottom: 30,
},
btns: {
flexDirection: isMobileWeb ? 'column' : 'row',
gap: 20,
justifyContent: 'center',
paddingBottom: 40,
},
btn: {
borderRadius: 30,
paddingHorizontal: 24,
paddingVertical: 12,
minWidth: 220,
},
btnLabel: {
textAlign: 'center',
fontSize: 18,
},
notice: {
paddingHorizontal: 40,
textAlign: 'center',
},
footer: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
padding: 20,
borderTopWidth: 1,
flexDirection: 'row',
},
footerLink: {
marginRight: 20,
},
})
}

View File

@ -7,11 +7,11 @@ import {
import {usePalette} from 'lib/hooks/usePalette'
import {useAnalytics} from 'lib/analytics/analytics'
import {useStores} from 'state/index'
import {isDesktopWeb} from 'platform/detection'
import {openCamera} from 'lib/media/picker'
import {useCameraPermission} from 'lib/hooks/usePermissions'
import {HITSLOP_10, POST_IMG_MAX} from 'lib/constants'
import {GalleryModel} from 'state/models/media/gallery'
import {isMobileWeb, isNative} from 'platform/detection'
type Props = {
gallery: GalleryModel
@ -43,7 +43,8 @@ export function OpenCameraBtn({gallery}: Props) {
}
}, [gallery, track, store, requestCameraAccessIfNeeded])
if (isDesktopWeb) {
const shouldShowCameraButton = isNative || isMobileWeb
if (!shouldShowCameraButton) {
return null
}

View File

@ -6,10 +6,10 @@ import {
} from '@fortawesome/react-native-fontawesome'
import {usePalette} from 'lib/hooks/usePalette'
import {useAnalytics} from 'lib/analytics/analytics'
import {isDesktopWeb} from 'platform/detection'
import {usePhotoLibraryPermission} from 'lib/hooks/usePermissions'
import {GalleryModel} from 'state/models/media/gallery'
import {HITSLOP_10} from 'lib/constants'
import {isNative} from 'platform/detection'
type Props = {
gallery: GalleryModel
@ -23,12 +23,12 @@ export function SelectPhotoBtn({gallery}: Props) {
const onPressSelectPhotos = useCallback(async () => {
track('Composer:GalleryOpened')
if (!isDesktopWeb && !(await requestPhotoAccessIfNeeded())) {
if (isNative && !(await requestPhotoAccessIfNeeded())) {
return
}
gallery.pick()
}, [track, gallery, requestPhotoAccessIfNeeded])
}, [track, requestPhotoAccessIfNeeded, gallery])
return (
<TouchableOpacity

View File

@ -23,7 +23,7 @@ import {ViewHeader} from '../util/ViewHeader'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {Text} from '../util/text/Text'
import {s} from 'lib/styles'
import {isNative, isDesktopWeb} from 'platform/detection'
import {isNative} from 'platform/detection'
import {usePalette} from 'lib/hooks/usePalette'
import {useSetTitle} from 'lib/hooks/useSetTitle'
import {useNavigation} from '@react-navigation/native'
@ -78,7 +78,7 @@ export const PostThread = observer(function PostThread({
treeView: boolean
}) {
const pal = usePalette('default')
const {isTablet} = useWebMediaQueries()
const {isTablet, isDesktop} = useWebMediaQueries()
const ref = useRef<FlatList>(null)
const hasScrolledIntoView = useRef<boolean>(false)
const [isRefreshing, setIsRefreshing] = React.useState(false)
@ -189,7 +189,7 @@ export const PostThread = observer(function PostThread({
} else if (item === REPLY_PROMPT) {
return (
<View>
{isDesktopWeb && <ComposePrompt onPressCompose={onPressReply} />}
{isDesktop && <ComposePrompt onPressCompose={onPressReply} />}
</View>
)
} else if (item === DELETED) {
@ -261,7 +261,20 @@ export const PostThread = observer(function PostThread({
}
return <></>
},
[onRefresh, onPressReply, pal, posts, isTablet, treeView],
[
isTablet,
isDesktop,
onPressReply,
pal.border,
pal.viewLight,
pal.textLight,
pal.view,
pal.text,
pal.colors.border,
posts,
onRefresh,
treeView,
],
)
// loading

View File

@ -34,7 +34,6 @@ import {usePalette} from 'lib/hooks/usePalette'
import {formatCount} from '../util/numeric/format'
import {TimeElapsed} from 'view/com/util/TimeElapsed'
import {makeProfileLink} from 'lib/routes/links'
import {isDesktopWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
export const PostThreadItem = observer(function PostThreadItem({
@ -51,6 +50,7 @@ export const PostThreadItem = observer(function PostThreadItem({
const pal = usePalette('default')
const store = useStores()
const [deleted, setDeleted] = React.useState(false)
const styles = useStyles()
const record = item.postRecord
const hasEngagement = item.post.likeCount || item.post.repostCount
@ -568,6 +568,7 @@ function PostOuterWrapper({
}>) {
const {isMobile} = useWebMediaQueries()
const pal = usePalette('default')
const styles = useStyles()
if (treeView && item._depth > 1) {
return (
<View
@ -636,90 +637,93 @@ function ExpandedPostDetails({
)
}
const styles = StyleSheet.create({
outer: {
borderTopWidth: 1,
paddingLeft: 8,
},
outerHighlighted: {
paddingTop: 16,
paddingLeft: 8,
paddingRight: 8,
},
noTopBorder: {
borderTopWidth: 0,
},
layout: {
flexDirection: 'row',
gap: 10,
paddingLeft: 8,
},
layoutAvi: {},
layoutContent: {
flex: 1,
paddingRight: 10,
},
meta: {
flexDirection: 'row',
paddingTop: 2,
paddingBottom: 2,
},
metaExpandedLine1: {
paddingTop: 5,
paddingBottom: 0,
},
metaItem: {
paddingRight: 5,
maxWidth: isDesktopWeb ? 380 : 220,
},
alert: {
marginBottom: 6,
},
postTextContainer: {
flexDirection: 'row',
alignItems: 'center',
flexWrap: 'wrap',
paddingBottom: 4,
paddingRight: 10,
},
postTextLargeContainer: {
paddingHorizontal: 0,
paddingBottom: 10,
},
translateLink: {
marginBottom: 6,
},
contentHider: {
marginBottom: 6,
},
contentHiderChild: {
marginTop: 6,
},
expandedInfo: {
flexDirection: 'row',
padding: 10,
borderTopWidth: 1,
borderBottomWidth: 1,
marginTop: 5,
marginBottom: 15,
},
expandedInfoItem: {
marginRight: 10,
},
loadMore: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
gap: 4,
paddingHorizontal: 20,
},
replyLine: {
width: 2,
marginLeft: 'auto',
marginRight: 'auto',
},
cursor: {
// @ts-ignore web only
cursor: 'pointer',
},
})
const useStyles = () => {
const {isDesktop} = useWebMediaQueries()
return StyleSheet.create({
outer: {
borderTopWidth: 1,
paddingLeft: 8,
},
outerHighlighted: {
paddingTop: 16,
paddingLeft: 8,
paddingRight: 8,
},
noTopBorder: {
borderTopWidth: 0,
},
layout: {
flexDirection: 'row',
gap: 10,
paddingLeft: 8,
},
layoutAvi: {},
layoutContent: {
flex: 1,
paddingRight: 10,
},
meta: {
flexDirection: 'row',
paddingTop: 2,
paddingBottom: 2,
},
metaExpandedLine1: {
paddingTop: 5,
paddingBottom: 0,
},
metaItem: {
paddingRight: 5,
maxWidth: isDesktop ? 380 : 220,
},
alert: {
marginBottom: 6,
},
postTextContainer: {
flexDirection: 'row',
alignItems: 'center',
flexWrap: 'wrap',
paddingBottom: 4,
paddingRight: 10,
},
postTextLargeContainer: {
paddingHorizontal: 0,
paddingBottom: 10,
},
translateLink: {
marginBottom: 6,
},
contentHider: {
marginBottom: 6,
},
contentHiderChild: {
marginTop: 6,
},
expandedInfo: {
flexDirection: 'row',
padding: 10,
borderTopWidth: 1,
borderBottomWidth: 1,
marginTop: 5,
marginBottom: 15,
},
expandedInfoItem: {
marginRight: 10,
},
loadMore: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
gap: 4,
paddingHorizontal: 20,
},
replyLine: {
width: 2,
marginLeft: 'auto',
marginRight: 'auto',
},
cursor: {
// @ts-ignore web only
cursor: 'pointer',
},
})
}

View File

@ -4,13 +4,13 @@ import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext'
import {Text} from './text/Text'
import {TextLink} from './Link'
import {isDesktopWeb} from 'platform/detection'
import {
H1 as ExpoH1,
H2 as ExpoH2,
H3 as ExpoH3,
H4 as ExpoH4,
} from '@expo/html-elements'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
/**
* These utilities are used to define long documents in an html-like
@ -27,30 +27,35 @@ interface IsChildProps {
// | React.ReactNode
export function H1({children}: React.PropsWithChildren<{}>) {
const styles = useStyles()
const pal = usePalette('default')
const typography = useTheme().typography['title-xl']
return <ExpoH1 style={[typography, pal.text, styles.h1]}>{children}</ExpoH1>
}
export function H2({children}: React.PropsWithChildren<{}>) {
const styles = useStyles()
const pal = usePalette('default')
const typography = useTheme().typography['title-lg']
return <ExpoH2 style={[typography, pal.text, styles.h2]}>{children}</ExpoH2>
}
export function H3({children}: React.PropsWithChildren<{}>) {
const styles = useStyles()
const pal = usePalette('default')
const typography = useTheme().typography.title
return <ExpoH3 style={[typography, pal.text, styles.h3]}>{children}</ExpoH3>
}
export function H4({children}: React.PropsWithChildren<{}>) {
const styles = useStyles()
const pal = usePalette('default')
const typography = useTheme().typography['title-sm']
return <ExpoH4 style={[typography, pal.text, styles.h4]}>{children}</ExpoH4>
}
export function P({children}: React.PropsWithChildren<{}>) {
const styles = useStyles()
const pal = usePalette('default')
return (
<Text type="md" style={[pal.text, styles.p]}>
@ -60,6 +65,7 @@ export function P({children}: React.PropsWithChildren<{}>) {
}
export function UL({children, isChild}: React.PropsWithChildren<IsChildProps>) {
const styles = useStyles()
return (
<View style={[styles.ul, isChild && styles.ulChild]}>
{markChildProps(children)}
@ -68,6 +74,7 @@ export function UL({children, isChild}: React.PropsWithChildren<IsChildProps>) {
}
export function OL({children, isChild}: React.PropsWithChildren<IsChildProps>) {
const styles = useStyles()
return (
<View style={[styles.ol, isChild && styles.olChild]}>
{markChildProps(children)}
@ -79,6 +86,7 @@ export function LI({
children,
value,
}: React.PropsWithChildren<{value?: string}>) {
const styles = useStyles()
const pal = usePalette('default')
return (
<View style={styles.li}>
@ -91,6 +99,7 @@ export function LI({
}
export function A({children, href}: React.PropsWithChildren<{href: string}>) {
const styles = useStyles()
const pal = usePalette('default')
return (
<TextLink
@ -112,6 +121,7 @@ export function STRONG({children}: React.PropsWithChildren<{}>) {
}
export function EM({children}: React.PropsWithChildren<{}>) {
const styles = useStyles()
const pal = usePalette('default')
return (
<Text type="md" style={[pal.text, styles.em]}>
@ -132,58 +142,61 @@ function markChildProps(children: React.ReactNode) {
})
}
const styles = StyleSheet.create({
h1: {
marginTop: 20,
marginBottom: 10,
letterSpacing: 0.8,
},
h2: {
marginTop: 20,
marginBottom: 10,
letterSpacing: 0.8,
},
h3: {
marginTop: 0,
marginBottom: 10,
},
h4: {
marginTop: 0,
marginBottom: 10,
fontWeight: 'bold',
},
p: {
marginBottom: 10,
},
ul: {
marginBottom: 10,
paddingLeft: isDesktopWeb ? 18 : 4,
},
ulChild: {
paddingTop: 10,
marginBottom: 0,
},
ol: {
marginBottom: 10,
paddingLeft: isDesktopWeb ? 18 : 4,
},
olChild: {
paddingTop: 10,
marginBottom: 0,
},
li: {
flexDirection: 'row',
paddingRight: 20,
marginBottom: 10,
},
liBullet: {
paddingRight: 10,
},
liText: {},
a: {
marginBottom: 10,
},
em: {
fontStyle: 'italic',
},
})
const useStyles = () => {
const {isDesktop} = useWebMediaQueries()
return StyleSheet.create({
h1: {
marginTop: 20,
marginBottom: 10,
letterSpacing: 0.8,
},
h2: {
marginTop: 20,
marginBottom: 10,
letterSpacing: 0.8,
},
h3: {
marginTop: 0,
marginBottom: 10,
},
h4: {
marginTop: 0,
marginBottom: 10,
fontWeight: 'bold',
},
p: {
marginBottom: 10,
},
ul: {
marginBottom: 10,
paddingLeft: isDesktop ? 18 : 4,
},
ulChild: {
paddingTop: 10,
marginBottom: 0,
},
ol: {
marginBottom: 10,
paddingLeft: isDesktop ? 18 : 4,
},
olChild: {
paddingTop: 10,
marginBottom: 0,
},
li: {
flexDirection: 'row',
paddingRight: 20,
marginBottom: 10,
},
liBullet: {
paddingRight: 10,
},
liText: {},
a: {
marginBottom: 10,
},
em: {
fontStyle: 'italic',
},
})
}

View File

@ -24,10 +24,11 @@ import {NavigationProp} from 'lib/routes/types'
import {router} from '../../../routes'
import {useStores, RootStoreModel} from 'state/index'
import {convertBskyAppUrlIfNeeded, isExternalUrl} from 'lib/strings/url-helpers'
import {isAndroid, isDesktopWeb} from 'platform/detection'
import {isAndroid} from 'platform/detection'
import {sanitizeUrl} from '@braintree/sanitize-url'
import {PressableWithHover} from './PressableWithHover'
import FixedTouchableHighlight from '../pager/FixedTouchableHighlight'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
type Event =
| React.MouseEvent<HTMLAnchorElement, MouseEvent>
@ -224,7 +225,9 @@ export const DesktopWebTextLink = observer(function DesktopWebTextLink({
lineHeight,
...props
}: DesktopWebTextLinkProps) {
if (isDesktopWeb) {
const {isDesktop} = useWebMediaQueries()
if (isDesktop) {
return (
<TextLink
testID={testID}