mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Move most responsive queries to the hook

+700 -412
+13 -7
src/lib/hooks/useWebMediaQueries.tsx
··· 2 2 import {isNative} from 'platform/detection' 3 3 4 4 export function useWebMediaQueries() { 5 - const isDesktop = useMediaQuery({ 6 - query: '(min-width: 1224px)', 7 - }) 8 - const isTabletOrMobile = useMediaQuery({query: '(max-width: 1224px)'}) 9 - const isMobile = useMediaQuery({query: '(max-width: 800px)'}) 5 + const isDesktop = useMediaQuery({minWidth: 1300}) 6 + const isTablet = useMediaQuery({minWidth: 800, maxWidth: 1300}) 7 + const isMobile = useMediaQuery({maxWidth: 800}) 8 + const isTabletOrMobile = isMobile || isTablet 9 + const isTabletOrDesktop = isDesktop || isTablet 10 10 if (isNative) { 11 - return {isMobile: true, isTabletOrMobile: true, isDesktop: false} 11 + return { 12 + isMobile: true, 13 + isTablet: false, 14 + isTabletOrMobile: true, 15 + isTabletOrDesktop: false, 16 + isDesktop: false, 17 + } 12 18 } 13 - return {isMobile, isTabletOrMobile, isDesktop} 19 + return {isMobile, isTablet, isTabletOrMobile, isTabletOrDesktop, isDesktop} 14 20 }
+1 -1
src/platform/detection.ts
··· 7 7 export const isNative = isIOS || isAndroid 8 8 export const devicePlatform = isIOS ? 'ios' : isAndroid ? 'android' : 'web' 9 9 export const isWeb = !isNative 10 - export const isMobileWebMediaQuery = 'only screen and (max-width: 1230px)' 10 + export const isMobileWebMediaQuery = 'only screen and (max-width: 1300px)' 11 11 export const isMobileWeb = 12 12 isWeb && 13 13 // @ts-ignore we know window exists -prf
+1 -3
src/view/com/auth/onboarding/WelcomeDesktop.tsx
··· 16 16 17 17 export const WelcomeDesktop = observer(({next}: Props) => { 18 18 const pal = usePalette('default') 19 - const horizontal = useMediaQuery({ 20 - query: '(min-width: 1230px)', 21 - }) 19 + const horizontal = useMediaQuery({minWidth: 1300}) 22 20 const title = ( 23 21 <> 24 22 <Text
+1 -2
src/view/com/auth/onboarding/WelcomeMobile.tsx
··· 7 7 import {Button} from 'view/com/util/forms/Button' 8 8 import {observer} from 'mobx-react-lite' 9 9 import {ViewHeader} from 'view/com/util/ViewHeader' 10 - import {isDesktopWeb} from 'platform/detection' 11 10 12 11 type Props = { 13 12 next: () => void ··· 95 94 const styles = StyleSheet.create({ 96 95 container: { 97 96 flex: 1, 98 - marginBottom: isDesktopWeb ? 30 : 60, 97 + marginBottom: 60, 99 98 marginHorizontal: 16, 100 99 justifyContent: 'space-between', 101 100 },
+23 -11
src/view/com/composer/Composer.tsx
··· 37 37 import {SelectPhotoBtn} from './photos/SelectPhotoBtn' 38 38 import {OpenCameraBtn} from './photos/OpenCameraBtn' 39 39 import {usePalette} from 'lib/hooks/usePalette' 40 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 41 + import {useExternalLinkFetch} from './useExternalLinkFetch' 42 + import {isWeb, isNative, isAndroid, isIOS} from 'platform/detection' 40 43 import QuoteEmbed from '../util/post-embeds/QuoteEmbed' 41 - import {useExternalLinkFetch} from './useExternalLinkFetch' 42 - import {isDesktopWeb, isAndroid, isIOS} from 'platform/detection' 43 44 import {GalleryModel} from 'state/models/media/gallery' 44 45 import {Gallery} from './photos/Gallery' 45 46 import {MAX_GRAPHEME_LENGTH} from 'lib/constants' ··· 61 62 }: Props) { 62 63 const {track} = useAnalytics() 63 64 const pal = usePalette('default') 65 + const {isDesktop, isMobile} = useWebMediaQueries() 64 66 const store = useStores() 65 67 const textInput = useRef<TextInputRef>(null) 66 68 const [isKeyboardVisible] = useIsKeyboardVisible({iosUseWillEvents: true}) ··· 99 101 () => ({ 100 102 paddingBottom: 101 103 isAndroid || (isIOS && !isKeyboardVisible) ? insets.bottom : 0, 102 - paddingTop: isAndroid ? insets.top : isDesktopWeb ? 0 : 15, 104 + paddingTop: isAndroid ? insets.top : isMobile ? 15 : 0, 103 105 }), 104 - [insets, isKeyboardVisible], 106 + [insets, isKeyboardVisible, isMobile], 105 107 ) 106 108 107 109 const onPressCancel = useCallback(() => { ··· 143 145 [onPressCancel], 144 146 ) 145 147 useEffect(() => { 146 - if (isDesktopWeb) { 148 + if (isWeb) { 147 149 window.addEventListener('keydown', onEscape) 148 150 return () => window.removeEventListener('keydown', onEscape) 149 151 } ··· 240 242 behavior={Platform.OS === 'ios' ? 'padding' : 'height'} 241 243 style={styles.outer}> 242 244 <View style={[s.flex1, viewStyles]} aria-modal accessibilityViewIsModal> 243 - <View style={styles.topbar}> 245 + <View style={[styles.topbar, isDesktop && styles.topbarDesktop]}> 244 246 <TouchableOpacity 245 247 testID="composerDiscardButton" 246 248 onPress={onPressCancel} ··· 334 336 </View> 335 337 ) : undefined} 336 338 337 - <View style={[pal.border, styles.textInputLayout]}> 339 + <View 340 + style={[ 341 + pal.border, 342 + styles.textInputLayout, 343 + isNative && styles.textInputLayoutMobile, 344 + ]}> 338 345 <UserAvatar avatar={store.me.avatar} size={50} /> 339 346 <TextInput 340 347 ref={textInput} ··· 395 402 <OpenCameraBtn gallery={gallery} /> 396 403 </> 397 404 ) : null} 398 - {isDesktopWeb ? <EmojiPickerButton /> : null} 405 + {isDesktop ? <EmojiPickerButton /> : null} 399 406 <View style={s.flex1} /> 400 407 <SelectLangBtn /> 401 408 <CharProgress count={graphemeLength} /> ··· 414 421 topbar: { 415 422 flexDirection: 'row', 416 423 alignItems: 'center', 417 - paddingTop: isDesktopWeb ? 10 : undefined, 418 - paddingBottom: isDesktopWeb ? 10 : 4, 424 + paddingBottom: 4, 419 425 paddingHorizontal: 20, 420 426 height: 55, 421 427 }, 428 + topbarDesktop: { 429 + paddingTop: 10, 430 + paddingBottom: 10, 431 + }, 422 432 postBtn: { 423 433 borderRadius: 20, 424 434 paddingHorizontal: 20, ··· 465 475 paddingHorizontal: 15, 466 476 }, 467 477 textInputLayout: { 468 - flex: isDesktopWeb ? undefined : 1, 469 478 flexDirection: 'row', 470 479 borderTopWidth: 1, 471 480 paddingTop: 16, 481 + }, 482 + textInputLayoutMobile: { 483 + flex: 1, 472 484 }, 473 485 replyToLayout: { 474 486 flexDirection: 'row',
+3 -2
src/view/com/composer/Prompt.tsx
··· 4 4 import {Text} from '../util/text/Text' 5 5 import {usePalette} from 'lib/hooks/usePalette' 6 6 import {useStores} from 'state/index' 7 - import {isDesktopWeb} from 'platform/detection' 7 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 8 8 9 9 export function ComposePrompt({onPressCompose}: {onPressCompose: () => void}) { 10 10 const store = useStores() 11 11 const pal = usePalette('default') 12 + const {isDesktop} = useWebMediaQueries() 12 13 return ( 13 14 <TouchableOpacity 14 15 testID="replyPromptBtn" ··· 22 23 type="xl" 23 24 style={[ 24 25 pal.text, 25 - isDesktopWeb ? styles.labelDesktopWeb : styles.labelMobile, 26 + isDesktop ? styles.labelDesktopWeb : styles.labelMobile, 26 27 ]}> 27 28 Write your reply 28 29 </Text>
+6 -5
src/view/com/composer/photos/Gallery.tsx
··· 7 7 import {StyleSheet, TouchableOpacity, View} from 'react-native' 8 8 import {Image} from 'expo-image' 9 9 import {Text} from 'view/com/util/text/Text' 10 - import {isDesktopWeb} from 'platform/detection' 11 10 import {openAltTextModal} from 'lib/media/alt-text' 12 11 import {useStores} from 'state/index' 13 12 import {usePalette} from 'lib/hooks/usePalette' 13 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 14 14 15 15 interface Props { 16 16 gallery: GalleryModel ··· 19 19 export const Gallery = observer(function ({gallery}: Props) { 20 20 const store = useStores() 21 21 const pal = usePalette('default') 22 + const {isMobile} = useWebMediaQueries() 22 23 23 24 let side: number 24 25 25 26 if (gallery.size === 1) { 26 27 side = 250 27 28 } else { 28 - side = (isDesktopWeb ? 560 : 350) / gallery.size 29 + side = (isMobile ? 350 : 560) / gallery.size 29 30 } 30 31 31 32 const imageStyle = { ··· 33 34 width: side, 34 35 } 35 36 36 - const isOverflow = !isDesktopWeb && gallery.size > 2 37 + const isOverflow = isMobile && gallery.size > 2 37 38 38 39 const altTextControlStyle = isOverflow 39 40 ? { 40 41 left: 4, 41 42 bottom: 4, 42 43 } 43 - : isDesktopWeb && gallery.size < 3 44 + : !isMobile && gallery.size < 3 44 45 ? { 45 46 left: 8, 46 47 top: 8, ··· 60 61 right: 4, 61 62 gap: 4, 62 63 } 63 - : isDesktopWeb && gallery.size < 3 64 + : !isMobile && gallery.size < 3 64 65 ? { 65 66 top: 8, 66 67 right: 8,
+5 -7
src/view/com/lists/ListItems.tsx
··· 22 22 import {ListModel} from 'state/models/content/list' 23 23 import {useAnalytics} from 'lib/analytics/analytics' 24 24 import {usePalette} from 'lib/hooks/usePalette' 25 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 25 26 import {useStores} from 'state/index' 26 27 import {s} from 'lib/styles' 27 - import {isDesktopWeb} from 'platform/detection' 28 28 import {ListActions} from './ListActions' 29 29 import {makeProfileLink} from 'lib/routes/links' 30 30 import {sanitizeHandle} from 'lib/strings/handles' ··· 283 283 }) => { 284 284 const pal = usePalette('default') 285 285 const store = useStores() 286 + const {isDesktop} = useWebMediaQueries() 286 287 const descriptionRT = React.useMemo( 287 288 () => 288 289 list?.description && ··· 318 319 richText={descriptionRT} 319 320 /> 320 321 )} 321 - {isDesktopWeb && ( 322 + {isDesktop && ( 322 323 <ListActions 323 324 isOwner={isOwner} 324 325 muted={list.viewer?.muted} ··· 334 335 <UserAvatar type="list" avatar={list.avatar} size={64} /> 335 336 </View> 336 337 </View> 337 - <View style={[styles.fakeSelector, pal.border]}> 338 + <View 339 + style={{flexDirection: 'row', paddingHorizontal: isDesktop ? 16 : 6}}> 338 340 <View 339 341 style={[styles.fakeSelectorItem, {borderColor: pal.colors.link}]}> 340 342 <Text type="md-medium" style={[pal.text]}> ··· 364 366 flexDirection: 'row', 365 367 gap: 8, 366 368 marginTop: 12, 367 - }, 368 - fakeSelector: { 369 - flexDirection: 'row', 370 - paddingHorizontal: isDesktopWeb ? 16 : 6, 371 369 }, 372 370 fakeSelectorItem: { 373 371 paddingHorizontal: 12,
+2 -2
src/view/com/modals/AddAppPasswords.tsx
··· 5 5 import {s} from 'lib/styles' 6 6 import {useStores} from 'state/index' 7 7 import {usePalette} from 'lib/hooks/usePalette' 8 - import {isDesktopWeb} from 'platform/detection' 8 + import {isNative} from 'platform/detection' 9 9 import { 10 10 FontAwesomeIcon, 11 11 FontAwesomeIconStyle, ··· 205 205 const styles = StyleSheet.create({ 206 206 container: { 207 207 flex: 1, 208 - paddingBottom: isDesktopWeb ? 0 : 50, 208 + paddingBottom: isNative ? 50 : 0, 209 209 paddingHorizontal: 16, 210 210 }, 211 211 textInputWrapper: {
+4 -4
src/view/com/modals/AltImage.tsx
··· 18 18 import {Text} from '../util/text/Text' 19 19 import LinearGradient from 'react-native-linear-gradient' 20 20 import {useStores} from 'state/index' 21 - import {isDesktopWeb, isAndroid} from 'platform/detection' 21 + import {isAndroid, isWeb} from 'platform/detection' 22 22 import {ImageModel} from 'state/models/media/image' 23 23 24 24 export const snapPoints = ['fullscreen'] ··· 35 35 const windim = useWindowDimensions() 36 36 37 37 const imageStyles = useMemo<ImageStyle>(() => { 38 - const maxWidth = isDesktopWeb ? 450 : windim.width 38 + const maxWidth = isWeb ? 450 : windim.width 39 39 if (image.height > image.width) { 40 40 return { 41 41 resizeMode: 'contain', ··· 137 137 flex: 1, 138 138 height: '100%', 139 139 width: '100%', 140 - paddingVertical: isDesktopWeb ? 0 : 18, 140 + paddingVertical: isWeb ? 0 : 18, 141 141 }, 142 142 scrollContainer: { 143 143 flex: 1, 144 144 height: '100%', 145 - paddingHorizontal: isDesktopWeb ? 0 : 12, 145 + paddingHorizontal: isWeb ? 0 : 12, 146 146 }, 147 147 scrollInner: { 148 148 gap: 12,
+2 -2
src/view/com/modals/Confirm.tsx
··· 11 11 import {ErrorMessage} from '../util/error/ErrorMessage' 12 12 import {cleanError} from 'lib/strings/errors' 13 13 import {usePalette} from 'lib/hooks/usePalette' 14 - import {isDesktopWeb} from 'platform/detection' 14 + import {isWeb} from 'platform/detection' 15 15 import type {ConfirmModal} from 'state/models/ui/shell' 16 16 17 17 export const snapPoints = ['50%'] ··· 96 96 container: { 97 97 flex: 1, 98 98 padding: 10, 99 - paddingBottom: isDesktopWeb ? 0 : 60, 99 + paddingBottom: isWeb ? 0 : 60, 100 100 }, 101 101 title: { 102 102 textAlign: 'center',
+14 -8
src/view/com/modals/ContentFilteringSettings.tsx
··· 11 11 import {ToggleButton} from '../util/forms/ToggleButton' 12 12 import {usePalette} from 'lib/hooks/usePalette' 13 13 import {CONFIGURABLE_LABEL_GROUPS} from 'lib/labeling/const' 14 - import {isDesktopWeb, isIOS} from 'platform/detection' 14 + import {isIOS} from 'platform/detection' 15 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 15 16 import * as Toast from '../util/Toast' 16 17 17 18 export const snapPoints = ['90%'] 18 19 19 20 export const Component = observer(({}: {}) => { 20 21 const store = useStores() 22 + const {isMobile} = useWebMediaQueries() 21 23 const pal = usePalette('default') 22 24 23 25 React.useEffect(() => { ··· 88 90 <ContentLabelPref group="hate" /> 89 91 <ContentLabelPref group="spam" /> 90 92 <ContentLabelPref group="impersonation" /> 91 - <View style={styles.bottomSpacer} /> 93 + <View style={{height: isMobile ? 60 : 0}} /> 92 94 </ScrollView> 93 - <View style={[styles.btnContainer, pal.borderDark]}> 95 + <View 96 + style={[ 97 + styles.btnContainer, 98 + isMobile && styles.btnContainerMobile, 99 + pal.borderDark, 100 + ]}> 94 101 <Pressable 95 102 testID="sendReportBtn" 96 103 onPress={onPressDone} ··· 259 266 flex: 1, 260 267 paddingHorizontal: 10, 261 268 }, 262 - bottomSpacer: { 263 - height: isDesktopWeb ? 0 : 60, 264 - }, 265 269 btnContainer: { 266 270 paddingTop: 10, 267 271 paddingHorizontal: 10, 268 - paddingBottom: isDesktopWeb ? 0 : 40, 269 - borderTopWidth: isDesktopWeb ? 0 : 1, 272 + }, 273 + btnContainerMobile: { 274 + paddingBottom: 40, 275 + borderTopWidth: 1, 270 276 }, 271 277 272 278 contentLabelPref: {
+8 -5
src/view/com/modals/CreateOrEditMuteList.tsx
··· 22 22 import {usePalette} from 'lib/hooks/usePalette' 23 23 import {useTheme} from 'lib/ThemeContext' 24 24 import {useAnalytics} from 'lib/analytics/analytics' 25 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 25 26 import {cleanError, isNetworkError} from 'lib/strings/errors' 26 - import {isDesktopWeb} from 'platform/detection' 27 27 28 28 const MAX_NAME = 64 // todo 29 29 const MAX_DESCRIPTION = 300 // todo ··· 38 38 list?: ListModel 39 39 }) { 40 40 const store = useStores() 41 + const {isMobile} = useWebMediaQueries() 41 42 const [error, setError] = useState<string>('') 42 43 const pal = usePalette('default') 43 44 const theme = useTheme() ··· 130 131 return ( 131 132 <KeyboardAvoidingView behavior="height"> 132 133 <ScrollView 133 - style={[pal.view, styles.container]} 134 + style={[ 135 + pal.view, 136 + { 137 + paddingHorizontal: isMobile ? 16 : 0, 138 + }, 139 + ]} 134 140 testID="createOrEditMuteListModal"> 135 141 <Text style={[styles.title, pal.text]}> 136 142 {list ? 'Edit Mute List' : 'New Mute List'} ··· 226 232 } 227 233 228 234 const styles = StyleSheet.create({ 229 - container: { 230 - paddingHorizontal: isDesktopWeb ? 0 : 16, 231 - }, 232 235 title: { 233 236 textAlign: 'center', 234 237 fontWeight: 'bold',
+3 -2
src/view/com/modals/DeleteAccount.tsx
··· 13 13 import {s, colors, gradients} from 'lib/styles' 14 14 import {usePalette} from 'lib/hooks/usePalette' 15 15 import {useTheme} from 'lib/ThemeContext' 16 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 16 17 import {ErrorMessage} from '../util/error/ErrorMessage' 17 18 import {cleanError} from 'lib/strings/errors' 18 19 import {resetToTab} from '../../../Navigation' 19 - import {isDesktopWeb} from 'platform/detection' 20 20 21 21 export const snapPoints = ['60%'] 22 22 ··· 24 24 const pal = usePalette('default') 25 25 const theme = useTheme() 26 26 const store = useStores() 27 + const {isMobile} = useWebMediaQueries() 27 28 const [isEmailSent, setIsEmailSent] = React.useState<boolean>(false) 28 29 const [confirmCode, setConfirmCode] = React.useState<string>('') 29 30 const [password, setPassword] = React.useState<string>('') ··· 78 79 type="title-xl" 79 80 numberOfLines={1} 80 81 style={[ 81 - isDesktopWeb ? styles.titleDesktop : styles.titleMobile, 82 + isMobile ? styles.titleMobile : styles.titleDesktop, 82 83 pal.text, 83 84 s.bold, 84 85 ]}>
+25 -10
src/view/com/modals/EditImage.tsx
··· 7 7 import {Text} from '../util/text/Text' 8 8 import LinearGradient from 'react-native-linear-gradient' 9 9 import {useStores} from 'state/index' 10 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 10 11 import ImageEditor, {Position} from 'react-avatar-editor' 11 12 import {TextInput} from './util' 12 13 import {enforceLen} from 'lib/strings/helpers' ··· 18 19 import {MaterialIcons} from '@expo/vector-icons' 19 20 import {observer} from 'mobx-react-lite' 20 21 import {getKeys} from 'lib/type-assertions' 21 - import {isDesktopWeb} from 'platform/detection' 22 22 23 23 export const snapPoints = ['80%'] 24 24 ··· 51 51 const theme = useTheme() 52 52 const store = useStores() 53 53 const windowDimensions = useWindowDimensions() 54 + const {isMobile} = useWebMediaQueries() 54 55 55 56 const { 56 57 aspectRatio, ··· 174 175 175 176 const computedWidth = 176 177 windowDimensions.width > 500 ? 410 : windowDimensions.width - 80 177 - const sideLength = isDesktopWeb ? 300 : computedWidth 178 + const sideLength = isMobile ? computedWidth : 300 178 179 179 180 const dimensions = image.getResizedDimensions(aspectRatio, sideLength) 180 181 const imgContainerStyles = {width: sideLength, height: sideLength} 181 182 182 183 const imgControlStyles = { 183 184 alignItems: 'center' as const, 184 - flexDirection: isDesktopWeb ? ('row' as const) : ('column' as const), 185 - gap: isDesktopWeb ? 5 : 0, 185 + flexDirection: isMobile ? ('column' as const) : ('row' as const), 186 + gap: isMobile ? 0 : 5, 186 187 } 187 188 188 189 return ( 189 - <View testID="editImageModal" style={[pal.view, styles.container, s.flex1]}> 190 + <View 191 + testID="editImageModal" 192 + style={[ 193 + pal.view, 194 + styles.container, 195 + s.flex1, 196 + { 197 + paddingHorizontal: isMobile ? 16 : undefined, 198 + }, 199 + ]}> 190 200 <Text style={[styles.title, pal.text]}>Edit image</Text> 191 201 <View style={[styles.gap18, s.flexRow]}> 192 202 <View> ··· 213 223 /> 214 224 </View> 215 225 <View> 216 - {isDesktopWeb ? ( 226 + {!isMobile ? ( 217 227 <Text type="sm-bold" style={pal.text}> 218 228 Ratios 219 229 </Text> ··· 248 258 ) 249 259 })} 250 260 </View> 251 - {isDesktopWeb ? ( 261 + {!isMobile ? ( 252 262 <Text type="sm-bold" style={[pal.text, styles.subsection]}> 253 263 Transformations 254 264 </Text> ··· 282 292 </Text> 283 293 <TextInput 284 294 testID="altTextImageInput" 285 - style={[styles.textArea, pal.border, pal.text]} 295 + style={[ 296 + styles.textArea, 297 + pal.border, 298 + pal.text, 299 + { 300 + maxHeight: isMobile ? 50 : undefined, 301 + }, 302 + ]} 286 303 keyboardAppearance={theme.colorScheme} 287 304 multiline 288 305 value={altText} ··· 317 334 const styles = StyleSheet.create({ 318 335 container: { 319 336 gap: 18, 320 - paddingHorizontal: isDesktopWeb ? undefined : 16, 321 337 height: '100%', 322 338 width: '100%', 323 339 }, ··· 369 385 fontSize: 16, 370 386 height: 100, 371 387 textAlignVertical: 'top', 372 - maxHeight: isDesktopWeb ? undefined : 50, 373 388 }, 374 389 bottomSection: { 375 390 borderTopWidth: 1,
+2 -2
src/view/com/modals/InviteCodes.tsx
··· 12 12 import {useStores} from 'state/index' 13 13 import {ScrollView} from './util' 14 14 import {usePalette} from 'lib/hooks/usePalette' 15 - import {isDesktopWeb} from 'platform/detection' 15 + import {isWeb} from 'platform/detection' 16 16 17 17 export const snapPoints = ['70%'] 18 18 ··· 127 127 const styles = StyleSheet.create({ 128 128 container: { 129 129 flex: 1, 130 - paddingBottom: isDesktopWeb ? 0 : 50, 130 + paddingBottom: isWeb ? 0 : 50, 131 131 }, 132 132 title: { 133 133 textAlign: 'center',
+2 -2
src/view/com/modals/ListAddRemoveUser.tsx
··· 19 19 import {sanitizeHandle} from 'lib/strings/handles' 20 20 import {s} from 'lib/styles' 21 21 import {usePalette} from 'lib/hooks/usePalette' 22 - import {isDesktopWeb, isAndroid} from 'platform/detection' 22 + import {isWeb, isAndroid} from 'platform/detection' 23 23 import isEqual from 'lodash.isequal' 24 24 25 25 export const snapPoints = ['fullscreen'] ··· 231 231 232 232 const styles = StyleSheet.create({ 233 233 container: { 234 - paddingHorizontal: isDesktopWeb ? 0 : 16, 234 + paddingHorizontal: isWeb ? 0 : 16, 235 235 }, 236 236 title: { 237 237 textAlign: 'center',
+3 -2
src/view/com/modals/Modal.web.tsx
··· 3 3 import {observer} from 'mobx-react-lite' 4 4 import {useStores} from 'state/index' 5 5 import {usePalette} from 'lib/hooks/usePalette' 6 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 6 7 import type {Modal as ModalIface} from 'state/models/ui/shell' 7 - import {isMobileWeb} from 'platform/detection' 8 8 9 9 import * as ConfirmModal from './Confirm' 10 10 import * as EditProfileModal from './EditProfile' ··· 47 47 function Modal({modal}: {modal: ModalIface}) { 48 48 const store = useStores() 49 49 const pal = usePalette('default') 50 + const {isMobile} = useWebMediaQueries() 50 51 51 52 if (!store.shell.isModalActive) { 52 53 return null ··· 119 120 <View 120 121 style={[ 121 122 styles.container, 122 - isMobileWeb && styles.containerMobile, 123 + isMobile && styles.containerMobile, 123 124 pal.view, 124 125 pal.border, 125 126 ]}>
+14 -5
src/view/com/modals/ModerationDetails.tsx
··· 2 2 import {StyleSheet, View} from 'react-native' 3 3 import {ModerationUI} from '@atproto/api' 4 4 import {useStores} from 'state/index' 5 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 5 6 import {s} from 'lib/styles' 6 7 import {Text} from '../util/text/Text' 7 8 import {TextLink} from '../util/Link' 8 9 import {usePalette} from 'lib/hooks/usePalette' 9 - import {isDesktopWeb} from 'platform/detection' 10 + import {isWeb} from 'platform/detection' 10 11 import {listUriToHref} from 'lib/strings/url-helpers' 11 12 import {Button} from '../util/forms/Button' 12 13 ··· 20 21 moderation: ModerationUI 21 22 }) { 22 23 const store = useStores() 24 + const {isMobile} = useWebMediaQueries() 23 25 const pal = usePalette('default') 24 26 25 27 let name ··· 64 66 } 65 67 66 68 return ( 67 - <View testID="moderationDetailsModal" style={[styles.container, pal.view]}> 69 + <View 70 + testID="moderationDetailsModal" 71 + style={[ 72 + styles.container, 73 + { 74 + paddingHorizontal: isMobile ? 14 : 0, 75 + }, 76 + pal.view, 77 + ]}> 68 78 <Text type="title-xl" style={[pal.text, styles.title]}> 69 79 {name} 70 80 </Text> ··· 87 97 const styles = StyleSheet.create({ 88 98 container: { 89 99 flex: 1, 90 - paddingHorizontal: isDesktopWeb ? 0 : 14, 91 100 }, 92 101 title: { 93 102 textAlign: 'center', ··· 99 108 }, 100 109 btn: { 101 110 paddingVertical: 14, 102 - marginTop: isDesktopWeb ? 40 : 0, 103 - marginBottom: isDesktopWeb ? 0 : 40, 111 + marginTop: isWeb ? 40 : 0, 112 + marginBottom: isWeb ? 0 : 40, 104 113 }, 105 114 })
+12 -6
src/view/com/modals/SelfLabel.tsx
··· 5 5 import {useStores} from 'state/index' 6 6 import {s, colors} from 'lib/styles' 7 7 import {usePalette} from 'lib/hooks/usePalette' 8 - import {isDesktopWeb} from 'platform/detection' 8 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 9 + import {isWeb} from 'platform/detection' 9 10 import {Button} from '../util/forms/Button' 10 11 import {SelectableBtn} from '../util/forms/SelectableBtn' 11 12 import {ScrollView} from 'view/com/modals/util' ··· 25 26 }) { 26 27 const pal = usePalette('default') 27 28 const store = useStores() 29 + const {isMobile} = useWebMediaQueries() 28 30 const [selected, setSelected] = useState(labels) 29 31 30 32 const toggleAdultLabel = (label: string) => { ··· 54 56 </View> 55 57 56 58 <ScrollView> 57 - <View style={[styles.section, pal.border, {borderBottomWidth: 1}]}> 59 + <View 60 + style={[ 61 + styles.section, 62 + pal.border, 63 + {borderBottomWidth: 1, paddingHorizontal: isMobile ? 20 : 0}, 64 + ]}> 58 65 <View 59 66 style={{ 60 67 flexDirection: 'row', ··· 152 159 const styles = StyleSheet.create({ 153 160 container: { 154 161 flex: 1, 155 - paddingBottom: isDesktopWeb ? 0 : 40, 162 + paddingBottom: isWeb ? 0 : 40, 156 163 }, 157 164 titleSection: { 158 - paddingTop: isDesktopWeb ? 0 : 4, 159 - paddingBottom: isDesktopWeb ? 14 : 10, 165 + paddingTop: isWeb ? 0 : 4, 166 + paddingBottom: isWeb ? 14 : 10, 160 167 }, 161 168 title: { 162 169 textAlign: 'center', ··· 170 177 section: { 171 178 borderTopWidth: 1, 172 179 paddingVertical: 20, 173 - paddingHorizontal: isDesktopWeb ? 0 : 20, 174 180 }, 175 181 adultExplainer: { 176 182 paddingLeft: 5,
+11 -4
src/view/com/modals/lang-settings/ConfirmLanguagesButton.tsx
··· 2 2 import {StyleSheet, Text, View, Pressable} from 'react-native' 3 3 import LinearGradient from 'react-native-linear-gradient' 4 4 import {s, colors, gradients} from 'lib/styles' 5 - import {isDesktopWeb} from 'platform/detection' 6 5 import {usePalette} from 'lib/hooks/usePalette' 6 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 7 7 8 8 export const ConfirmLanguagesButton = ({ 9 9 onPress, ··· 13 13 extraText?: string 14 14 }) => { 15 15 const pal = usePalette('default') 16 + const {isMobile} = useWebMediaQueries() 16 17 return ( 17 - <View style={[styles.btnContainer, pal.borderDark]}> 18 + <View 19 + style={[ 20 + styles.btnContainer, 21 + pal.borderDark, 22 + isMobile && { 23 + paddingBottom: 40, 24 + borderTopWidth: 1, 25 + }, 26 + ]}> 18 27 <Pressable 19 28 testID="confirmContentLanguagesBtn" 20 29 onPress={onPress} ··· 37 46 btnContainer: { 38 47 paddingTop: 10, 39 48 paddingHorizontal: 10, 40 - paddingBottom: isDesktopWeb ? 0 : 40, 41 - borderTopWidth: isDesktopWeb ? 0 : 1, 42 49 }, 43 50 btn: { 44 51 flexDirection: 'row',
+21 -7
src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx
··· 4 4 import {useStores} from 'state/index' 5 5 import {Text} from '../../util/text/Text' 6 6 import {usePalette} from 'lib/hooks/usePalette' 7 - import {isDesktopWeb, deviceLocales} from 'platform/detection' 7 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 8 + import {deviceLocales} from 'platform/detection' 8 9 import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../../locale/languages' 9 10 import {LanguageToggle} from './LanguageToggle' 10 11 import {ConfirmLanguagesButton} from './ConfirmLanguagesButton' ··· 14 15 export function Component({}: {}) { 15 16 const store = useStores() 16 17 const pal = usePalette('default') 18 + const {isMobile} = useWebMediaQueries() 17 19 const onPressDone = React.useCallback(() => { 18 20 store.shell.closeModal() 19 21 }, [store]) ··· 47 49 ) 48 50 49 51 return ( 50 - <View testID="contentLanguagesModal" style={[pal.view, styles.container]}> 52 + <View 53 + testID="contentLanguagesModal" 54 + style={[ 55 + pal.view, 56 + styles.container, 57 + isMobile 58 + ? { 59 + paddingTop: 20, 60 + } 61 + : { 62 + maxHeight: '90vh', 63 + }, 64 + ]}> 51 65 <Text style={[pal.text, styles.title]}>Content Languages</Text> 52 66 <Text style={[pal.text, styles.description]}> 53 67 Which languages would you like to see in your algorithmic feeds? ··· 67 81 }} 68 82 /> 69 83 ))} 70 - <View style={styles.bottomSpacer} /> 84 + <View 85 + style={{ 86 + height: isMobile ? 60 : 0, 87 + }} 88 + /> 71 89 </ScrollView> 72 90 <ConfirmLanguagesButton onPress={onPressDone} /> 73 91 </View> ··· 77 95 const styles = StyleSheet.create({ 78 96 container: { 79 97 flex: 1, 80 - paddingTop: 20, 81 98 }, 82 99 title: { 83 100 textAlign: 'center', ··· 93 110 scrollContainer: { 94 111 flex: 1, 95 112 paddingHorizontal: 10, 96 - }, 97 - bottomSpacer: { 98 - height: isDesktopWeb ? 0 : 60, 99 113 }, 100 114 })
+21 -7
src/view/com/modals/lang-settings/PostLanguagesSettings.tsx
··· 5 5 import {useStores} from 'state/index' 6 6 import {Text} from '../../util/text/Text' 7 7 import {usePalette} from 'lib/hooks/usePalette' 8 - import {isDesktopWeb, deviceLocales} from 'platform/detection' 8 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 9 + import {deviceLocales} from 'platform/detection' 9 10 import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../../locale/languages' 10 11 import {ConfirmLanguagesButton} from './ConfirmLanguagesButton' 11 12 import {ToggleButton} from 'view/com/util/forms/ToggleButton' ··· 15 16 export const Component = observer(() => { 16 17 const store = useStores() 17 18 const pal = usePalette('default') 19 + const {isMobile} = useWebMediaQueries() 18 20 const onPressDone = React.useCallback(() => { 19 21 store.shell.closeModal() 20 22 }, [store]) ··· 48 50 ) 49 51 50 52 return ( 51 - <View testID="postLanguagesModal" style={[pal.view, styles.container]}> 53 + <View 54 + testID="postLanguagesModal" 55 + style={[ 56 + pal.view, 57 + styles.container, 58 + isMobile 59 + ? { 60 + paddingTop: 20, 61 + } 62 + : { 63 + maxHeight: '90vh', 64 + }, 65 + ]}> 52 66 <Text style={[pal.text, styles.title]}>Post Languages</Text> 53 67 <Text style={[pal.text, styles.description]}> 54 68 Which languages are used in this post? ··· 80 94 /> 81 95 ) 82 96 })} 83 - <View style={styles.bottomSpacer} /> 97 + <View 98 + style={{ 99 + height: isMobile ? 60 : 0, 100 + }} 101 + /> 84 102 </ScrollView> 85 103 <ConfirmLanguagesButton onPress={onPressDone} /> 86 104 </View> ··· 90 108 const styles = StyleSheet.create({ 91 109 container: { 92 110 flex: 1, 93 - paddingTop: 20, 94 111 }, 95 112 title: { 96 113 textAlign: 'center', ··· 106 123 scrollContainer: { 107 124 flex: 1, 108 125 paddingHorizontal: 10, 109 - }, 110 - bottomSpacer: { 111 - height: isDesktopWeb ? 0 : 60, 112 126 }, 113 127 languageToggle: { 114 128 borderTopWidth: 1,
+6 -5
src/view/com/modals/report/InputIssueDetails.tsx
··· 5 5 import {CharProgress} from '../../composer/char-progress/CharProgress' 6 6 import {Text} from '../../util/text/Text' 7 7 import {usePalette} from 'lib/hooks/usePalette' 8 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 8 9 import {s} from 'lib/styles' 9 10 import {SendReportButton} from './SendReportButton' 10 - import {isDesktopWeb} from 'platform/detection' 11 11 12 12 export function InputIssueDetails({ 13 13 details, ··· 23 23 isProcessing: boolean 24 24 }) { 25 25 const pal = usePalette('default') 26 + const {isMobile} = useWebMediaQueries() 26 27 27 28 return ( 28 - <View style={[styles.detailsContainer]}> 29 + <View 30 + style={{ 31 + marginTop: isMobile ? 12 : 0, 32 + }}> 29 33 <TouchableOpacity 30 34 testID="addDetailsBtn" 31 35 style={[s.mb10, styles.backBtn]} ··· 63 67 } 64 68 65 69 const styles = StyleSheet.create({ 66 - detailsContainer: { 67 - marginTop: isDesktopWeb ? 0 : 12, 68 - }, 69 70 backBtn: { 70 71 flexDirection: 'row', 71 72 alignItems: 'center',
+17 -12
src/view/com/modals/report/Modal.tsx
··· 3 3 import {ScrollView} from 'react-native-gesture-handler' 4 4 import {AtUri} from '@atproto/api' 5 5 import {useStores} from 'state/index' 6 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 6 7 import {s} from 'lib/styles' 7 8 import {Text} from '../../util/text/Text' 8 9 import * as Toast from '../../util/Toast' ··· 37 38 export function Component(content: ReportComponentProps) { 38 39 const store = useStores() 39 40 const pal = usePalette('default') 41 + const {isMobile} = useWebMediaQueries() 40 42 const [isProcessing, setIsProcessing] = useState(false) 41 43 const [showDetailsInput, setShowDetailsInput] = useState(false) 42 44 const [error, setError] = useState<string>() ··· 87 89 88 90 return ( 89 91 <ScrollView testID="reportModal" style={[s.flex1, pal.view]}> 90 - <View style={styles.container}> 92 + <View 93 + style={[ 94 + styles.container, 95 + isMobile && { 96 + paddingBottom: 40, 97 + }, 98 + ]}> 91 99 {showDetailsInput ? ( 92 100 <InputIssueDetails 93 101 details={details} ··· 153 161 <Text style={[pal.textLight, styles.description]}> 154 162 What is the issue with this {collectionName}? 155 163 </Text> 156 - <ReportReasonOptions 157 - atUri={atUri} 158 - selectedIssue={issue} 159 - onSelectIssue={onSelectIssue} 160 - /> 161 - {error ? ( 162 - <View style={s.mt10}> 163 - <ErrorMessage message={error} /> 164 - </View> 165 - ) : undefined} 164 + <View style={{marginBottom: 10}}> 165 + <ReportReasonOptions 166 + atUri={atUri} 167 + selectedIssue={issue} 168 + onSelectIssue={onSelectIssue} 169 + /> 170 + </View> 171 + {error ? <ErrorMessage message={error} /> : undefined} 166 172 {/* If no atUri is present, the report would be for account in which case, we allow sending without specifying a reason */} 167 173 {issue || !atUri ? ( 168 174 <> ··· 188 194 const styles = StyleSheet.create({ 189 195 container: { 190 196 paddingHorizontal: 10, 191 - paddingBottom: 40, 192 197 }, 193 198 title: { 194 199 textAlign: 'center',
+2 -2
src/view/com/pager/FeedsTabBar.web.tsx
··· 13 13 ( 14 14 props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void}, 15 15 ) => { 16 - const {isDesktop} = useWebMediaQueries() 17 - if (!isDesktop) { 16 + const {isMobile} = useWebMediaQueries() 17 + if (isMobile) { 18 18 return <FeedsTabBarMobile {...props} /> 19 19 } else { 20 20 return <FeedsTabBarDesktop {...props} />
+48 -45
src/view/com/pager/TabBar.tsx
··· 3 3 import {Text} from '../util/text/Text' 4 4 import {PressableWithHover} from '../util/PressableWithHover' 5 5 import {usePalette} from 'lib/hooks/usePalette' 6 - import {isDesktopWeb, isMobileWeb} from 'platform/detection' 6 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 7 + import {isWeb} from 'platform/detection' 7 8 import {DraggableScrollView} from './DraggableScrollView' 8 9 9 10 export interface TabBarProps { ··· 30 31 () => ({borderBottomColor: indicatorColor || pal.colors.link}), 31 32 [indicatorColor, pal], 32 33 ) 34 + const {isDesktop, isTablet} = useWebMediaQueries() 33 35 34 36 // scrolls to the selected item when the page changes 35 37 useEffect(() => { ··· 61 63 [], 62 64 ) 63 65 66 + const styles = isDesktop || isTablet ? desktopStyles : mobileStyles 64 67 return ( 65 68 <View testID={testID} style={[pal.view, styles.outer]}> 66 69 <DraggableScrollView ··· 78 81 hoverStyle={pal.viewLight} 79 82 onPress={() => onPressItem(i)}> 80 83 <Text 81 - type={isDesktopWeb ? 'xl-bold' : 'lg-bold'} 84 + type={isDesktop || isTablet ? 'xl-bold' : 'lg-bold'} 82 85 testID={testID ? `${testID}-${item}` : undefined} 83 86 style={selected ? pal.text : pal.textLight}> 84 87 {item} ··· 91 94 ) 92 95 } 93 96 94 - const styles = isDesktopWeb 95 - ? StyleSheet.create({ 96 - outer: { 97 - flexDirection: 'row', 98 - width: 598, 99 - }, 100 - contentContainer: { 101 - columnGap: 8, 102 - marginLeft: 14, 103 - paddingRight: 14, 104 - backgroundColor: 'transparent', 105 - }, 106 - item: { 107 - paddingTop: 14, 108 - paddingBottom: 12, 109 - paddingHorizontal: 10, 110 - borderBottomWidth: 3, 111 - borderBottomColor: 'transparent', 112 - justifyContent: 'center', 113 - }, 114 - }) 115 - : StyleSheet.create({ 116 - outer: { 117 - flex: 1, 118 - flexDirection: 'row', 119 - backgroundColor: 'transparent', 120 - maxWidth: '100%', 121 - }, 122 - contentContainer: { 123 - columnGap: isMobileWeb ? 0 : 20, 124 - marginLeft: isMobileWeb ? 0 : 18, 125 - paddingRight: isMobileWeb ? 0 : 36, 126 - backgroundColor: 'transparent', 127 - }, 128 - item: { 129 - paddingTop: 10, 130 - paddingBottom: 10, 131 - paddingHorizontal: isMobileWeb ? 8 : 0, 132 - borderBottomWidth: 3, 133 - borderBottomColor: 'transparent', 134 - justifyContent: 'center', 135 - }, 136 - }) 97 + const desktopStyles = StyleSheet.create({ 98 + outer: { 99 + flexDirection: 'row', 100 + width: 598, 101 + }, 102 + contentContainer: { 103 + columnGap: 8, 104 + marginLeft: 14, 105 + paddingRight: 14, 106 + backgroundColor: 'transparent', 107 + }, 108 + item: { 109 + paddingTop: 14, 110 + paddingBottom: 12, 111 + paddingHorizontal: 10, 112 + borderBottomWidth: 3, 113 + borderBottomColor: 'transparent', 114 + justifyContent: 'center', 115 + }, 116 + }) 117 + 118 + const mobileStyles = StyleSheet.create({ 119 + outer: { 120 + flex: 1, 121 + flexDirection: 'row', 122 + backgroundColor: 'transparent', 123 + maxWidth: '100%', 124 + }, 125 + contentContainer: { 126 + columnGap: isWeb ? 0 : 20, 127 + marginLeft: isWeb ? 0 : 18, 128 + paddingRight: isWeb ? 0 : 36, 129 + backgroundColor: 'transparent', 130 + }, 131 + item: { 132 + paddingTop: 10, 133 + paddingBottom: 10, 134 + paddingHorizontal: isWeb ? 8 : 0, 135 + borderBottomWidth: 3, 136 + borderBottomColor: 'transparent', 137 + justifyContent: 'center', 138 + }, 139 + })
+35 -33
src/view/com/posts/MultiFeed.tsx
··· 22 22 import {useAnalytics} from 'lib/analytics/analytics' 23 23 import {usePalette} from 'lib/hooks/usePalette' 24 24 import {useTheme} from 'lib/ThemeContext' 25 - import {isDesktopWeb} from 'platform/detection' 25 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 26 26 import {CogIcon} from 'lib/icons' 27 27 28 28 export const MultiFeed = observer(function Feed({ ··· 48 48 }) { 49 49 const pal = usePalette('default') 50 50 const theme = useTheme() 51 + const {isMobile} = useWebMediaQueries() 51 52 const {track} = useAnalytics() 52 53 const [isRefreshing, setIsRefreshing] = React.useState(false) 53 54 ··· 80 81 const renderItem = React.useCallback( 81 82 ({item}: {item: MultiFeedItem}) => { 82 83 if (item.type === 'header') { 83 - if (isDesktopWeb) { 84 + if (!isMobile) { 84 85 return ( 85 - <View style={[pal.view, pal.border, styles.headerDesktop]}> 86 - <Text type="2xl-bold" style={pal.text}> 87 - My Feeds 88 - </Text> 89 - <Link href="/settings/saved-feeds"> 90 - <CogIcon strokeWidth={1.5} style={pal.icon} size={28} /> 91 - </Link> 92 - </View> 86 + <> 87 + <View style={[pal.view, pal.border, styles.headerDesktop]}> 88 + <Text type="2xl-bold" style={pal.text}> 89 + My Feeds 90 + </Text> 91 + <Link href="/settings/saved-feeds"> 92 + <CogIcon strokeWidth={1.5} style={pal.icon} size={28} /> 93 + </Link> 94 + </View> 95 + <DiscoverLink /> 96 + </> 93 97 ) 94 98 } 95 - return <View style={[styles.header, pal.border]} /> 99 + return ( 100 + <> 101 + <View style={[styles.header, pal.border]} /> 102 + <DiscoverLink /> 103 + </> 104 + ) 96 105 } else if (item.type === 'feed-header') { 97 106 return ( 98 107 <View style={styles.feedHeader}> ··· 124 133 </Link> 125 134 ) 126 135 } else if (item.type === 'footer') { 127 - return ( 128 - <Link style={[styles.footerLink, pal.viewLight]} href="/search/feeds"> 129 - <FontAwesomeIcon icon="search" size={18} color={pal.colors.text} /> 130 - <Text type="xl-medium" style={pal.text}> 131 - Discover new feeds 132 - </Text> 133 - </Link> 134 - ) 136 + return <DiscoverLink /> 135 137 } 136 138 return null 137 139 }, 138 - [pal], 140 + [pal, isMobile], 139 141 ) 140 142 141 143 const ListFooter = React.useCallback( ··· 150 152 [multifeed.isLoading, isRefreshing, pal], 151 153 ) 152 154 153 - const ListHeader = React.useCallback(() => { 154 - return ( 155 - <Link style={[styles.footerLink, pal.viewLight]} href="/search/feeds"> 156 - <FontAwesomeIcon icon="search" size={18} color={pal.colors.text} /> 157 - <Text type="xl-medium" style={pal.text}> 158 - Discover new feeds 159 - </Text> 160 - </Link> 161 - ) 162 - }, [pal]) 163 - 164 155 return ( 165 156 <View testID={testID} style={style}> 166 157 {multifeed.items.length > 0 && ( ··· 171 162 keyExtractor={item => item._reactKey} 172 163 renderItem={renderItem} 173 164 ListFooterComponent={ListFooter} 174 - ListHeaderComponent={ListHeader} 175 165 refreshControl={ 176 166 <RefreshControl 177 167 refreshing={isRefreshing} ··· 199 189 ) 200 190 }) 201 191 192 + function DiscoverLink() { 193 + const pal = usePalette('default') 194 + return ( 195 + <Link style={[styles.discoverLink, pal.viewLight]} href="/search/feeds"> 196 + <FontAwesomeIcon icon="search" size={18} color={pal.colors.text} /> 197 + <Text type="xl-medium" style={pal.text}> 198 + Discover new feeds 199 + </Text> 200 + </Link> 201 + ) 202 + } 203 + 202 204 const styles = StyleSheet.create({ 203 205 container: { 204 206 height: '100%', ··· 237 239 borderTopWidth: 1, 238 240 borderBottomWidth: 1, 239 241 }, 240 - footerLink: { 242 + discoverLink: { 241 243 flexDirection: 'row', 242 244 alignItems: 'center', 243 245 justifyContent: 'center',
+4 -2
src/view/com/profile/ProfileHeader.tsx
··· 27 27 import {ProfileHeaderAlerts} from '../util/moderation/ProfileHeaderAlerts' 28 28 import {usePalette} from 'lib/hooks/usePalette' 29 29 import {useAnalytics} from 'lib/analytics/analytics' 30 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 30 31 import {NavigationProp} from 'lib/routes/types' 31 - import {isDesktopWeb, isNative} from 'platform/detection' 32 + import {isNative} from 'platform/detection' 32 33 import {FollowState} from 'state/models/cache/my-follows' 33 34 import {shareUrl} from 'lib/sharing' 34 35 import {formatCount} from '../util/numeric/format' ··· 108 109 const navigation = useNavigation<NavigationProp>() 109 110 const {track} = useAnalytics() 110 111 const invalidHandle = isInvalidHandle(view.handle) 112 + const {isDesktop} = useWebMediaQueries() 111 113 112 114 const onPressBack = React.useCallback(() => { 113 115 navigation.goBack() ··· 510 512 )} 511 513 <ProfileHeaderAlerts moderation={view.moderation} /> 512 514 </View> 513 - {!isDesktopWeb && !hideBackButton && ( 515 + {!isDesktop && !hideBackButton && ( 514 516 <TouchableWithoutFeedback 515 517 onPress={onPressBack} 516 518 hitSlop={BACK_HITSLOP}
+10 -6
src/view/com/search/SearchResults.tsx
··· 13 13 } from 'view/com/util/LoadingPlaceholder' 14 14 import {Text} from 'view/com/util/text/Text' 15 15 import {usePalette} from 'lib/hooks/usePalette' 16 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 16 17 import {s} from 'lib/styles' 17 - import {isDesktopWeb} from 'platform/detection' 18 18 19 19 const SECTIONS = ['Posts', 'Users'] 20 20 21 21 export const SearchResults = observer(({model}: {model: SearchUIModel}) => { 22 22 const pal = usePalette('default') 23 + const {isMobile} = useWebMediaQueries() 23 24 24 25 const renderTabBar = React.useCallback( 25 26 (props: RenderTabBarFnProps) => { ··· 39 40 40 41 return ( 41 42 <Pager renderTabBar={renderTabBar} tabBarPosition="top" initialPage={0}> 42 - <View style={[styles.results]}> 43 + <View 44 + style={{ 45 + paddingTop: isMobile ? 42 : 50, 46 + }}> 43 47 <PostResults key="0" model={model} /> 44 48 </View> 45 - <View style={[styles.results]}> 49 + <View 50 + style={{ 51 + paddingTop: isMobile ? 42 : 50, 52 + }}> 46 53 <Profiles key="1" model={model} /> 47 54 </View> 48 55 </Pager> ··· 127 134 empty: { 128 135 paddingHorizontal: 14, 129 136 paddingVertical: 16, 130 - }, 131 - results: { 132 - paddingTop: isDesktopWeb ? 50 : 42, 133 137 }, 134 138 })
+8 -2
src/view/com/util/ViewHeader.tsx
··· 8 8 import {useStores} from 'state/index' 9 9 import {usePalette} from 'lib/hooks/usePalette' 10 10 import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' 11 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 11 12 import {useAnalytics} from 'lib/analytics/analytics' 12 13 import {NavigationProp} from 'lib/routes/types' 13 - import {isDesktopWeb} from 'platform/detection' 14 14 15 15 const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20} 16 16 ··· 35 35 const store = useStores() 36 36 const navigation = useNavigation<NavigationProp>() 37 37 const {track} = useAnalytics() 38 + const {isDesktop} = useWebMediaQueries() 38 39 39 40 const onPressBack = React.useCallback(() => { 40 41 if (navigation.canGoBack()) { ··· 49 50 store.shell.openDrawer() 50 51 }, [track, store]) 51 52 52 - if (isDesktopWeb) { 53 + if (isDesktop) { 53 54 if (showOnDesktop) { 54 55 return ( 55 56 <DesktopWebHeader ··· 208 209 alignItems: 'center', 209 210 paddingHorizontal: 12, 210 211 paddingVertical: 6, 212 + width: '100%', 213 + maxWidth: 600, 214 + marginLeft: 'auto', 215 + marginRight: 'auto', 211 216 }, 212 217 headerFloating: { 213 218 position: 'absolute', 214 219 top: 0, 215 220 width: '100%', 221 + maxWidth: 'none', 216 222 }, 217 223 desktopHeader: { 218 224 paddingVertical: 12,
+8
src/view/com/util/Views.web.tsx
··· 24 24 } from 'react-native' 25 25 import {addStyle} from 'lib/styles' 26 26 import {usePalette} from 'lib/hooks/usePalette' 27 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 27 28 28 29 interface AddedProps { 29 30 desktopFixedHeight?: boolean ··· 48 49 ref: React.Ref<RNFlatList>, 49 50 ) { 50 51 const pal = usePalette('default') 52 + const {isMobile} = useWebMediaQueries() 51 53 contentContainerStyle = addStyle( 52 54 contentContainerStyle, 53 55 styles.containerScroll, ··· 67 69 } 68 70 if (desktopFixedHeight) { 69 71 style = addStyle(style, styles.fixedHeight) 72 + if (!isMobile) { 73 + style = addStyle(style, styles.stableGutters) 74 + } 70 75 } 71 76 return ( 72 77 <RNFlatList ··· 126 131 }, 127 132 fixedHeight: { 128 133 height: '100vh', 134 + }, 135 + stableGutters: { 136 + // @ts-ignore web only -prf 129 137 scrollbarGutter: 'stable both-edges', 130 138 }, 131 139 })
+28 -12
src/view/com/util/fab/FABInner.tsx
··· 5 5 import {gradients} from 'lib/styles' 6 6 import {useAnimatedValue} from 'lib/hooks/useAnimatedValue' 7 7 import {useStores} from 'state/index' 8 - import {isMobileWeb} from 'platform/detection' 8 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 9 + import {isWeb} from 'platform/detection' 9 10 10 11 export interface FABProps 11 12 extends ComponentProps<typeof TouchableWithoutFeedback> { ··· 14 15 } 15 16 16 17 export const FABInner = observer(({testID, icon, ...props}: FABProps) => { 18 + const {isTablet} = useWebMediaQueries() 17 19 const store = useStores() 18 20 const interp = useAnimatedValue(0) 19 21 React.useEffect(() => { ··· 27 29 const transform = { 28 30 transform: [{translateY: Animated.multiply(interp, 60)}], 29 31 } 32 + const size = isTablet ? styles.sizeLarge : styles.sizeRegular 30 33 return ( 31 34 <TouchableWithoutFeedback testID={testID} {...props}> 32 35 <Animated.View 33 - style={[styles.outer, isMobileWeb && styles.mobileWebOuter, transform]}> 36 + style={[ 37 + styles.outer, 38 + size, 39 + isWeb && isTablet 40 + ? { 41 + right: 50, 42 + bottom: 50, 43 + } 44 + : { 45 + bottom: 114, 46 + }, 47 + transform, 48 + ]}> 34 49 <LinearGradient 35 50 colors={[gradients.blueLight.start, gradients.blueLight.end]} 36 51 start={{x: 0, y: 0}} 37 52 end={{x: 1, y: 1}} 38 - style={styles.inner}> 53 + style={[styles.inner, size]}> 39 54 {icon} 40 55 </LinearGradient> 41 56 </Animated.View> ··· 44 59 }) 45 60 46 61 const styles = StyleSheet.create({ 62 + sizeRegular: { 63 + width: 60, 64 + height: 60, 65 + borderRadius: 30, 66 + }, 67 + sizeLarge: { 68 + width: 70, 69 + height: 70, 70 + borderRadius: 35, 71 + }, 47 72 outer: { 48 73 position: 'absolute', 49 74 zIndex: 1, 50 75 right: 24, 51 76 bottom: 94, 52 - width: 60, 53 - height: 60, 54 - borderRadius: 30, 55 - }, 56 - mobileWebOuter: { 57 - bottom: 114, 58 77 }, 59 78 inner: { 60 - width: 60, 61 - height: 60, 62 - borderRadius: 30, 63 79 justifyContent: 'center', 64 80 alignItems: 'center', 65 81 },
+6 -6
src/view/com/util/forms/SelectableBtn.tsx
··· 2 2 import {Pressable, ViewStyle, StyleProp, StyleSheet} from 'react-native' 3 3 import {Text} from '../text/Text' 4 4 import {usePalette} from 'lib/hooks/usePalette' 5 - import {isDesktopWeb} from 'platform/detection' 5 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 6 6 7 7 interface SelectableBtnProps { 8 8 testID?: string ··· 28 28 const pal = usePalette('default') 29 29 const palPrimary = usePalette('inverted') 30 30 const needsWidthStyles = !style || !('width' in style || 'flex' in style) 31 + const {isMobile} = useWebMediaQueries() 31 32 return ( 32 33 <Pressable 33 34 testID={testID} 34 35 style={[ 35 36 styles.btn, 36 - needsWidthStyles && styles.btnWidth, 37 + needsWidthStyles && { 38 + flex: isMobile ? 1 : undefined, 39 + width: !isMobile ? 100 : undefined, 40 + }, 37 41 left && styles.btnLeft, 38 42 right && styles.btnRight, 39 43 pal.border, ··· 57 61 borderLeftWidth: 0, 58 62 paddingHorizontal: 10, 59 63 paddingVertical: 10, 60 - }, 61 - btnWidth: { 62 - flex: isDesktopWeb ? undefined : 1, 63 - width: isDesktopWeb ? 100 : undefined, 64 64 }, 65 65 btnLeft: { 66 66 borderTopLeftRadius: 8,
+3 -3
src/view/com/util/layouts/Breakpoints.web.tsx
··· 2 2 import MediaQuery from 'react-responsive' 3 3 4 4 export const Desktop = ({children}: React.PropsWithChildren<{}>) => ( 5 - <MediaQuery minWidth={1224}>{children}</MediaQuery> 5 + <MediaQuery minWidth={1300}>{children}</MediaQuery> 6 6 ) 7 7 export const TabletOrDesktop = ({children}: React.PropsWithChildren<{}>) => ( 8 8 <MediaQuery minWidth={800}>{children}</MediaQuery> 9 9 ) 10 10 export const Tablet = ({children}: React.PropsWithChildren<{}>) => ( 11 - <MediaQuery minWidth={800} maxWidth={1224}> 11 + <MediaQuery minWidth={800} maxWidth={1300}> 12 12 {children} 13 13 </MediaQuery> 14 14 ) 15 15 export const TabletOrMobile = ({children}: React.PropsWithChildren<{}>) => ( 16 - <MediaQuery maxWidth={1224}>{children}</MediaQuery> 16 + <MediaQuery maxWidth={1300}>{children}</MediaQuery> 17 17 ) 18 18 export const Mobile = ({children}: React.PropsWithChildren<{}>) => ( 19 19 <MediaQuery maxWidth={800}>{children}</MediaQuery>
+3 -2
src/view/com/util/load-latest/LoadLatestBtn.web.tsx
··· 3 3 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 4 4 import {Text} from '../text/Text' 5 5 import {usePalette} from 'lib/hooks/usePalette' 6 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 6 7 import {LoadLatestBtn as LoadLatestBtnMobile} from './LoadLatestBtnMobile' 7 - import {isMobileWeb} from 'platform/detection' 8 8 import {HITSLOP_20} from 'lib/constants' 9 9 10 10 export const LoadLatestBtn = ({ ··· 19 19 minimalShellMode?: boolean 20 20 }) => { 21 21 const pal = usePalette('default') 22 - if (isMobileWeb) { 22 + const {isMobile} = useWebMediaQueries() 23 + if (isMobile) { 23 24 return ( 24 25 <LoadLatestBtnMobile 25 26 onPress={onPress}
+3 -2
src/view/com/util/moderation/ContentHider.tsx
··· 1 1 import React from 'react' 2 2 import {Pressable, StyleProp, StyleSheet, View, ViewStyle} from 'react-native' 3 3 import {usePalette} from 'lib/hooks/usePalette' 4 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 4 5 import {ModerationUI} from '@atproto/api' 5 6 import {Text} from '../text/Text' 6 7 import {ShieldExclamation} from 'lib/icons' 7 8 import {describeModerationCause} from 'lib/moderation' 8 9 import {useStores} from 'state/index' 9 - import {isDesktopWeb} from 'platform/detection' 10 10 11 11 export function ContentHider({ 12 12 testID, ··· 24 24 }>) { 25 25 const store = useStores() 26 26 const pal = usePalette('default') 27 + const {isMobile} = useWebMediaQueries() 27 28 const [override, setOverride] = React.useState(false) 28 29 29 30 if (!moderation.blur || (ignoreMute && moderation.cause?.type === 'muted')) { ··· 54 55 accessibilityLabel="" 55 56 style={[ 56 57 styles.cover, 58 + {paddingRight: isMobile ? 22 : 18}, 57 59 moderation.noOverride 58 60 ? {borderWidth: 1, borderColor: pal.colors.borderDark} 59 61 : pal.viewLight, ··· 96 98 marginTop: 4, 97 99 paddingVertical: 14, 98 100 paddingLeft: 14, 99 - paddingRight: isDesktopWeb ? 18 : 22, 100 101 }, 101 102 showBtn: { 102 103 marginLeft: 'auto',
+7 -3
src/view/com/util/moderation/PostHider.tsx
··· 2 2 import {StyleSheet, Pressable, View} from 'react-native' 3 3 import {ModerationUI} from '@atproto/api' 4 4 import {usePalette} from 'lib/hooks/usePalette' 5 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 5 6 import {Link} from '../Link' 6 7 import {Text} from '../text/Text' 7 8 import {addStyle} from 'lib/styles' 8 9 import {describeModerationCause} from 'lib/moderation' 9 10 import {ShieldExclamation} from 'lib/icons' 10 11 import {useStores} from 'state/index' 11 - import {isDesktopWeb} from 'platform/detection' 12 12 13 13 interface Props extends ComponentProps<typeof Link> { 14 14 // testID?: string ··· 27 27 }: Props) { 28 28 const store = useStores() 29 29 const pal = usePalette('default') 30 + const {isMobile} = useWebMediaQueries() 30 31 const [override, setOverride] = React.useState(false) 31 32 32 33 if (!moderation.blur) { ··· 55 56 accessibilityRole="button" 56 57 accessibilityHint={override ? 'Hide the content' : 'Show the content'} 57 58 accessibilityLabel="" 58 - style={[styles.description, pal.viewLight]}> 59 + style={[ 60 + styles.description, 61 + {paddingRight: isMobile ? 22 : 18}, 62 + pal.viewLight, 63 + ]}> 59 64 <Pressable 60 65 onPress={() => { 61 66 store.shell.openModal({ ··· 100 105 gap: 4, 101 106 paddingVertical: 14, 102 107 paddingLeft: 18, 103 - paddingRight: isDesktopWeb ? 18 : 22, 104 108 marginTop: 1, 105 109 }, 106 110 showBtn: {
+3 -2
src/view/com/util/moderation/ScreenHider.tsx
··· 13 13 import {useNavigation} from '@react-navigation/native' 14 14 import {ModerationUI} from '@atproto/api' 15 15 import {usePalette} from 'lib/hooks/usePalette' 16 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 16 17 import {NavigationProp} from 'lib/routes/types' 17 18 import {Text} from '../text/Text' 18 19 import {Button} from '../forms/Button' 19 - import {isDesktopWeb} from 'platform/detection' 20 20 import {describeModerationCause} from 'lib/moderation' 21 21 import {useStores} from 'state/index' 22 22 ··· 39 39 const palInverted = usePalette('inverted') 40 40 const [override, setOverride] = React.useState(false) 41 41 const navigation = useNavigation<NavigationProp>() 42 + const {isMobile} = useWebMediaQueries() 42 43 43 44 if (!moderation.blur || override) { 44 45 return ( ··· 85 86 </Text> 86 87 </TouchableWithoutFeedback> 87 88 </Text> 88 - {!isDesktopWeb && <View style={styles.spacer} />} 89 + {isMobile && <View style={styles.spacer} />} 89 90 <View style={styles.btnContainer}> 90 91 <Button 91 92 type="inverted"
+33 -30
src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
··· 3 3 import {Text} from '../text/Text' 4 4 import {StyleSheet, View} from 'react-native' 5 5 import {usePalette} from 'lib/hooks/usePalette' 6 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 6 7 import {AppBskyEmbedExternal} from '@atproto/api' 7 - import {isDesktopWeb} from 'platform/detection' 8 8 import {toNiceDomain} from 'lib/strings/url-helpers' 9 9 10 10 export const ExternalLinkEmbed = ({ ··· 15 15 imageChild?: React.ReactNode 16 16 }) => { 17 17 const pal = usePalette('default') 18 + const {isMobile} = useWebMediaQueries() 18 19 return ( 19 - <View style={styles.extContainer}> 20 + <View 21 + style={{ 22 + flexDirection: isMobile ? 'column' : 'row', 23 + }}> 20 24 {link.thumb ? ( 21 - <View style={styles.extImageContainer}> 25 + <View 26 + style={ 27 + !isMobile 28 + ? { 29 + borderTopLeftRadius: 6, 30 + borderBottomLeftRadius: 6, 31 + width: 120, 32 + aspectRatio: 1, 33 + overflow: 'hidden', 34 + } 35 + : { 36 + borderTopLeftRadius: 6, 37 + borderTopRightRadius: 6, 38 + width: '100%', 39 + height: 200, 40 + overflow: 'hidden', 41 + } 42 + }> 22 43 <Image 23 44 style={styles.extImage} 24 45 source={{uri: link.thumb}} ··· 27 48 {imageChild} 28 49 </View> 29 50 ) : undefined} 30 - <View style={styles.extInner}> 51 + <View 52 + style={{ 53 + paddingHorizontal: isMobile ? 10 : 14, 54 + paddingTop: 8, 55 + paddingBottom: 10, 56 + flex: !isMobile ? 1 : undefined, 57 + }}> 31 58 <Text 32 59 type="sm" 33 60 numberOfLines={1} ··· 36 63 </Text> 37 64 <Text 38 65 type="lg-bold" 39 - numberOfLines={isDesktopWeb ? 2 : 4} 66 + numberOfLines={isMobile ? 4 : 2} 40 67 style={[pal.text]}> 41 68 {link.title || link.uri} 42 69 </Text> 43 70 {link.description ? ( 44 71 <Text 45 72 type="md" 46 - numberOfLines={isDesktopWeb ? 2 : 4} 73 + numberOfLines={isMobile ? 4 : 2} 47 74 style={[pal.text, styles.extDescription]}> 48 75 {link.description} 49 76 </Text> ··· 54 81 } 55 82 56 83 const styles = StyleSheet.create({ 57 - extContainer: { 58 - flexDirection: isDesktopWeb ? 'row' : 'column', 59 - }, 60 - extInner: { 61 - paddingHorizontal: isDesktopWeb ? 14 : 10, 62 - paddingTop: 8, 63 - paddingBottom: 10, 64 - flex: isDesktopWeb ? 1 : undefined, 65 - }, 66 - extImageContainer: isDesktopWeb 67 - ? { 68 - borderTopLeftRadius: 6, 69 - borderBottomLeftRadius: 6, 70 - width: 120, 71 - aspectRatio: 1, 72 - overflow: 'hidden', 73 - } 74 - : { 75 - borderTopLeftRadius: 6, 76 - borderTopRightRadius: 6, 77 - width: '100%', 78 - height: 200, 79 - overflow: 'hidden', 80 - }, 81 84 extImage: { 82 85 width: '100%', 83 86 height: 200,
+15 -4
src/view/com/util/post-embeds/index.tsx
··· 22 22 import {ImagesLightbox} from 'state/models/ui/shell' 23 23 import {useStores} from 'state/index' 24 24 import {usePalette} from 'lib/hooks/usePalette' 25 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 25 26 import {YoutubeEmbed} from './YoutubeEmbed' 26 27 import {ExternalLinkEmbed} from './ExternalLinkEmbed' 27 28 import {getYoutubeVideoId} from 'lib/strings/url-helpers' ··· 29 30 import {AutoSizedImage} from '../images/AutoSizedImage' 30 31 import {CustomFeedEmbed} from './CustomFeedEmbed' 31 32 import {ListEmbed} from './ListEmbed' 32 - import {isDesktopWeb} from 'platform/detection' 33 33 import {isCauseALabelOnUri} from 'lib/moderation' 34 34 35 35 type Embed = ··· 50 50 }) { 51 51 const pal = usePalette('default') 52 52 const store = useStores() 53 + const {isMobile} = useWebMediaQueries() 53 54 54 55 // quote post with media 55 56 // = ··· 111 112 uri={thumb} 112 113 onPress={() => openLightbox(0)} 113 114 onPressIn={() => onPressIn(0)} 114 - style={styles.singleImage}> 115 + style={[ 116 + styles.singleImage, 117 + isMobile && styles.singleImageMobile, 118 + ]}> 115 119 {alt === '' ? null : ( 116 120 <View style={styles.altContainer}> 117 121 <Text style={styles.alt} accessible={false}> ··· 130 134 images={embed.images} 131 135 onPress={openLightbox} 132 136 onPressIn={onPressIn} 133 - style={embed.images.length === 1 ? styles.singleImage : undefined} 137 + style={ 138 + embed.images.length === 1 139 + ? [styles.singleImage, isMobile && styles.singleImageMobile] 140 + : undefined 141 + } 134 142 /> 135 143 </View> 136 144 ) ··· 169 177 }, 170 178 singleImage: { 171 179 borderRadius: 8, 172 - maxHeight: isDesktopWeb ? 1000 : 500, 180 + maxHeight: 1000, 181 + }, 182 + singleImageMobile: { 183 + maxHeight: 500, 173 184 }, 174 185 extOuter: { 175 186 borderWidth: 1,
+13 -10
src/view/screens/AppPasswords.tsx
··· 7 7 import * as Toast from '../com/util/Toast' 8 8 import {useStores} from 'state/index' 9 9 import {usePalette} from 'lib/hooks/usePalette' 10 - import {isDesktopWeb} from 'platform/detection' 10 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 11 11 import {withAuthRequired} from 'view/com/auth/withAuthRequired' 12 12 import {observer} from 'mobx-react-lite' 13 13 import {NativeStackScreenProps} from '@react-navigation/native-stack' ··· 23 23 const pal = usePalette('default') 24 24 const store = useStores() 25 25 const {screen} = useAnalytics() 26 + const {isTabletOrDesktop} = useWebMediaQueries() 26 27 27 28 useFocusEffect( 28 29 React.useCallback(() => { ··· 41 42 <CenteredView 42 43 style={[ 43 44 styles.container, 44 - isDesktopWeb && styles.containerDesktop, 45 + isTabletOrDesktop && styles.containerDesktop, 45 46 pal.view, 46 47 pal.border, 47 48 ]} ··· 53 54 pressing the button below. 54 55 </Text> 55 56 </View> 56 - {!isDesktopWeb && <View style={styles.flex1} />} 57 + {!isTabletOrDesktop && <View style={styles.flex1} />} 57 58 <View 58 59 style={[ 59 60 styles.btnContainer, 60 - isDesktopWeb && styles.btnContainerDesktop, 61 + isTabletOrDesktop && styles.btnContainerDesktop, 61 62 ]}> 62 63 <Button 63 64 testID="appPasswordBtn" ··· 77 78 <CenteredView 78 79 style={[ 79 80 styles.container, 80 - isDesktopWeb && styles.containerDesktop, 81 + isTabletOrDesktop && styles.containerDesktop, 81 82 pal.view, 82 83 pal.border, 83 84 ]} ··· 87 88 style={[ 88 89 styles.scrollContainer, 89 90 pal.border, 90 - !isDesktopWeb && styles.flex1, 91 + !isTabletOrDesktop && styles.flex1, 91 92 ]}> 92 93 {store.me.appPasswords.map((password, i) => ( 93 94 <AppPassword ··· 97 98 createdAt={password.createdAt} 98 99 /> 99 100 ))} 100 - {isDesktopWeb && ( 101 + {isTabletOrDesktop && ( 101 102 <View style={[styles.btnContainer, styles.btnContainerDesktop]}> 102 103 <Button 103 104 testID="appPasswordBtn" ··· 110 111 </View> 111 112 )} 112 113 </ScrollView> 113 - {!isDesktopWeb && ( 114 + {!isTabletOrDesktop && ( 114 115 <View style={styles.btnContainer}> 115 116 <Button 116 117 testID="appPasswordBtn" ··· 128 129 ) 129 130 130 131 function AppPasswordsHeader() { 132 + const {isTabletOrDesktop} = useWebMediaQueries() 131 133 const pal = usePalette('default') 132 134 return ( 133 135 <> ··· 137 139 style={[ 138 140 styles.description, 139 141 pal.text, 140 - isDesktopWeb && styles.descriptionDesktop, 142 + isTabletOrDesktop && styles.descriptionDesktop, 141 143 ]}> 142 144 Use app passwords to login to other Bluesky clients without giving full 143 145 access to your account or password. ··· 207 209 const styles = StyleSheet.create({ 208 210 container: { 209 211 flex: 1, 210 - paddingBottom: isDesktopWeb ? 0 : 100, 212 + paddingBottom: 100, 211 213 }, 212 214 containerDesktop: { 213 215 borderLeftWidth: 1, 214 216 borderRightWidth: 1, 217 + paddingBottom: 0, 215 218 }, 216 219 title: { 217 220 textAlign: 'center',
+16 -5
src/view/screens/CustomFeed.tsx
··· 22 22 import {Button} from 'view/com/util/forms/Button' 23 23 import {Text} from 'view/com/util/text/Text' 24 24 import * as Toast from 'view/com/util/Toast' 25 - import {isDesktopWeb} from 'platform/detection' 25 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 26 26 import {useSetTitle} from 'lib/hooks/useSetTitle' 27 27 import {shareUrl} from 'lib/sharing' 28 28 import {toShareUrl} from 'lib/strings/url-helpers' ··· 122 122 ({route, feedOwnerDid}: Props & {feedOwnerDid: string}) => { 123 123 const store = useStores() 124 124 const pal = usePalette('default') 125 + const {isTabletOrDesktop} = useWebMediaQueries() 125 126 const {track} = useAnalytics() 126 127 const {rkey, name: handleOrDid} = route.params 127 128 const uri = useMemo( ··· 357 358 )} 358 359 </Text> 359 360 )} 360 - {isDesktopWeb && ( 361 + {isTabletOrDesktop && ( 361 362 <View style={[styles.headerBtns, styles.headerBtnsDesktop]}> 362 363 <Button 363 364 type={currentFeed?.isSaved ? 'default' : 'inverted'} ··· 452 453 ) : null} 453 454 </View> 454 455 </View> 455 - <View style={[styles.fakeSelector, pal.border]}> 456 + <View 457 + style={[ 458 + styles.fakeSelector, 459 + { 460 + paddingHorizontal: isTabletOrDesktop ? 16 : 6, 461 + }, 462 + pal.border, 463 + ]}> 456 464 <View 457 465 style={[styles.fakeSelectorItem, {borderColor: pal.colors.link}]}> 458 466 <Text type="md-medium" style={[pal.text]}> ··· 474 482 rkey, 475 483 isPinned, 476 484 onTogglePinned, 485 + isTabletOrDesktop, 477 486 ]) 478 487 479 488 const renderEmptyState = React.useCallback(() => { ··· 486 495 487 496 return ( 488 497 <View style={s.hContentRegion}> 489 - <ViewHeader title="" renderButton={currentFeed && renderHeaderBtns} /> 498 + {!isTabletOrDesktop && ( 499 + <ViewHeader title="" renderButton={currentFeed && renderHeaderBtns} /> 500 + )} 490 501 <Feed 491 502 scrollElRef={scrollElRef} 492 503 feed={algoFeed} ··· 495 506 ListHeaderComponent={renderListHeaderComponent} 496 507 renderEmptyState={renderEmptyState} 497 508 extraData={[uri, isPinned]} 509 + style={!isTabletOrDesktop ? {flex: 1} : undefined} 498 510 /> 499 511 {isScrolledDown ? ( 500 512 <LoadLatestBtn ··· 550 562 }, 551 563 fakeSelector: { 552 564 flexDirection: 'row', 553 - paddingHorizontal: isDesktopWeb ? 16 : 6, 554 565 }, 555 566 fakeSelectorItem: { 556 567 paddingHorizontal: 12,
+7 -5
src/view/screens/DiscoverFeeds.tsx
··· 10 10 import {CenteredView, FlatList} from 'view/com/util/Views' 11 11 import {CustomFeed} from 'view/com/feeds/CustomFeed' 12 12 import {Text} from 'view/com/util/text/Text' 13 - import {isDesktopWeb} from 'platform/detection' 14 13 import {usePalette} from 'lib/hooks/usePalette' 14 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 15 15 import {s} from 'lib/styles' 16 16 import {CustomFeedModel} from 'state/models/feeds/custom-feed' 17 17 import {HeaderWithInput} from 'view/com/search/HeaderWithInput' ··· 23 23 const store = useStores() 24 24 const pal = usePalette('default') 25 25 const feeds = React.useMemo(() => new FeedsDiscoveryModel(store), [store]) 26 + const {isTabletOrDesktop} = useWebMediaQueries() 26 27 27 28 // search stuff 28 29 const [isInputFocused, setIsInputFocused] = React.useState<boolean>(false) ··· 74 75 <View style={styles.empty}> 75 76 <Text type="lg" style={pal.textLight}> 76 77 {feeds.isLoading 77 - ? isDesktopWeb 78 + ? isTabletOrDesktop 78 79 ? 'Loading...' 79 80 : '' 80 81 : query ··· 100 101 101 102 return ( 102 103 <CenteredView style={[styles.container, pal.view]}> 103 - <View style={[isDesktopWeb && styles.containerDesktop, pal.border]}> 104 + <View 105 + style={[isTabletOrDesktop && styles.containerDesktop, pal.border]}> 104 106 <ViewHeader title="Discover Feeds" showOnDesktop /> 105 - <View style={{marginTop: isDesktopWeb ? 5 : 0, marginBottom: 4}}> 107 + <View style={{marginTop: isTabletOrDesktop ? 5 : 0, marginBottom: 4}}> 106 108 <HeaderWithInput 107 109 isInputFocused={isInputFocused} 108 110 query={query} ··· 116 118 </View> 117 119 </View> 118 120 <FlatList 119 - style={[!isDesktopWeb && s.flex1]} 121 + style={[!isTabletOrDesktop && s.flex1]} 120 122 data={feeds.feeds} 121 123 keyExtractor={item => item.data.uri} 122 124 contentContainerStyle={styles.contentContainer}
+12 -9
src/view/screens/Feeds.tsx
··· 12 12 import {observer} from 'mobx-react-lite' 13 13 import {PostsMultiFeedModel} from 'state/models/feeds/multi-feed' 14 14 import {MultiFeed} from 'view/com/posts/MultiFeed' 15 - import {isDesktopWeb} from 'platform/detection' 16 15 import {usePalette} from 'lib/hooks/usePalette' 17 16 import {useTimer} from 'lib/hooks/useTimer' 18 17 import {useStores} from 'state/index' 18 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 19 19 import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' 20 20 import {ComposeIcon2, CogIcon} from 'lib/icons' 21 21 import {s} from 'lib/styles' 22 22 23 23 const LOAD_NEW_PROMPT_TIME = 60e3 // 60 seconds 24 - const HEADER_OFFSET = isDesktopWeb ? 0 : 40 24 + const MOBILE_HEADER_OFFSET = 40 25 25 26 26 type Props = NativeStackScreenProps<FeedsTabNavigatorParams, 'Feeds'> 27 27 export const FeedsScreen = withAuthRequired( 28 28 observer<Props>(({}: Props) => { 29 29 const pal = usePalette('default') 30 30 const store = useStores() 31 + const {isMobile} = useWebMediaQueries() 31 32 const flatListRef = React.useRef<FlatList>(null) 32 33 const multifeed = React.useMemo<PostsMultiFeedModel>( 33 34 () => new PostsMultiFeedModel(store), ··· 105 106 multifeed={multifeed} 106 107 onScroll={onMainScroll} 107 108 scrollEventThrottle={100} 108 - headerOffset={HEADER_OFFSET} 109 - /> 110 - <ViewHeader 111 - title="My Feeds" 112 - canGoBack={false} 113 - hideOnScroll 114 - renderButton={renderHeaderBtn} 109 + headerOffset={isMobile ? MOBILE_HEADER_OFFSET : undefined} 115 110 /> 111 + {isMobile && ( 112 + <ViewHeader 113 + title="My Feeds" 114 + canGoBack={false} 115 + hideOnScroll 116 + renderButton={renderHeaderBtn} 117 + /> 118 + )} 116 119 {isScrolledDown || loadPromptVisible ? ( 117 120 <LoadLatestBtn 118 121 onPress={onSoftReset}
+8 -22
src/view/screens/Home.tsx
··· 19 19 import {s} from 'lib/styles' 20 20 import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' 21 21 import {useAnalytics} from 'lib/analytics/analytics' 22 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 22 23 import {ComposeIcon2} from 'lib/icons' 23 - import {isDesktopWeb, isMobileWebMediaQuery, isWeb} from 'platform/detection' 24 24 25 25 const HEADER_OFFSET_MOBILE = 78 26 26 const HEADER_OFFSET_DESKTOP = 50 27 - const HEADER_OFFSET = isDesktopWeb 28 - ? HEADER_OFFSET_DESKTOP 29 - : HEADER_OFFSET_MOBILE 30 27 const POLL_FREQ = 30e3 // 30sec 31 28 32 29 type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'> ··· 158 155 renderEmptyState?: () => JSX.Element 159 156 }) => { 160 157 const store = useStores() 158 + const {isMobile} = useWebMediaQueries() 161 159 const [onMainScroll, isScrolledDown, resetMainScroll] = 162 160 useOnMainScroll(store) 163 161 const {screen, track} = useAnalytics() 164 - const [headerOffset, setHeaderOffset] = React.useState(HEADER_OFFSET) 162 + const [headerOffset, setHeaderOffset] = React.useState( 163 + isMobile ? HEADER_OFFSET_MOBILE : HEADER_OFFSET_DESKTOP, 164 + ) 165 165 const scrollElRef = React.useRef<FlatList>(null) 166 166 const {appState} = useAppState({ 167 167 onForeground: () => doPoll(true), ··· 206 206 }, [isPageFocused, scrollToTop, feed]) 207 207 208 208 // listens for resize events 209 - const listenForResize = React.useCallback(() => { 210 - // @ts-ignore we know window exists -prf 211 - const isMobileWeb = global.window.matchMedia( 212 - isMobileWebMediaQuery, 213 - )?.matches 214 - setHeaderOffset( 215 - isMobileWeb ? HEADER_OFFSET_MOBILE : HEADER_OFFSET_DESKTOP, 216 - ) 217 - }, []) 209 + React.useEffect(() => { 210 + setHeaderOffset(isMobile ? HEADER_OFFSET_MOBILE : HEADER_OFFSET_DESKTOP) 211 + }, [isMobile]) 218 212 219 213 // fires when page within screen is activated/deactivated 220 214 // - check for latest ··· 234 228 feed.update() 235 229 } 236 230 237 - if (isWeb) { 238 - window.addEventListener('resize', listenForResize) 239 - } 240 - 241 231 return () => { 242 232 clearInterval(pollInterval) 243 233 softResetSub.remove() 244 234 feedCleanup() 245 - if (isWeb) { 246 - isWeb && window.removeEventListener('resize', listenForResize) 247 - } 248 235 } 249 236 }, [ 250 237 store, ··· 254 241 feed, 255 242 isPageFocused, 256 243 isScreenFocused, 257 - listenForResize, 258 244 ]) 259 245 260 246 const onPressCompose = React.useCallback(() => {
+3 -2
src/view/screens/Moderation.tsx
··· 16 16 import {Text} from '../com/util/text/Text' 17 17 import {usePalette} from 'lib/hooks/usePalette' 18 18 import {useAnalytics} from 'lib/analytics/analytics' 19 - import {isDesktopWeb} from 'platform/detection' 19 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 20 20 21 21 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Moderation'> 22 22 export const ModerationScreen = withAuthRequired( ··· 24 24 const pal = usePalette('default') 25 25 const store = useStores() 26 26 const {screen, track} = useAnalytics() 27 + const {isTabletOrDesktop} = useWebMediaQueries() 27 28 28 29 useFocusEffect( 29 30 React.useCallback(() => { ··· 42 43 style={[ 43 44 s.hContentRegion, 44 45 pal.border, 45 - isDesktopWeb ? styles.desktopContainer : pal.viewLight, 46 + isTabletOrDesktop ? styles.desktopContainer : pal.viewLight, 46 47 ]} 47 48 testID="moderationScreen"> 48 49 <ViewHeader title="Moderation" showOnDesktop />
+8 -6
src/view/screens/ModerationBlockedAccounts.tsx
··· 10 10 import {Text} from '../com/util/text/Text' 11 11 import {useStores} from 'state/index' 12 12 import {usePalette} from 'lib/hooks/usePalette' 13 - import {isDesktopWeb} from 'platform/detection' 13 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 14 14 import {withAuthRequired} from 'view/com/auth/withAuthRequired' 15 15 import {observer} from 'mobx-react-lite' 16 16 import {NativeStackScreenProps} from '@react-navigation/native-stack' ··· 30 30 observer(({}: Props) => { 31 31 const pal = usePalette('default') 32 32 const store = useStores() 33 + const {isTabletOrDesktop} = useWebMediaQueries() 33 34 const {screen} = useAnalytics() 34 35 const blockedAccounts = useMemo( 35 36 () => new BlockedAccountsModel(store), ··· 72 73 <CenteredView 73 74 style={[ 74 75 styles.container, 75 - isDesktopWeb && styles.containerDesktop, 76 + isTabletOrDesktop && styles.containerDesktop, 76 77 pal.view, 77 78 pal.border, 78 79 ]} ··· 83 84 style={[ 84 85 styles.description, 85 86 pal.text, 86 - isDesktopWeb && styles.descriptionDesktop, 87 + isTabletOrDesktop && styles.descriptionDesktop, 87 88 ]}> 88 89 Blocked accounts cannot reply in your threads, mention you, or 89 90 otherwise interact with you. You will not see their content and they 90 91 will be prevented from seeing yours. 91 92 </Text> 92 93 {!blockedAccounts.hasContent ? ( 93 - <View style={[pal.border, !isDesktopWeb && styles.flex1]}> 94 + <View style={[pal.border, !isTabletOrDesktop && styles.flex1]}> 94 95 <View style={[styles.empty, pal.viewLight]}> 95 96 <Text type="lg" style={[pal.text, styles.emptyText]}> 96 97 You have not blocked any accounts yet. To block an account, go ··· 101 102 </View> 102 103 ) : ( 103 104 <FlatList 104 - style={[!isDesktopWeb && styles.flex1]} 105 + style={[!isTabletOrDesktop && styles.flex1]} 105 106 data={blockedAccounts.blocks} 106 107 keyExtractor={(item: ActorDefs.ProfileView) => item.did} 107 108 refreshControl={ ··· 133 134 const styles = StyleSheet.create({ 134 135 container: { 135 136 flex: 1, 136 - paddingBottom: isDesktopWeb ? 0 : 100, 137 + paddingBottom: 100, 137 138 }, 138 139 containerDesktop: { 139 140 borderLeftWidth: 1, 140 141 borderRightWidth: 1, 142 + paddingBottom: 0, 141 143 }, 142 144 title: { 143 145 textAlign: 'center',
+6 -4
src/view/screens/ModerationMuteLists.tsx
··· 15 15 import {Button} from 'view/com/util/forms/Button' 16 16 import {NavigationProp} from 'lib/routes/types' 17 17 import {usePalette} from 'lib/hooks/usePalette' 18 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 18 19 import {CenteredView} from 'view/com/util/Views' 19 20 import {ViewHeader} from 'view/com/util/ViewHeader' 20 - import {isDesktopWeb} from 'platform/detection' 21 21 22 22 type Props = NativeStackScreenProps< 23 23 CommonNavigatorParams, ··· 26 26 export const ModerationMuteListsScreen = withAuthRequired(({}: Props) => { 27 27 const pal = usePalette('default') 28 28 const store = useStores() 29 + const {isTabletOrDesktop} = useWebMediaQueries() 29 30 const navigation = useNavigation<NavigationProp>() 30 31 31 32 const mutelists: ListsListModel = React.useMemo( ··· 89 90 styles.container, 90 91 pal.view, 91 92 pal.border, 92 - isDesktopWeb && styles.containerDesktop, 93 + isTabletOrDesktop && styles.containerDesktop, 93 94 ]} 94 95 testID="moderationMutelistsScreen"> 95 96 <ViewHeader ··· 99 100 /> 100 101 <ListsList 101 102 listsList={mutelists} 102 - showAddBtns={isDesktopWeb} 103 + showAddBtns={isTabletOrDesktop} 103 104 renderEmptyState={renderEmptyState} 104 105 onPressCreateNew={onPressNewMuteList} 105 106 /> ··· 110 111 const styles = StyleSheet.create({ 111 112 container: { 112 113 flex: 1, 113 - paddingBottom: isDesktopWeb ? 0 : 100, 114 + paddingBottom: 100, 114 115 }, 115 116 containerDesktop: { 116 117 borderLeftWidth: 1, 117 118 borderRightWidth: 1, 119 + paddingBottom: 0, 118 120 }, 119 121 createBtn: { 120 122 width: 40,
+8 -6
src/view/screens/ModerationMutedAccounts.tsx
··· 10 10 import {Text} from '../com/util/text/Text' 11 11 import {useStores} from 'state/index' 12 12 import {usePalette} from 'lib/hooks/usePalette' 13 - import {isDesktopWeb} from 'platform/detection' 13 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 14 14 import {withAuthRequired} from 'view/com/auth/withAuthRequired' 15 15 import {observer} from 'mobx-react-lite' 16 16 import {NativeStackScreenProps} from '@react-navigation/native-stack' ··· 30 30 observer(({}: Props) => { 31 31 const pal = usePalette('default') 32 32 const store = useStores() 33 + const {isTabletOrDesktop} = useWebMediaQueries() 33 34 const {screen} = useAnalytics() 34 35 const mutedAccounts = useMemo(() => new MutedAccountsModel(store), [store]) 35 36 ··· 69 70 <CenteredView 70 71 style={[ 71 72 styles.container, 72 - isDesktopWeb && styles.containerDesktop, 73 + isTabletOrDesktop && styles.containerDesktop, 73 74 pal.view, 74 75 pal.border, 75 76 ]} ··· 80 81 style={[ 81 82 styles.description, 82 83 pal.text, 83 - isDesktopWeb && styles.descriptionDesktop, 84 + isTabletOrDesktop && styles.descriptionDesktop, 84 85 ]}> 85 86 Muted accounts have their posts removed from your feed and from your 86 87 notifications. Mutes are completely private. 87 88 </Text> 88 89 {!mutedAccounts.hasContent ? ( 89 - <View style={[pal.border, !isDesktopWeb && styles.flex1]}> 90 + <View style={[pal.border, !isTabletOrDesktop && styles.flex1]}> 90 91 <View style={[styles.empty, pal.viewLight]}> 91 92 <Text type="lg" style={[pal.text, styles.emptyText]}> 92 93 You have not muted any accounts yet. To mute an account, go to ··· 97 98 </View> 98 99 ) : ( 99 100 <FlatList 100 - style={[!isDesktopWeb && styles.flex1]} 101 + style={[!isTabletOrDesktop && styles.flex1]} 101 102 data={mutedAccounts.mutes} 102 103 keyExtractor={item => item.did} 103 104 refreshControl={ ··· 129 130 const styles = StyleSheet.create({ 130 131 container: { 131 132 flex: 1, 132 - paddingBottom: isDesktopWeb ? 0 : 100, 133 + paddingBottom: 100, 133 134 }, 134 135 containerDesktop: { 135 136 borderLeftWidth: 1, 136 137 borderRightWidth: 1, 138 + paddingBottom: 0, 137 139 }, 138 140 title: { 139 141 textAlign: 'center',
+3 -2
src/view/screens/PostThread.tsx
··· 12 12 import {s} from 'lib/styles' 13 13 import {useSafeAreaInsets} from 'react-native-safe-area-context' 14 14 import {clamp} from 'lodash' 15 - import {isDesktopWeb} from 'platform/detection' 15 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 16 16 17 17 const SHELL_FOOTER_HEIGHT = 44 18 18 ··· 26 26 () => new PostThreadModel(store, {uri}), 27 27 [store, uri], 28 28 ) 29 + const {isMobile} = useWebMediaQueries() 29 30 30 31 useFocusEffect( 31 32 React.useCallback(() => { ··· 75 76 onPressReply={onPressReply} 76 77 /> 77 78 </View> 78 - {!isDesktopWeb && ( 79 + {isMobile && ( 79 80 <View 80 81 style={[ 81 82 styles.prompt,
+15 -8
src/view/screens/PreferencesHomeFeed.tsx
··· 6 6 import {useStores} from 'state/index' 7 7 import {s, colors} from 'lib/styles' 8 8 import {usePalette} from 'lib/hooks/usePalette' 9 - import {isWeb, isDesktopWeb} from 'platform/detection' 9 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 10 + import {isWeb} from 'platform/detection' 10 11 import {ToggleButton} from 'view/com/util/forms/ToggleButton' 11 12 import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' 12 13 import {ViewHeader} from 'view/com/util/ViewHeader' ··· 50 51 export const PreferencesHomeFeed = observer(({navigation}: Props) => { 51 52 const pal = usePalette('default') 52 53 const store = useStores() 54 + const {isTabletOrDesktop} = useWebMediaQueries() 53 55 54 56 return ( 55 57 <CenteredView ··· 58 60 pal.view, 59 61 pal.border, 60 62 styles.container, 61 - isDesktopWeb && styles.desktopContainer, 63 + isTabletOrDesktop && styles.desktopContainer, 62 64 ]}> 63 65 <ViewHeader title="Home Feed Preferences" showOnDesktop /> 64 - <View style={styles.titleSection}> 66 + <View 67 + style={[styles.titleSection, isTabletOrDesktop && {paddingTop: 20}]}> 65 68 <Text type="xl" style={[pal.textLight, styles.description]}> 66 69 Fine-tune the content you see on your home screen. 67 70 </Text> ··· 122 125 </View> 123 126 </ScrollView> 124 127 125 - <View style={[styles.btnContainer, pal.borderDark]}> 128 + <View 129 + style={[ 130 + styles.btnContainer, 131 + !isTabletOrDesktop && {borderTopWidth: 1, paddingHorizontal: 20}, 132 + pal.borderDark, 133 + ]}> 126 134 <TouchableOpacity 127 135 testID="confirmBtn" 128 136 onPress={() => { ··· 130 138 ? navigation.goBack() 131 139 : navigation.navigate('Settings') 132 140 }} 133 - style={[styles.btn, isDesktopWeb && styles.btnDesktop]} 141 + style={[styles.btn, isTabletOrDesktop && styles.btnDesktop]} 134 142 accessibilityRole="button" 135 143 accessibilityLabel="Confirm" 136 144 accessibilityHint=""> ··· 144 152 const styles = StyleSheet.create({ 145 153 container: { 146 154 flex: 1, 147 - paddingBottom: isDesktopWeb ? 40 : 90, 155 + paddingBottom: 90, 148 156 }, 149 157 desktopContainer: { 150 158 borderLeftWidth: 1, 151 159 borderRightWidth: 1, 160 + paddingBottom: 40, 152 161 }, 153 162 titleSection: { 154 163 paddingBottom: 30, 155 - paddingTop: isDesktopWeb ? 20 : 0, 156 164 }, 157 165 title: { 158 166 textAlign: 'center', ··· 184 192 }, 185 193 btnContainer: { 186 194 paddingTop: 20, 187 - borderTopWidth: isDesktopWeb ? 0 : 1, 188 195 }, 189 196 dimmed: { 190 197 opacity: 0.3,
+5 -3
src/view/screens/ProfileList.tsx
··· 14 14 import {useStores} from 'state/index' 15 15 import {usePalette} from 'lib/hooks/usePalette' 16 16 import {useSetTitle} from 'lib/hooks/useSetTitle' 17 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 17 18 import {NavigationProp} from 'lib/routes/types' 18 - import {isDesktopWeb} from 'platform/detection' 19 19 import {toShareUrl} from 'lib/strings/url-helpers' 20 20 import {shareUrl} from 'lib/sharing' 21 21 import {ListActions} from 'view/com/lists/ListActions' ··· 26 26 observer(({route}: Props) => { 27 27 const store = useStores() 28 28 const navigation = useNavigation<NavigationProp>() 29 + const {isTabletOrDesktop} = useWebMediaQueries() 29 30 const pal = usePalette('default') 30 31 const {name, rkey} = route.params 31 32 ··· 131 132 <CenteredView 132 133 style={[ 133 134 styles.container, 134 - isDesktopWeb && styles.containerDesktop, 135 + isTabletOrDesktop && styles.containerDesktop, 135 136 pal.view, 136 137 pal.border, 137 138 ]} ··· 155 156 const styles = StyleSheet.create({ 156 157 container: { 157 158 flex: 1, 158 - paddingBottom: isDesktopWeb ? 0 : 100, 159 + paddingBottom: 100, 159 160 }, 160 161 containerDesktop: { 161 162 borderLeftWidth: 1, 162 163 borderRightWidth: 1, 164 + paddingBottom: 0, 163 165 }, 164 166 })
+8 -10
src/view/screens/SavedFeeds.tsx
··· 14 14 import {CommonNavigatorParams} from 'lib/routes/types' 15 15 import {observer} from 'mobx-react-lite' 16 16 import {useStores} from 'state/index' 17 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 17 18 import {withAuthRequired} from 'view/com/auth/withAuthRequired' 18 19 import {ViewHeader} from 'view/com/util/ViewHeader' 19 20 import {CenteredView} from 'view/com/util/Views' 20 21 import {Text} from 'view/com/util/text/Text' 21 - import {isDesktopWeb, isWeb} from 'platform/detection' 22 + import {isWeb} from 'platform/detection' 22 23 import {s, colors} from 'lib/styles' 23 24 import DraggableFlatList, { 24 25 ShadowDecorator, ··· 37 38 observer(({}: Props) => { 38 39 const pal = usePalette('default') 39 40 const store = useStores() 41 + const {isMobile, isTabletOrDesktop} = useWebMediaQueries() 40 42 const {screen} = useAnalytics() 41 43 42 44 const savedFeeds = useMemo(() => store.me.savedFeeds, [store]) ··· 53 55 <View 54 56 style={[ 55 57 pal.border, 56 - !isDesktopWeb && s.flex1, 58 + isMobile && s.flex1, 57 59 pal.viewLight, 58 60 styles.empty, 59 61 ]}> ··· 62 64 </Text> 63 65 </View> 64 66 ) 65 - }, [pal]) 67 + }, [pal, isMobile]) 66 68 67 69 const renderListFooterComponent = useCallback(() => { 68 70 return ( ··· 116 118 style={[ 117 119 s.hContentRegion, 118 120 pal.border, 119 - isDesktopWeb && styles.desktopContainer, 121 + isTabletOrDesktop && styles.desktopContainer, 120 122 ]}> 121 - <ViewHeader 122 - title="Edit My Feeds" 123 - showOnDesktop 124 - showBorder={!isDesktopWeb} 125 - /> 123 + <ViewHeader title="Edit My Feeds" showOnDesktop showBorder /> 126 124 <DraggableFlatList 127 - containerStyle={[isDesktopWeb ? s.hContentRegion : s.flex1]} 125 + containerStyle={[isTabletOrDesktop ? s.hContentRegion : s.flex1]} 128 126 data={savedFeeds.all} 129 127 keyExtractor={item => item.data.uri} 130 128 refreshing={savedFeeds.isRefreshing}
+3 -2
src/view/screens/Search.web.tsx
··· 12 12 SearchTabNavigatorParams, 13 13 } from 'lib/routes/types' 14 14 import {useStores} from 'state/index' 15 + import {CenteredView} from 'view/com/util/Views' 15 16 import * as Mobile from './SearchMobile' 16 17 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 17 18 ··· 57 58 58 59 if (!isDesktop) { 59 60 return ( 60 - <View style={styles.scrollContainer}> 61 + <CenteredView style={styles.scrollContainer}> 61 62 <Mobile.SearchScreen navigation={navigation} route={route} /> 62 - </View> 63 + </CenteredView> 63 64 ) 64 65 } 65 66
+4 -3
src/view/screens/Settings.tsx
··· 35 35 import {SelectableBtn} from 'view/com/util/forms/SelectableBtn' 36 36 import {usePalette} from 'lib/hooks/usePalette' 37 37 import {useCustomPalette} from 'lib/hooks/useCustomPalette' 38 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 38 39 import {AccountData} from 'state/models/session' 39 40 import {useAnalytics} from 'lib/analytics/analytics' 40 41 import {NavigationProp} from 'lib/routes/types' 41 - import {isDesktopWeb} from 'platform/detection' 42 42 import {pluralize} from 'lib/strings/helpers' 43 43 import {formatCount} from 'view/com/util/numeric/format' 44 44 import Clipboard from '@react-native-clipboard/clipboard' ··· 58 58 const pal = usePalette('default') 59 59 const store = useStores() 60 60 const navigation = useNavigation<NavigationProp>() 61 + const {isMobile} = useWebMediaQueries() 61 62 const {screen, track} = useAnalytics() 62 63 const [isSwitching, setIsSwitching] = React.useState(false) 63 64 const [debugHeaderEnabled, toggleDebugHeader] = useDebugHeaderSetting( ··· 203 204 <ViewHeader title="Settings" /> 204 205 <ScrollView 205 206 style={[s.hContentRegion]} 206 - contentContainerStyle={!isDesktopWeb && pal.viewLight} 207 + contentContainerStyle={isMobile && pal.viewLight} 207 208 scrollIndicatorInsets={{right: 1}}> 208 209 <View style={styles.spacer20} /> 209 210 {store.session.currentSession !== undefined ? ( ··· 508 509 System log 509 510 </Text> 510 511 </TouchableOpacity> 511 - {isDesktopWeb || __DEV__ ? ( 512 + {__DEV__ ? ( 512 513 <ToggleButton 513 514 type="default-light" 514 515 label="Experiment: Use AppView Proxy"
+17 -7
src/view/shell/Composer.web.tsx
··· 4 4 import {ComposePost} from '../com/composer/Composer' 5 5 import {ComposerOpts} from 'state/models/ui/shell' 6 6 import {usePalette} from 'lib/hooks/usePalette' 7 - import {isMobileWeb} from 'platform/detection' 7 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 8 8 9 9 const BOTTOM_BAR_HEIGHT = 61 10 10 ··· 26 26 mention?: ComposerOpts['mention'] 27 27 }) => { 28 28 const pal = usePalette('default') 29 + const {isMobile} = useWebMediaQueries() 29 30 30 31 // rendering 31 32 // = ··· 36 37 37 38 return ( 38 39 <View style={styles.mask} aria-modal accessibilityViewIsModal> 39 - <View style={[styles.container, pal.view, pal.border]}> 40 + <View 41 + style={[ 42 + styles.container, 43 + isMobile && styles.containerMobile, 44 + pal.view, 45 + pal.border, 46 + ]}> 40 47 <ComposePost 41 48 replyTo={replyTo} 42 49 quote={quote} ··· 66 73 width: '100%', 67 74 paddingVertical: 0, 68 75 paddingHorizontal: 2, 69 - borderRadius: isMobileWeb ? 0 : 8, 70 - marginBottom: isMobileWeb ? BOTTOM_BAR_HEIGHT : 0, 76 + borderRadius: 8, 77 + marginBottom: 0, 71 78 borderWidth: 1, 72 - maxHeight: isMobileWeb 73 - ? `calc(100% - ${BOTTOM_BAR_HEIGHT}px)` 74 - : 'calc(100% - (40px * 2))', 79 + maxHeight: 'calc(100% - (40px * 2))', 80 + }, 81 + containerMobile: { 82 + borderRadius: 0, 83 + marginBottom: BOTTOM_BAR_HEIGHT, 84 + maxHeight: `calc(100% - ${BOTTOM_BAR_HEIGHT}px)`, 75 85 }, 76 86 })
+119 -28
src/view/shell/desktop/LeftNav.tsx
··· 17 17 import {LoadingPlaceholder} from 'view/com/util/LoadingPlaceholder' 18 18 import {usePalette} from 'lib/hooks/usePalette' 19 19 import {useStores} from 'state/index' 20 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 20 21 import {s, colors} from 'lib/styles' 21 22 import { 22 23 HomeIcon, ··· 41 42 42 43 const ProfileCard = observer(() => { 43 44 const store = useStores() 45 + const {isDesktop} = useWebMediaQueries() 46 + const size = isDesktop ? 64 : 48 44 47 return store.me.handle ? ( 45 - <Link href={makeProfileLink(store.me)} style={styles.profileCard} asAnchor> 46 - <UserAvatar avatar={store.me.avatar} size={64} /> 48 + <Link 49 + href={makeProfileLink(store.me)} 50 + style={[styles.profileCard, !isDesktop && styles.profileCardTablet]} 51 + asAnchor> 52 + <UserAvatar avatar={store.me.avatar} size={size} /> 47 53 </Link> 48 54 ) : ( 49 - <View style={styles.profileCard}> 50 - <LoadingPlaceholder width={64} height={64} style={{borderRadius: 64}} /> 55 + <View style={[styles.profileCard, !isDesktop && styles.profileCardTablet]}> 56 + <LoadingPlaceholder 57 + width={size} 58 + height={size} 59 + style={{borderRadius: size}} 60 + /> 51 61 </View> 52 62 ) 53 63 }) 54 64 55 65 function BackBtn() { 66 + const {isTablet} = useWebMediaQueries() 56 67 const pal = usePalette('default') 57 68 const navigation = useNavigation<NavigationProp>() 58 69 const shouldShow = useNavigationState(state => !isStateAtTabRoot(state)) ··· 65 76 } 66 77 }, [navigation]) 67 78 68 - if (!shouldShow) { 79 + if (!shouldShow || isTablet) { 69 80 return <></> 70 81 } 71 82 return ( ··· 96 107 ({count, href, icon, iconFilled, label}: NavItemProps) => { 97 108 const pal = usePalette('default') 98 109 const store = useStores() 110 + const {isDesktop, isTablet} = useWebMediaQueries() 99 111 const [pathName] = React.useMemo(() => router.matchPath(href), [href]) 100 112 const currentRouteInfo = useNavigationState(state => { 101 113 if (!state) { ··· 137 149 accessibilityRole="tab" 138 150 accessibilityLabel={label} 139 151 accessibilityHint=""> 140 - <View style={[styles.navItemIconWrapper]}> 152 + <View 153 + style={[ 154 + styles.navItemIconWrapper, 155 + isTablet && styles.navItemIconWrapperTablet, 156 + ]}> 141 157 {isCurrent ? iconFilled : icon} 142 158 {typeof count === 'string' && count ? ( 143 - <Text type="button" style={styles.navItemCount}> 159 + <Text 160 + type="button" 161 + style={[ 162 + styles.navItemCount, 163 + isTablet && styles.navItemCountTablet, 164 + ]}> 144 165 {count} 145 166 </Text> 146 167 ) : null} 147 168 </View> 148 - <Text type="title" style={[isCurrent ? s.bold : s.normal, pal.text]}> 149 - {label} 150 - </Text> 169 + {isDesktop && ( 170 + <Text type="title" style={[isCurrent ? s.bold : s.normal, pal.text]}> 171 + {label} 172 + </Text> 173 + )} 151 174 </PressableWithHover> 152 175 ) 153 176 }, ··· 156 179 function ComposeBtn() { 157 180 const store = useStores() 158 181 const {getState} = useNavigation() 182 + const {isTablet} = useWebMediaQueries() 159 183 160 184 const getProfileHandle = () => { 161 185 const {routes} = getState() ··· 172 196 const onPressCompose = () => 173 197 store.shell.openComposer({mention: getProfileHandle()}) 174 198 199 + if (isTablet) { 200 + return null 201 + } 175 202 return ( 176 203 <TouchableOpacity 177 204 style={[styles.newPostBtn]} ··· 196 223 export const DesktopLeftNav = observer(function DesktopLeftNav() { 197 224 const store = useStores() 198 225 const pal = usePalette('default') 226 + const {isDesktop, isTablet} = useWebMediaQueries() 199 227 200 228 return ( 201 - <View style={[styles.leftNav, pal.view]}> 229 + <View 230 + style={[ 231 + styles.leftNav, 232 + isTablet && styles.leftNavTablet, 233 + pal.view, 234 + pal.border, 235 + ]}> 202 236 {store.session.hasSession && <ProfileCard />} 203 237 <BackBtn /> 204 238 <NavItem 205 239 href="/" 206 - icon={<HomeIcon size={24} style={pal.text} />} 240 + icon={<HomeIcon size={isDesktop ? 24 : 28} style={pal.text} />} 207 241 iconFilled={ 208 - <HomeIconSolid strokeWidth={4} size={24} style={pal.text} /> 242 + <HomeIconSolid 243 + strokeWidth={4} 244 + size={isDesktop ? 24 : 28} 245 + style={pal.text} 246 + /> 209 247 } 210 248 label="Home" 211 249 /> 212 250 <NavItem 213 251 href="/search" 214 252 icon={ 215 - <MagnifyingGlassIcon2 strokeWidth={2} size={24} style={pal.text} /> 253 + <MagnifyingGlassIcon2 254 + strokeWidth={2} 255 + size={isDesktop ? 24 : 26} 256 + style={pal.text} 257 + /> 216 258 } 217 259 iconFilled={ 218 260 <MagnifyingGlassIcon2Solid 219 261 strokeWidth={2} 220 - size={24} 262 + size={isDesktop ? 24 : 26} 221 263 style={pal.text} 222 264 /> 223 265 } ··· 229 271 <SatelliteDishIcon 230 272 strokeWidth={1.75} 231 273 style={pal.text as FontAwesomeIconStyle} 232 - size={24} 274 + size={isDesktop ? 24 : 28} 233 275 /> 234 276 } 235 277 iconFilled={ 236 278 <SatelliteDishIconSolid 237 279 strokeWidth={1.75} 238 280 style={pal.text as FontAwesomeIconStyle} 239 - size={24} 281 + size={isDesktop ? 24 : 28} 240 282 /> 241 283 } 242 284 label="My Feeds" ··· 244 286 <NavItem 245 287 href="/notifications" 246 288 count={store.me.notifications.unreadCountLabel} 247 - icon={<BellIcon strokeWidth={2} size={24} style={pal.text} />} 289 + icon={ 290 + <BellIcon 291 + strokeWidth={2} 292 + size={isDesktop ? 24 : 26} 293 + style={pal.text} 294 + /> 295 + } 248 296 iconFilled={ 249 - <BellIconSolid strokeWidth={1.5} size={24} style={pal.text} /> 297 + <BellIconSolid 298 + strokeWidth={1.5} 299 + size={isDesktop ? 24 : 26} 300 + style={pal.text} 301 + /> 250 302 } 251 303 label="Notifications" 252 304 /> ··· 256 308 <HandIcon 257 309 strokeWidth={5.5} 258 310 style={pal.text as FontAwesomeIconStyle} 259 - size={24} 311 + size={isDesktop ? 24 : 27} 260 312 /> 261 313 } 262 314 iconFilled={ 263 315 <FontAwesomeIcon 264 316 icon="hand" 265 317 style={pal.text as FontAwesomeIconStyle} 266 - size={20} 318 + size={isDesktop ? 20 : 26} 267 319 /> 268 320 } 269 321 label="Moderation" ··· 271 323 {store.session.hasSession && ( 272 324 <NavItem 273 325 href={makeProfileLink(store.me)} 274 - icon={<UserIcon strokeWidth={1.75} size={28} style={pal.text} />} 326 + icon={ 327 + <UserIcon 328 + strokeWidth={1.75} 329 + size={isDesktop ? 28 : 30} 330 + style={pal.text} 331 + /> 332 + } 275 333 iconFilled={ 276 - <UserIconSolid strokeWidth={1.75} size={28} style={pal.text} /> 334 + <UserIconSolid 335 + strokeWidth={1.75} 336 + size={isDesktop ? 28 : 30} 337 + style={pal.text} 338 + /> 277 339 } 278 340 label="Profile" 279 341 /> 280 342 )} 281 343 <NavItem 282 344 href="/settings" 283 - icon={<CogIcon strokeWidth={1.75} size={28} style={pal.text} />} 345 + icon={ 346 + <CogIcon 347 + strokeWidth={1.75} 348 + size={isDesktop ? 28 : 32} 349 + style={pal.text} 350 + /> 351 + } 284 352 iconFilled={ 285 - <CogIconSolid strokeWidth={1.5} size={28} style={pal.text} /> 353 + <CogIconSolid 354 + strokeWidth={1.5} 355 + size={isDesktop ? 28 : 32} 356 + style={pal.text} 357 + /> 286 358 } 287 359 label="Settings" 288 360 /> ··· 300 372 maxHeight: 'calc(100vh - 10px)', 301 373 overflowY: 'auto', 302 374 }, 375 + leftNavTablet: { 376 + top: 0, 377 + left: 0, 378 + right: 'auto', 379 + borderRightWidth: 1, 380 + height: '100%', 381 + width: 76, 382 + alignItems: 'center', 383 + }, 303 384 304 385 profileCard: { 305 386 marginVertical: 10, 306 387 width: 90, 307 388 paddingLeft: 12, 389 + }, 390 + profileCardTablet: { 391 + width: 70, 308 392 }, 309 393 310 394 backBtn: { ··· 329 413 width: 28, 330 414 height: 28, 331 415 marginTop: 2, 416 + }, 417 + navItemIconWrapperTablet: { 418 + width: 40, 419 + height: 40, 332 420 }, 333 421 navItemCount: { 334 422 position: 'absolute', ··· 341 429 paddingHorizontal: 4, 342 430 borderRadius: 6, 343 431 }, 432 + navItemCountTablet: { 433 + left: 18, 434 + fontSize: 14, 435 + }, 344 436 345 437 newPostBtn: { 346 438 flexDirection: 'row', ··· 354 446 marginLeft: 12, 355 447 marginTop: 20, 356 448 marginBottom: 10, 449 + gap: 8, 357 450 }, 358 - newPostBtnIconWrapper: { 359 - marginRight: 8, 360 - }, 451 + newPostBtnIconWrapper: {}, 361 452 newPostBtnLabel: { 362 453 color: colors.white, 363 454 fontSize: 16,
+6
src/view/shell/desktop/RightNav.tsx
··· 9 9 import {FEEDBACK_FORM_URL, HELP_DESK_URL} from 'lib/constants' 10 10 import {s} from 'lib/styles' 11 11 import {useStores} from 'state/index' 12 + import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' 12 13 import {pluralize} from 'lib/strings/helpers' 13 14 import {formatCount} from 'view/com/util/numeric/format' 14 15 ··· 16 17 const store = useStores() 17 18 const pal = usePalette('default') 18 19 const palError = usePalette('error') 20 + 21 + const {isTablet} = useWebMediaQueries() 22 + if (isTablet) { 23 + return null 24 + } 19 25 20 26 return ( 21 27 <View style={[styles.rightNav, pal.view]}>
+3 -3
src/view/shell/index.web.tsx
··· 19 19 20 20 const ShellInner = observer(() => { 21 21 const store = useStores() 22 - const {isDesktop} = useWebMediaQueries() 22 + const {isDesktop, isMobile} = useWebMediaQueries() 23 23 const navigator = useNavigation<NavigationProp>() 24 24 25 25 useEffect(() => { ··· 28 28 }) 29 29 }, [navigator, store.shell]) 30 30 31 - const showBottomBar = !isDesktop && !store.onboarding.isActive 31 + const showBottomBar = isMobile && !store.onboarding.isActive 32 32 const showSideNavs = 33 - isDesktop && store.session.hasSession && !store.onboarding.isActive 33 + !isMobile && store.session.hasSession && !store.onboarding.isActive 34 34 return ( 35 35 <> 36 36 <View style={s.hContentRegion}>