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

View File

@ -12,7 +12,6 @@ export const isMobileWeb =
isWeb && isWeb &&
// @ts-ignore we know window exists -prf // @ts-ignore we know window exists -prf
global.window.matchMedia(isMobileWebMediaQuery)?.matches global.window.matchMedia(isMobileWebMediaQuery)?.matches
export const isDesktopWeb = isWeb && !isMobileWeb
export const deviceLocales = dedupArray( export const deviceLocales = dedupArray(
getLocales?.().map?.(locale => locale.languageCode), 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 {s, colors} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {CenteredView} from '../util/Views' import {CenteredView} from '../util/Views'
import {isMobileWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
export const SplashScreen = ({ export const SplashScreen = ({
onPressSignin, onPressSignin,
@ -16,6 +17,9 @@ export const SplashScreen = ({
onPressCreateAccount: () => void onPressCreateAccount: () => void
}) => { }) => {
const pal = usePalette('default') const pal = usePalette('default')
const {isTabletOrMobile} = useWebMediaQueries()
const styles = useStyles()
const isMobileWeb = isWeb && isTabletOrMobile
return ( return (
<CenteredView style={[styles.container, pal.view]}> <CenteredView style={[styles.container, pal.view]}>
@ -55,13 +59,14 @@ export const SplashScreen = ({
</View> </View>
</ErrorBoundary> </ErrorBoundary>
</View> </View>
<Footer /> <Footer styles={styles} />
</CenteredView> </CenteredView>
) )
} }
function Footer() { function Footer({styles}: {styles: ReturnType<typeof useStyles>}) {
const pal = usePalette('default') const pal = usePalette('default')
return ( return (
<View style={[styles.footer, pal.view, pal.border]}> <View style={[styles.footer, pal.view, pal.border]}>
<TextLink <TextLink
@ -82,8 +87,10 @@ function Footer() {
</View> </View>
) )
} }
const useStyles = () => {
const styles = StyleSheet.create({ const {isTabletOrMobile} = useWebMediaQueries()
const isMobileWeb = isWeb && isTabletOrMobile
return StyleSheet.create({
container: { container: {
height: '100%', height: '100%',
}, },
@ -157,3 +164,4 @@ const styles = StyleSheet.create({
marginRight: 20, marginRight: 20,
}, },
}) })
}

View File

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

View File

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

View File

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

View File

@ -34,7 +34,6 @@ import {usePalette} from 'lib/hooks/usePalette'
import {formatCount} from '../util/numeric/format' import {formatCount} from '../util/numeric/format'
import {TimeElapsed} from 'view/com/util/TimeElapsed' import {TimeElapsed} from 'view/com/util/TimeElapsed'
import {makeProfileLink} from 'lib/routes/links' import {makeProfileLink} from 'lib/routes/links'
import {isDesktopWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
export const PostThreadItem = observer(function PostThreadItem({ export const PostThreadItem = observer(function PostThreadItem({
@ -51,6 +50,7 @@ export const PostThreadItem = observer(function PostThreadItem({
const pal = usePalette('default') const pal = usePalette('default')
const store = useStores() const store = useStores()
const [deleted, setDeleted] = React.useState(false) const [deleted, setDeleted] = React.useState(false)
const styles = useStyles()
const record = item.postRecord const record = item.postRecord
const hasEngagement = item.post.likeCount || item.post.repostCount const hasEngagement = item.post.likeCount || item.post.repostCount
@ -568,6 +568,7 @@ function PostOuterWrapper({
}>) { }>) {
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const pal = usePalette('default') const pal = usePalette('default')
const styles = useStyles()
if (treeView && item._depth > 1) { if (treeView && item._depth > 1) {
return ( return (
<View <View
@ -636,7 +637,9 @@ function ExpandedPostDetails({
) )
} }
const styles = StyleSheet.create({ const useStyles = () => {
const {isDesktop} = useWebMediaQueries()
return StyleSheet.create({
outer: { outer: {
borderTopWidth: 1, borderTopWidth: 1,
paddingLeft: 8, paddingLeft: 8,
@ -670,7 +673,7 @@ const styles = StyleSheet.create({
}, },
metaItem: { metaItem: {
paddingRight: 5, paddingRight: 5,
maxWidth: isDesktopWeb ? 380 : 220, maxWidth: isDesktop ? 380 : 220,
}, },
alert: { alert: {
marginBottom: 6, marginBottom: 6,
@ -723,3 +726,4 @@ const styles = StyleSheet.create({
cursor: 'pointer', cursor: 'pointer',
}, },
}) })
}

View File

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

View File

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