Updates to use dynamic/responsive styles on web (#1351)

* Move most responsive queries to the hook

* Fix invalid CSS value

* Fixes to tablet render of post thread

* Fix overflow issues on web

* Fix search header on tablet

* Fix QP margin in web composer

* Fix: only apply double gutter once to flatlist (close #1368)

* Fix styles on discover feeds header

* Fix double discover links in multifeed
zio/stable
Paul Frazee 2023-09-05 10:42:19 -07:00 committed by GitHub
parent be8084ae10
commit 764c7cd569
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 762 additions and 461 deletions

View File

@ -2,13 +2,19 @@ import {useMediaQuery} from 'react-responsive'
import {isNative} from 'platform/detection'
export function useWebMediaQueries() {
const isDesktop = useMediaQuery({
query: '(min-width: 1224px)',
})
const isTabletOrMobile = useMediaQuery({query: '(max-width: 1224px)'})
const isMobile = useMediaQuery({query: '(max-width: 800px)'})
const isDesktop = useMediaQuery({minWidth: 1300})
const isTablet = useMediaQuery({minWidth: 800, maxWidth: 1300})
const isMobile = useMediaQuery({maxWidth: 800})
const isTabletOrMobile = isMobile || isTablet
const isTabletOrDesktop = isDesktop || isTablet
if (isNative) {
return {isMobile: true, isTabletOrMobile: true, isDesktop: false}
return {
isMobile: true,
isTablet: false,
isTabletOrMobile: true,
isTabletOrDesktop: false,
isDesktop: false,
}
}
return {isMobile, isTabletOrMobile, isDesktop}
return {isMobile, isTablet, isTabletOrMobile, isTabletOrDesktop, isDesktop}
}

View File

@ -7,7 +7,7 @@ export const isAndroid = Platform.OS === 'android'
export const isNative = isIOS || isAndroid
export const devicePlatform = isIOS ? 'ios' : isAndroid ? 'android' : 'web'
export const isWeb = !isNative
export const isMobileWebMediaQuery = 'only screen and (max-width: 1230px)'
export const isMobileWebMediaQuery = 'only screen and (max-width: 1300px)'
export const isMobileWeb =
isWeb &&
// @ts-ignore we know window exists -prf

View File

@ -111,7 +111,8 @@ export class PostsMultiFeedModel {
uri: makeProfileLink(feedInfo.data.creator, 'feed', urip.rkey),
})
}
if (!this.hasMore) {
if (!this.hasMore && this.hasContent) {
// only show if hasContent to avoid double discover-feed links
items.push({_reactKey: '__footer__', type: 'footer'})
}
return items

View File

@ -16,9 +16,7 @@ type Props = {
export const WelcomeDesktop = observer(({next}: Props) => {
const pal = usePalette('default')
const horizontal = useMediaQuery({
query: '(min-width: 1230px)',
})
const horizontal = useMediaQuery({minWidth: 1300})
const title = (
<>
<Text

View File

@ -7,7 +7,6 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Button} from 'view/com/util/forms/Button'
import {observer} from 'mobx-react-lite'
import {ViewHeader} from 'view/com/util/ViewHeader'
import {isDesktopWeb} from 'platform/detection'
type Props = {
next: () => void
@ -95,7 +94,7 @@ export const WelcomeMobile = observer(({next, skip}: Props) => {
const styles = StyleSheet.create({
container: {
flex: 1,
marginBottom: isDesktopWeb ? 30 : 60,
marginBottom: 60,
marginHorizontal: 16,
justifyContent: 'space-between',
},

View File

@ -37,9 +37,10 @@ import {toShortUrl} from 'lib/strings/url-helpers'
import {SelectPhotoBtn} from './photos/SelectPhotoBtn'
import {OpenCameraBtn} from './photos/OpenCameraBtn'
import {usePalette} from 'lib/hooks/usePalette'
import QuoteEmbed from '../util/post-embeds/QuoteEmbed'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useExternalLinkFetch} from './useExternalLinkFetch'
import {isDesktopWeb, isAndroid, isIOS} from 'platform/detection'
import {isWeb, isNative, isAndroid, isIOS} from 'platform/detection'
import QuoteEmbed from '../util/post-embeds/QuoteEmbed'
import {GalleryModel} from 'state/models/media/gallery'
import {Gallery} from './photos/Gallery'
import {MAX_GRAPHEME_LENGTH} from 'lib/constants'
@ -61,6 +62,7 @@ export const ComposePost = observer(function ComposePost({
}: Props) {
const {track} = useAnalytics()
const pal = usePalette('default')
const {isDesktop, isMobile} = useWebMediaQueries()
const store = useStores()
const textInput = useRef<TextInputRef>(null)
const [isKeyboardVisible] = useIsKeyboardVisible({iosUseWillEvents: true})
@ -99,9 +101,9 @@ export const ComposePost = observer(function ComposePost({
() => ({
paddingBottom:
isAndroid || (isIOS && !isKeyboardVisible) ? insets.bottom : 0,
paddingTop: isAndroid ? insets.top : isDesktopWeb ? 0 : 15,
paddingTop: isAndroid ? insets.top : isMobile ? 15 : 0,
}),
[insets, isKeyboardVisible],
[insets, isKeyboardVisible, isMobile],
)
const onPressCancel = useCallback(() => {
@ -143,7 +145,7 @@ export const ComposePost = observer(function ComposePost({
[onPressCancel],
)
useEffect(() => {
if (isDesktopWeb) {
if (isWeb) {
window.addEventListener('keydown', onEscape)
return () => window.removeEventListener('keydown', onEscape)
}
@ -240,7 +242,7 @@ export const ComposePost = observer(function ComposePost({
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.outer}>
<View style={[s.flex1, viewStyles]} aria-modal accessibilityViewIsModal>
<View style={styles.topbar}>
<View style={[styles.topbar, isDesktop && styles.topbarDesktop]}>
<TouchableOpacity
testID="composerDiscardButton"
onPress={onPressCancel}
@ -334,7 +336,12 @@ export const ComposePost = observer(function ComposePost({
</View>
) : undefined}
<View style={[pal.border, styles.textInputLayout]}>
<View
style={[
pal.border,
styles.textInputLayout,
isNative && styles.textInputLayoutMobile,
]}>
<UserAvatar avatar={store.me.avatar} size={50} />
<TextInput
ref={textInput}
@ -362,7 +369,7 @@ export const ComposePost = observer(function ComposePost({
/>
)}
{quote ? (
<View style={s.mt5}>
<View style={[s.mt5, isWeb && s.mb10]}>
<QuoteEmbed quote={quote} />
</View>
) : undefined}
@ -395,7 +402,7 @@ export const ComposePost = observer(function ComposePost({
<OpenCameraBtn gallery={gallery} />
</>
) : null}
{isDesktopWeb ? <EmojiPickerButton /> : null}
{isDesktop ? <EmojiPickerButton /> : null}
<View style={s.flex1} />
<SelectLangBtn />
<CharProgress count={graphemeLength} />
@ -414,11 +421,14 @@ const styles = StyleSheet.create({
topbar: {
flexDirection: 'row',
alignItems: 'center',
paddingTop: isDesktopWeb ? 10 : undefined,
paddingBottom: isDesktopWeb ? 10 : 4,
paddingBottom: 4,
paddingHorizontal: 20,
height: 55,
},
topbarDesktop: {
paddingTop: 10,
paddingBottom: 10,
},
postBtn: {
borderRadius: 20,
paddingHorizontal: 20,
@ -465,11 +475,13 @@ const styles = StyleSheet.create({
paddingHorizontal: 15,
},
textInputLayout: {
flex: isDesktopWeb ? undefined : 1,
flexDirection: 'row',
borderTopWidth: 1,
paddingTop: 16,
},
textInputLayoutMobile: {
flex: 1,
},
replyToLayout: {
flexDirection: 'row',
borderTopWidth: 1,

View File

@ -4,11 +4,12 @@ import {UserAvatar} from '../util/UserAvatar'
import {Text} from '../util/text/Text'
import {usePalette} from 'lib/hooks/usePalette'
import {useStores} from 'state/index'
import {isDesktopWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
export function ComposePrompt({onPressCompose}: {onPressCompose: () => void}) {
const store = useStores()
const pal = usePalette('default')
const {isDesktop} = useWebMediaQueries()
return (
<TouchableOpacity
testID="replyPromptBtn"
@ -22,7 +23,7 @@ export function ComposePrompt({onPressCompose}: {onPressCompose: () => void}) {
type="xl"
style={[
pal.text,
isDesktopWeb ? styles.labelDesktopWeb : styles.labelMobile,
isDesktop ? styles.labelDesktopWeb : styles.labelMobile,
]}>
Write your reply
</Text>

View File

@ -7,10 +7,10 @@ import {s, colors} from 'lib/styles'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {Image} from 'expo-image'
import {Text} from 'view/com/util/text/Text'
import {isDesktopWeb} from 'platform/detection'
import {openAltTextModal} from 'lib/media/alt-text'
import {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
interface Props {
gallery: GalleryModel
@ -19,13 +19,14 @@ interface Props {
export const Gallery = observer(function ({gallery}: Props) {
const store = useStores()
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
let side: number
if (gallery.size === 1) {
side = 250
} else {
side = (isDesktopWeb ? 560 : 350) / gallery.size
side = (isMobile ? 350 : 560) / gallery.size
}
const imageStyle = {
@ -33,14 +34,14 @@ export const Gallery = observer(function ({gallery}: Props) {
width: side,
}
const isOverflow = !isDesktopWeb && gallery.size > 2
const isOverflow = isMobile && gallery.size > 2
const altTextControlStyle = isOverflow
? {
left: 4,
bottom: 4,
}
: isDesktopWeb && gallery.size < 3
: !isMobile && gallery.size < 3
? {
left: 8,
top: 8,
@ -60,7 +61,7 @@ export const Gallery = observer(function ({gallery}: Props) {
right: 4,
gap: 4,
}
: isDesktopWeb && gallery.size < 3
: !isMobile && gallery.size < 3
? {
top: 8,
right: 8,

View File

@ -22,9 +22,9 @@ import {TextLink} from '../util/Link'
import {ListModel} from 'state/models/content/list'
import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useStores} from 'state/index'
import {s} from 'lib/styles'
import {isDesktopWeb} from 'platform/detection'
import {ListActions} from './ListActions'
import {makeProfileLink} from 'lib/routes/links'
import {sanitizeHandle} from 'lib/strings/handles'
@ -283,6 +283,7 @@ const ListHeader = observer(
}) => {
const pal = usePalette('default')
const store = useStores()
const {isDesktop} = useWebMediaQueries()
const descriptionRT = React.useMemo(
() =>
list?.description &&
@ -318,7 +319,7 @@ const ListHeader = observer(
richText={descriptionRT}
/>
)}
{isDesktopWeb && (
{isDesktop && (
<ListActions
isOwner={isOwner}
muted={list.viewer?.muted}
@ -334,7 +335,8 @@ const ListHeader = observer(
<UserAvatar type="list" avatar={list.avatar} size={64} />
</View>
</View>
<View style={[styles.fakeSelector, pal.border]}>
<View
style={{flexDirection: 'row', paddingHorizontal: isDesktop ? 16 : 6}}>
<View
style={[styles.fakeSelectorItem, {borderColor: pal.colors.link}]}>
<Text type="md-medium" style={[pal.text]}>
@ -365,10 +367,6 @@ const styles = StyleSheet.create({
gap: 8,
marginTop: 12,
},
fakeSelector: {
flexDirection: 'row',
paddingHorizontal: isDesktopWeb ? 16 : 6,
},
fakeSelectorItem: {
paddingHorizontal: 12,
paddingBottom: 8,

View File

@ -5,7 +5,7 @@ import {Button} from '../util/forms/Button'
import {s} from 'lib/styles'
import {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette'
import {isDesktopWeb} from 'platform/detection'
import {isNative} from 'platform/detection'
import {
FontAwesomeIcon,
FontAwesomeIconStyle,
@ -205,7 +205,7 @@ export function Component({}: {}) {
const styles = StyleSheet.create({
container: {
flex: 1,
paddingBottom: isDesktopWeb ? 0 : 50,
paddingBottom: isNative ? 50 : 0,
paddingHorizontal: 16,
},
textInputWrapper: {

View File

@ -18,7 +18,7 @@ import {useTheme} from 'lib/ThemeContext'
import {Text} from '../util/text/Text'
import LinearGradient from 'react-native-linear-gradient'
import {useStores} from 'state/index'
import {isDesktopWeb, isAndroid} from 'platform/detection'
import {isAndroid, isWeb} from 'platform/detection'
import {ImageModel} from 'state/models/media/image'
export const snapPoints = ['fullscreen']
@ -35,7 +35,7 @@ export function Component({image}: Props) {
const windim = useWindowDimensions()
const imageStyles = useMemo<ImageStyle>(() => {
const maxWidth = isDesktopWeb ? 450 : windim.width
const maxWidth = isWeb ? 450 : windim.width
if (image.height > image.width) {
return {
resizeMode: 'contain',
@ -137,12 +137,12 @@ const styles = StyleSheet.create({
flex: 1,
height: '100%',
width: '100%',
paddingVertical: isDesktopWeb ? 0 : 18,
paddingVertical: isWeb ? 0 : 18,
},
scrollContainer: {
flex: 1,
height: '100%',
paddingHorizontal: isDesktopWeb ? 0 : 12,
paddingHorizontal: isWeb ? 0 : 12,
},
scrollInner: {
gap: 12,

View File

@ -11,7 +11,7 @@ import {s, colors} from 'lib/styles'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {cleanError} from 'lib/strings/errors'
import {usePalette} from 'lib/hooks/usePalette'
import {isDesktopWeb} from 'platform/detection'
import {isWeb} from 'platform/detection'
import type {ConfirmModal} from 'state/models/ui/shell'
export const snapPoints = ['50%']
@ -96,7 +96,7 @@ const styles = StyleSheet.create({
container: {
flex: 1,
padding: 10,
paddingBottom: isDesktopWeb ? 0 : 60,
paddingBottom: isWeb ? 0 : 60,
},
title: {
textAlign: 'center',

View File

@ -11,13 +11,15 @@ import {TextLink} from '../util/Link'
import {ToggleButton} from '../util/forms/ToggleButton'
import {usePalette} from 'lib/hooks/usePalette'
import {CONFIGURABLE_LABEL_GROUPS} from 'lib/labeling/const'
import {isDesktopWeb, isIOS} from 'platform/detection'
import {isIOS} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import * as Toast from '../util/Toast'
export const snapPoints = ['90%']
export const Component = observer(({}: {}) => {
const store = useStores()
const {isMobile} = useWebMediaQueries()
const pal = usePalette('default')
React.useEffect(() => {
@ -88,9 +90,14 @@ export const Component = observer(({}: {}) => {
<ContentLabelPref group="hate" />
<ContentLabelPref group="spam" />
<ContentLabelPref group="impersonation" />
<View style={styles.bottomSpacer} />
<View style={{height: isMobile ? 60 : 0}} />
</ScrollView>
<View style={[styles.btnContainer, pal.borderDark]}>
<View
style={[
styles.btnContainer,
isMobile && styles.btnContainerMobile,
pal.borderDark,
]}>
<Pressable
testID="sendReportBtn"
onPress={onPressDone}
@ -259,14 +266,13 @@ const styles = StyleSheet.create({
flex: 1,
paddingHorizontal: 10,
},
bottomSpacer: {
height: isDesktopWeb ? 0 : 60,
},
btnContainer: {
paddingTop: 10,
paddingHorizontal: 10,
paddingBottom: isDesktopWeb ? 0 : 40,
borderTopWidth: isDesktopWeb ? 0 : 1,
},
btnContainerMobile: {
paddingBottom: 40,
borderTopWidth: 1,
},
contentLabelPref: {

View File

@ -22,8 +22,8 @@ import {UserAvatar} from '../util/UserAvatar'
import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext'
import {useAnalytics} from 'lib/analytics/analytics'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {cleanError, isNetworkError} from 'lib/strings/errors'
import {isDesktopWeb} from 'platform/detection'
const MAX_NAME = 64 // todo
const MAX_DESCRIPTION = 300 // todo
@ -38,6 +38,7 @@ export function Component({
list?: ListModel
}) {
const store = useStores()
const {isMobile} = useWebMediaQueries()
const [error, setError] = useState<string>('')
const pal = usePalette('default')
const theme = useTheme()
@ -130,7 +131,12 @@ export function Component({
return (
<KeyboardAvoidingView behavior="height">
<ScrollView
style={[pal.view, styles.container]}
style={[
pal.view,
{
paddingHorizontal: isMobile ? 16 : 0,
},
]}
testID="createOrEditMuteListModal">
<Text style={[styles.title, pal.text]}>
{list ? 'Edit Mute List' : 'New Mute List'}
@ -226,9 +232,6 @@ export function Component({
}
const styles = StyleSheet.create({
container: {
paddingHorizontal: isDesktopWeb ? 0 : 16,
},
title: {
textAlign: 'center',
fontWeight: 'bold',

View File

@ -13,10 +13,10 @@ import {useStores} from 'state/index'
import {s, colors, gradients} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {cleanError} from 'lib/strings/errors'
import {resetToTab} from '../../../Navigation'
import {isDesktopWeb} from 'platform/detection'
export const snapPoints = ['60%']
@ -24,6 +24,7 @@ export function Component({}: {}) {
const pal = usePalette('default')
const theme = useTheme()
const store = useStores()
const {isMobile} = useWebMediaQueries()
const [isEmailSent, setIsEmailSent] = React.useState<boolean>(false)
const [confirmCode, setConfirmCode] = React.useState<string>('')
const [password, setPassword] = React.useState<string>('')
@ -78,7 +79,7 @@ export function Component({}: {}) {
type="title-xl"
numberOfLines={1}
style={[
isDesktopWeb ? styles.titleDesktop : styles.titleMobile,
isMobile ? styles.titleMobile : styles.titleDesktop,
pal.text,
s.bold,
]}>

View File

@ -7,6 +7,7 @@ import {useTheme} from 'lib/ThemeContext'
import {Text} from '../util/text/Text'
import LinearGradient from 'react-native-linear-gradient'
import {useStores} from 'state/index'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import ImageEditor, {Position} from 'react-avatar-editor'
import {TextInput} from './util'
import {enforceLen} from 'lib/strings/helpers'
@ -18,7 +19,6 @@ import {Slider} from '@miblanchard/react-native-slider'
import {MaterialIcons} from '@expo/vector-icons'
import {observer} from 'mobx-react-lite'
import {getKeys} from 'lib/type-assertions'
import {isDesktopWeb} from 'platform/detection'
export const snapPoints = ['80%']
@ -51,6 +51,7 @@ export const Component = observer(function ({image, gallery}: Props) {
const theme = useTheme()
const store = useStores()
const windowDimensions = useWindowDimensions()
const {isMobile} = useWebMediaQueries()
const {
aspectRatio,
@ -174,19 +175,28 @@ export const Component = observer(function ({image, gallery}: Props) {
const computedWidth =
windowDimensions.width > 500 ? 410 : windowDimensions.width - 80
const sideLength = isDesktopWeb ? 300 : computedWidth
const sideLength = isMobile ? computedWidth : 300
const dimensions = image.getResizedDimensions(aspectRatio, sideLength)
const imgContainerStyles = {width: sideLength, height: sideLength}
const imgControlStyles = {
alignItems: 'center' as const,
flexDirection: isDesktopWeb ? ('row' as const) : ('column' as const),
gap: isDesktopWeb ? 5 : 0,
flexDirection: isMobile ? ('column' as const) : ('row' as const),
gap: isMobile ? 0 : 5,
}
return (
<View testID="editImageModal" style={[pal.view, styles.container, s.flex1]}>
<View
testID="editImageModal"
style={[
pal.view,
styles.container,
s.flex1,
{
paddingHorizontal: isMobile ? 16 : undefined,
},
]}>
<Text style={[styles.title, pal.text]}>Edit image</Text>
<View style={[styles.gap18, s.flexRow]}>
<View>
@ -213,7 +223,7 @@ export const Component = observer(function ({image, gallery}: Props) {
/>
</View>
<View>
{isDesktopWeb ? (
{!isMobile ? (
<Text type="sm-bold" style={pal.text}>
Ratios
</Text>
@ -248,7 +258,7 @@ export const Component = observer(function ({image, gallery}: Props) {
)
})}
</View>
{isDesktopWeb ? (
{!isMobile ? (
<Text type="sm-bold" style={[pal.text, styles.subsection]}>
Transformations
</Text>
@ -282,7 +292,14 @@ export const Component = observer(function ({image, gallery}: Props) {
</Text>
<TextInput
testID="altTextImageInput"
style={[styles.textArea, pal.border, pal.text]}
style={[
styles.textArea,
pal.border,
pal.text,
{
maxHeight: isMobile ? 50 : undefined,
},
]}
keyboardAppearance={theme.colorScheme}
multiline
value={altText}
@ -317,7 +334,6 @@ export const Component = observer(function ({image, gallery}: Props) {
const styles = StyleSheet.create({
container: {
gap: 18,
paddingHorizontal: isDesktopWeb ? undefined : 16,
height: '100%',
width: '100%',
},
@ -369,7 +385,6 @@ const styles = StyleSheet.create({
fontSize: 16,
height: 100,
textAlignVertical: 'top',
maxHeight: isDesktopWeb ? undefined : 50,
},
bottomSection: {
borderTopWidth: 1,

View File

@ -12,7 +12,7 @@ import * as Toast from '../util/Toast'
import {useStores} from 'state/index'
import {ScrollView} from './util'
import {usePalette} from 'lib/hooks/usePalette'
import {isDesktopWeb} from 'platform/detection'
import {isWeb} from 'platform/detection'
export const snapPoints = ['70%']
@ -127,7 +127,7 @@ const InviteCode = observer(
const styles = StyleSheet.create({
container: {
flex: 1,
paddingBottom: isDesktopWeb ? 0 : 50,
paddingBottom: isWeb ? 0 : 50,
},
title: {
textAlign: 'center',

View File

@ -19,7 +19,7 @@ import {sanitizeDisplayName} from 'lib/strings/display-names'
import {sanitizeHandle} from 'lib/strings/handles'
import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {isDesktopWeb, isAndroid} from 'platform/detection'
import {isWeb, isAndroid} from 'platform/detection'
import isEqual from 'lodash.isequal'
export const snapPoints = ['fullscreen']
@ -231,7 +231,7 @@ export const Component = observer(
const styles = StyleSheet.create({
container: {
paddingHorizontal: isDesktopWeb ? 0 : 16,
paddingHorizontal: isWeb ? 0 : 16,
},
title: {
textAlign: 'center',

View File

@ -3,8 +3,8 @@ import {TouchableWithoutFeedback, StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite'
import {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import type {Modal as ModalIface} from 'state/models/ui/shell'
import {isMobileWeb} from 'platform/detection'
import * as ConfirmModal from './Confirm'
import * as EditProfileModal from './EditProfile'
@ -47,6 +47,7 @@ export const ModalsContainer = observer(function ModalsContainer() {
function Modal({modal}: {modal: ModalIface}) {
const store = useStores()
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
if (!store.shell.isModalActive) {
return null
@ -119,7 +120,7 @@ function Modal({modal}: {modal: ModalIface}) {
<View
style={[
styles.container,
isMobileWeb && styles.containerMobile,
isMobile && styles.containerMobile,
pal.view,
pal.border,
]}>

View File

@ -2,11 +2,12 @@ import React from 'react'
import {StyleSheet, View} from 'react-native'
import {ModerationUI} from '@atproto/api'
import {useStores} from 'state/index'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {s} from 'lib/styles'
import {Text} from '../util/text/Text'
import {TextLink} from '../util/Link'
import {usePalette} from 'lib/hooks/usePalette'
import {isDesktopWeb} from 'platform/detection'
import {isWeb} from 'platform/detection'
import {listUriToHref} from 'lib/strings/url-helpers'
import {Button} from '../util/forms/Button'
@ -20,6 +21,7 @@ export function Component({
moderation: ModerationUI
}) {
const store = useStores()
const {isMobile} = useWebMediaQueries()
const pal = usePalette('default')
let name
@ -64,7 +66,15 @@ export function Component({
}
return (
<View testID="moderationDetailsModal" style={[styles.container, pal.view]}>
<View
testID="moderationDetailsModal"
style={[
styles.container,
{
paddingHorizontal: isMobile ? 14 : 0,
},
pal.view,
]}>
<Text type="title-xl" style={[pal.text, styles.title]}>
{name}
</Text>
@ -87,7 +97,6 @@ export function Component({
const styles = StyleSheet.create({
container: {
flex: 1,
paddingHorizontal: isDesktopWeb ? 0 : 14,
},
title: {
textAlign: 'center',
@ -99,7 +108,7 @@ const styles = StyleSheet.create({
},
btn: {
paddingVertical: 14,
marginTop: isDesktopWeb ? 40 : 0,
marginBottom: isDesktopWeb ? 0 : 40,
marginTop: isWeb ? 40 : 0,
marginBottom: isWeb ? 0 : 40,
},
})

View File

@ -5,7 +5,8 @@ import {Text} from '../util/text/Text'
import {useStores} from 'state/index'
import {s, colors} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {isDesktopWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {isWeb} from 'platform/detection'
import {Button} from '../util/forms/Button'
import {SelectableBtn} from '../util/forms/SelectableBtn'
import {ScrollView} from 'view/com/modals/util'
@ -25,6 +26,7 @@ export const Component = observer(function Component({
}) {
const pal = usePalette('default')
const store = useStores()
const {isMobile} = useWebMediaQueries()
const [selected, setSelected] = useState(labels)
const toggleAdultLabel = (label: string) => {
@ -54,7 +56,12 @@ export const Component = observer(function Component({
</View>
<ScrollView>
<View style={[styles.section, pal.border, {borderBottomWidth: 1}]}>
<View
style={[
styles.section,
pal.border,
{borderBottomWidth: 1, paddingHorizontal: isMobile ? 20 : 0},
]}>
<View
style={{
flexDirection: 'row',
@ -152,11 +159,11 @@ export const Component = observer(function Component({
const styles = StyleSheet.create({
container: {
flex: 1,
paddingBottom: isDesktopWeb ? 0 : 40,
paddingBottom: isWeb ? 0 : 40,
},
titleSection: {
paddingTop: isDesktopWeb ? 0 : 4,
paddingBottom: isDesktopWeb ? 14 : 10,
paddingTop: isWeb ? 0 : 4,
paddingBottom: isWeb ? 14 : 10,
},
title: {
textAlign: 'center',
@ -170,7 +177,6 @@ const styles = StyleSheet.create({
section: {
borderTopWidth: 1,
paddingVertical: 20,
paddingHorizontal: isDesktopWeb ? 0 : 20,
},
adultExplainer: {
paddingLeft: 5,

View File

@ -2,8 +2,8 @@ import React from 'react'
import {StyleSheet, Text, View, Pressable} from 'react-native'
import LinearGradient from 'react-native-linear-gradient'
import {s, colors, gradients} from 'lib/styles'
import {isDesktopWeb} from 'platform/detection'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
export const ConfirmLanguagesButton = ({
onPress,
@ -13,8 +13,17 @@ export const ConfirmLanguagesButton = ({
extraText?: string
}) => {
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
return (
<View style={[styles.btnContainer, pal.borderDark]}>
<View
style={[
styles.btnContainer,
pal.borderDark,
isMobile && {
paddingBottom: 40,
borderTopWidth: 1,
},
]}>
<Pressable
testID="confirmContentLanguagesBtn"
onPress={onPress}
@ -37,8 +46,6 @@ const styles = StyleSheet.create({
btnContainer: {
paddingTop: 10,
paddingHorizontal: 10,
paddingBottom: isDesktopWeb ? 0 : 40,
borderTopWidth: isDesktopWeb ? 0 : 1,
},
btn: {
flexDirection: 'row',

View File

@ -4,7 +4,8 @@ import {ScrollView} from '../util'
import {useStores} from 'state/index'
import {Text} from '../../util/text/Text'
import {usePalette} from 'lib/hooks/usePalette'
import {isDesktopWeb, deviceLocales} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {deviceLocales} from 'platform/detection'
import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../../locale/languages'
import {LanguageToggle} from './LanguageToggle'
import {ConfirmLanguagesButton} from './ConfirmLanguagesButton'
@ -14,6 +15,7 @@ export const snapPoints = ['100%']
export function Component({}: {}) {
const store = useStores()
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
const onPressDone = React.useCallback(() => {
store.shell.closeModal()
}, [store])
@ -47,7 +49,19 @@ export function Component({}: {}) {
)
return (
<View testID="contentLanguagesModal" style={[pal.view, styles.container]}>
<View
testID="contentLanguagesModal"
style={[
pal.view,
styles.container,
isMobile
? {
paddingTop: 20,
}
: {
maxHeight: '90vh',
},
]}>
<Text style={[pal.text, styles.title]}>Content Languages</Text>
<Text style={[pal.text, styles.description]}>
Which languages would you like to see in your algorithmic feeds?
@ -67,7 +81,11 @@ export function Component({}: {}) {
}}
/>
))}
<View style={styles.bottomSpacer} />
<View
style={{
height: isMobile ? 60 : 0,
}}
/>
</ScrollView>
<ConfirmLanguagesButton onPress={onPressDone} />
</View>
@ -77,7 +95,6 @@ export function Component({}: {}) {
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 20,
},
title: {
textAlign: 'center',
@ -94,7 +111,4 @@ const styles = StyleSheet.create({
flex: 1,
paddingHorizontal: 10,
},
bottomSpacer: {
height: isDesktopWeb ? 0 : 60,
},
})

View File

@ -5,7 +5,8 @@ import {ScrollView} from '../util'
import {useStores} from 'state/index'
import {Text} from '../../util/text/Text'
import {usePalette} from 'lib/hooks/usePalette'
import {isDesktopWeb, deviceLocales} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {deviceLocales} from 'platform/detection'
import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../../locale/languages'
import {ConfirmLanguagesButton} from './ConfirmLanguagesButton'
import {ToggleButton} from 'view/com/util/forms/ToggleButton'
@ -15,6 +16,7 @@ export const snapPoints = ['100%']
export const Component = observer(() => {
const store = useStores()
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
const onPressDone = React.useCallback(() => {
store.shell.closeModal()
}, [store])
@ -48,7 +50,19 @@ export const Component = observer(() => {
)
return (
<View testID="postLanguagesModal" style={[pal.view, styles.container]}>
<View
testID="postLanguagesModal"
style={[
pal.view,
styles.container,
isMobile
? {
paddingTop: 20,
}
: {
maxHeight: '90vh',
},
]}>
<Text style={[pal.text, styles.title]}>Post Languages</Text>
<Text style={[pal.text, styles.description]}>
Which languages are used in this post?
@ -80,7 +94,11 @@ export const Component = observer(() => {
/>
)
})}
<View style={styles.bottomSpacer} />
<View
style={{
height: isMobile ? 60 : 0,
}}
/>
</ScrollView>
<ConfirmLanguagesButton onPress={onPressDone} />
</View>
@ -90,7 +108,6 @@ export const Component = observer(() => {
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 20,
},
title: {
textAlign: 'center',
@ -107,9 +124,6 @@ const styles = StyleSheet.create({
flex: 1,
paddingHorizontal: 10,
},
bottomSpacer: {
height: isDesktopWeb ? 0 : 60,
},
languageToggle: {
borderTopWidth: 1,
borderRadius: 0,

View File

@ -5,9 +5,9 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {CharProgress} from '../../composer/char-progress/CharProgress'
import {Text} from '../../util/text/Text'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {s} from 'lib/styles'
import {SendReportButton} from './SendReportButton'
import {isDesktopWeb} from 'platform/detection'
export function InputIssueDetails({
details,
@ -23,9 +23,13 @@ export function InputIssueDetails({
isProcessing: boolean
}) {
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
return (
<View style={[styles.detailsContainer]}>
<View
style={{
marginTop: isMobile ? 12 : 0,
}}>
<TouchableOpacity
testID="addDetailsBtn"
style={[s.mb10, styles.backBtn]}
@ -63,9 +67,6 @@ export function InputIssueDetails({
}
const styles = StyleSheet.create({
detailsContainer: {
marginTop: isDesktopWeb ? 0 : 12,
},
backBtn: {
flexDirection: 'row',
alignItems: 'center',

View File

@ -3,6 +3,7 @@ import {Linking, StyleSheet, TouchableOpacity, View} from 'react-native'
import {ScrollView} from 'react-native-gesture-handler'
import {AtUri} from '@atproto/api'
import {useStores} from 'state/index'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {s} from 'lib/styles'
import {Text} from '../../util/text/Text'
import * as Toast from '../../util/Toast'
@ -37,6 +38,7 @@ type ReportComponentProps =
export function Component(content: ReportComponentProps) {
const store = useStores()
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
const [isProcessing, setIsProcessing] = useState(false)
const [showDetailsInput, setShowDetailsInput] = useState(false)
const [error, setError] = useState<string>()
@ -87,7 +89,13 @@ export function Component(content: ReportComponentProps) {
return (
<ScrollView testID="reportModal" style={[s.flex1, pal.view]}>
<View style={styles.container}>
<View
style={[
styles.container,
isMobile && {
paddingBottom: 40,
},
]}>
{showDetailsInput ? (
<InputIssueDetails
details={details}
@ -153,16 +161,14 @@ const SelectIssue = ({
<Text style={[pal.textLight, styles.description]}>
What is the issue with this {collectionName}?
</Text>
<ReportReasonOptions
atUri={atUri}
selectedIssue={issue}
onSelectIssue={onSelectIssue}
/>
{error ? (
<View style={s.mt10}>
<ErrorMessage message={error} />
</View>
) : undefined}
<View style={{marginBottom: 10}}>
<ReportReasonOptions
atUri={atUri}
selectedIssue={issue}
onSelectIssue={onSelectIssue}
/>
</View>
{error ? <ErrorMessage message={error} /> : undefined}
{/* If no atUri is present, the report would be for account in which case, we allow sending without specifying a reason */}
{issue || !atUri ? (
<>
@ -188,7 +194,6 @@ const SelectIssue = ({
const styles = StyleSheet.create({
container: {
paddingHorizontal: 10,
paddingBottom: 40,
},
title: {
textAlign: 'center',

View File

@ -13,8 +13,8 @@ export const FeedsTabBar = observer(
(
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
) => {
const {isDesktop} = useWebMediaQueries()
if (!isDesktop) {
const {isMobile} = useWebMediaQueries()
if (isMobile) {
return <FeedsTabBarMobile {...props} />
} else {
return <FeedsTabBarDesktop {...props} />

View File

@ -3,7 +3,8 @@ import {StyleSheet, View, ScrollView, LayoutChangeEvent} from 'react-native'
import {Text} from '../util/text/Text'
import {PressableWithHover} from '../util/PressableWithHover'
import {usePalette} from 'lib/hooks/usePalette'
import {isDesktopWeb, isMobileWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {isWeb} from 'platform/detection'
import {DraggableScrollView} from './DraggableScrollView'
export interface TabBarProps {
@ -30,6 +31,7 @@ export function TabBar({
() => ({borderBottomColor: indicatorColor || pal.colors.link}),
[indicatorColor, pal],
)
const {isDesktop, isTablet} = useWebMediaQueries()
// scrolls to the selected item when the page changes
useEffect(() => {
@ -61,6 +63,7 @@ export function TabBar({
[],
)
const styles = isDesktop || isTablet ? desktopStyles : mobileStyles
return (
<View testID={testID} style={[pal.view, styles.outer]}>
<DraggableScrollView
@ -78,7 +81,7 @@ export function TabBar({
hoverStyle={pal.viewLight}
onPress={() => onPressItem(i)}>
<Text
type={isDesktopWeb ? 'xl-bold' : 'lg-bold'}
type={isDesktop || isTablet ? 'xl-bold' : 'lg-bold'}
testID={testID ? `${testID}-${item}` : undefined}
style={selected ? pal.text : pal.textLight}>
{item}
@ -91,46 +94,46 @@ export function TabBar({
)
}
const styles = isDesktopWeb
? StyleSheet.create({
outer: {
flexDirection: 'row',
width: 598,
},
contentContainer: {
columnGap: 8,
marginLeft: 14,
paddingRight: 14,
backgroundColor: 'transparent',
},
item: {
paddingTop: 14,
paddingBottom: 12,
paddingHorizontal: 10,
borderBottomWidth: 3,
borderBottomColor: 'transparent',
justifyContent: 'center',
},
})
: StyleSheet.create({
outer: {
flex: 1,
flexDirection: 'row',
backgroundColor: 'transparent',
maxWidth: '100%',
},
contentContainer: {
columnGap: isMobileWeb ? 0 : 20,
marginLeft: isMobileWeb ? 0 : 18,
paddingRight: isMobileWeb ? 0 : 36,
backgroundColor: 'transparent',
},
item: {
paddingTop: 10,
paddingBottom: 10,
paddingHorizontal: isMobileWeb ? 8 : 0,
borderBottomWidth: 3,
borderBottomColor: 'transparent',
justifyContent: 'center',
},
})
const desktopStyles = StyleSheet.create({
outer: {
flexDirection: 'row',
width: 598,
},
contentContainer: {
columnGap: 8,
marginLeft: 14,
paddingRight: 14,
backgroundColor: 'transparent',
},
item: {
paddingTop: 14,
paddingBottom: 12,
paddingHorizontal: 10,
borderBottomWidth: 3,
borderBottomColor: 'transparent',
justifyContent: 'center',
},
})
const mobileStyles = StyleSheet.create({
outer: {
flex: 1,
flexDirection: 'row',
backgroundColor: 'transparent',
maxWidth: '100%',
},
contentContainer: {
columnGap: isWeb ? 0 : 20,
marginLeft: isWeb ? 0 : 18,
paddingRight: isWeb ? 0 : 36,
backgroundColor: 'transparent',
},
item: {
paddingTop: 10,
paddingBottom: 10,
paddingHorizontal: isWeb ? 8 : 0,
borderBottomWidth: 3,
borderBottomColor: 'transparent',
justifyContent: 'center',
},
})

View File

@ -18,18 +18,24 @@ import {
} from '@fortawesome/react-native-fontawesome'
import {PostThreadItem} from './PostThreadItem'
import {ComposePrompt} from '../composer/Prompt'
import {ViewHeader} from '../util/ViewHeader'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {Text} from '../util/text/Text'
import {s} from 'lib/styles'
import {isIOS, isDesktopWeb, isMobileWeb} from 'platform/detection'
import {isIOS, isDesktopWeb} from 'platform/detection'
import {usePalette} from 'lib/hooks/usePalette'
import {useSetTitle} from 'lib/hooks/useSetTitle'
import {useNavigation} from '@react-navigation/native'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {NavigationProp} from 'lib/routes/types'
import {sanitizeDisplayName} from 'lib/strings/display-names'
const MAINTAIN_VISIBLE_CONTENT_POSITION = {minIndexForVisible: 0}
const TOP_COMPONENT = {
_reactKey: '__top_component__',
_isHighlightedPost: false,
}
const PARENT_SPINNER = {
_reactKey: '__parent_spinner__',
_isHighlightedPost: false,
@ -47,6 +53,7 @@ const BOTTOM_COMPONENT = {
}
type YieldedItem =
| PostThreadItemModel
| typeof TOP_COMPONENT
| typeof PARENT_SPINNER
| typeof REPLY_PROMPT
| typeof DELETED
@ -63,13 +70,14 @@ export const PostThread = observer(function PostThread({
onPressReply: () => void
}) {
const pal = usePalette('default')
const {isTablet} = useWebMediaQueries()
const ref = useRef<FlatList>(null)
const hasScrolledIntoView = useRef<boolean>(false)
const [isRefreshing, setIsRefreshing] = React.useState(false)
const navigation = useNavigation<NavigationProp>()
const posts = React.useMemo(() => {
if (view.thread) {
const arr = Array.from(flattenThread(view.thread))
const arr = [TOP_COMPONENT].concat(Array.from(flattenThread(view.thread)))
if (view.isLoadingFromCache) {
if (view.thread?.postRecord?.reply) {
arr.unshift(PARENT_SPINNER)
@ -158,7 +166,9 @@ export const PostThread = observer(function PostThread({
const renderItem = React.useCallback(
({item, index}: {item: YieldedItem; index: number}) => {
if (item === PARENT_SPINNER) {
if (item === TOP_COMPONENT) {
return isTablet ? <ViewHeader title="Post" /> : null
} else if (item === PARENT_SPINNER) {
return (
<View style={styles.parentSpinner}>
<ActivityIndicator />
@ -186,19 +196,8 @@ export const PostThread = observer(function PostThread({
// HACK
// due to some complexities with how flatlist works, this is the easiest way
// I could find to get a border positioned directly under the last item
// -
// addendum -- it's also the best way to get mobile web to add padding
// at the bottom of the thread since paddingbottom is ignored. yikes.
// -prf
return (
<View
style={[
styles.bottomBorder,
pal.border,
isMobileWeb && styles.bottomSpacer,
]}
/>
)
return <View style={[pal.border, styles.bottomSpacer]} />
} else if (item === CHILD_SPINNER) {
return (
<View style={styles.childSpinner}>
@ -219,7 +218,7 @@ export const PostThread = observer(function PostThread({
}
return <></>
},
[onRefresh, onPressReply, pal, posts],
[onRefresh, onPressReply, pal, posts, isTablet],
)
// loading
@ -331,7 +330,6 @@ export const PostThread = observer(function PostThread({
}
onScrollToIndexFailed={onScrollToIndexFailed}
style={s.hContentRegion}
contentContainerStyle={styles.contentContainerExtra}
/>
)
})
@ -384,13 +382,8 @@ const styles = StyleSheet.create({
paddingVertical: 10,
},
childSpinner: {},
bottomBorder: {
borderBottomWidth: 1,
},
bottomSpacer: {
height: 400,
},
contentContainerExtra: {
paddingBottom: 500,
borderTopWidth: 1,
},
})

View File

@ -22,7 +22,7 @@ import {s} from 'lib/styles'
import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext'
import {isDesktopWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {CogIcon} from 'lib/icons'
export const MultiFeed = observer(function Feed({
@ -48,6 +48,7 @@ export const MultiFeed = observer(function Feed({
}) {
const pal = usePalette('default')
const theme = useTheme()
const {isMobile} = useWebMediaQueries()
const {track} = useAnalytics()
const [isRefreshing, setIsRefreshing] = React.useState(false)
@ -80,19 +81,27 @@ export const MultiFeed = observer(function Feed({
const renderItem = React.useCallback(
({item}: {item: MultiFeedItem}) => {
if (item.type === 'header') {
if (isDesktopWeb) {
if (!isMobile) {
return (
<View style={[pal.view, pal.border, styles.headerDesktop]}>
<Text type="2xl-bold" style={pal.text}>
My Feeds
</Text>
<Link href="/settings/saved-feeds">
<CogIcon strokeWidth={1.5} style={pal.icon} size={28} />
</Link>
</View>
<>
<View style={[pal.view, pal.border, styles.headerDesktop]}>
<Text type="2xl-bold" style={pal.text}>
My Feeds
</Text>
<Link href="/settings/saved-feeds">
<CogIcon strokeWidth={1.5} style={pal.icon} size={28} />
</Link>
</View>
<DiscoverLink />
</>
)
}
return <View style={[styles.header, pal.border]} />
return (
<>
<View style={[styles.header, pal.border]} />
<DiscoverLink />
</>
)
} else if (item.type === 'feed-header') {
return (
<View style={styles.feedHeader}>
@ -124,18 +133,11 @@ export const MultiFeed = observer(function Feed({
</Link>
)
} else if (item.type === 'footer') {
return (
<Link style={[styles.footerLink, pal.viewLight]} href="/search/feeds">
<FontAwesomeIcon icon="search" size={18} color={pal.colors.text} />
<Text type="xl-medium" style={pal.text}>
Discover new feeds
</Text>
</Link>
)
return <DiscoverLink />
}
return null
},
[pal],
[pal, isMobile],
)
const ListFooter = React.useCallback(
@ -150,17 +152,6 @@ export const MultiFeed = observer(function Feed({
[multifeed.isLoading, isRefreshing, pal],
)
const ListHeader = React.useCallback(() => {
return (
<Link style={[styles.footerLink, pal.viewLight]} href="/search/feeds">
<FontAwesomeIcon icon="search" size={18} color={pal.colors.text} />
<Text type="xl-medium" style={pal.text}>
Discover new feeds
</Text>
</Link>
)
}, [pal])
return (
<View testID={testID} style={style}>
{multifeed.items.length > 0 && (
@ -171,7 +162,6 @@ export const MultiFeed = observer(function Feed({
keyExtractor={item => item._reactKey}
renderItem={renderItem}
ListFooterComponent={ListFooter}
ListHeaderComponent={ListHeader}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
@ -199,6 +189,18 @@ export const MultiFeed = observer(function Feed({
)
})
function DiscoverLink() {
const pal = usePalette('default')
return (
<Link style={[styles.discoverLink, pal.viewLight]} href="/search/feeds">
<FontAwesomeIcon icon="search" size={18} color={pal.colors.text} />
<Text type="xl-medium" style={pal.text}>
Discover new feeds
</Text>
</Link>
)
}
const styles = StyleSheet.create({
container: {
height: '100%',
@ -237,7 +239,7 @@ const styles = StyleSheet.create({
borderTopWidth: 1,
borderBottomWidth: 1,
},
footerLink: {
discoverLink: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',

View File

@ -27,8 +27,9 @@ import {UserBanner} from '../util/UserBanner'
import {ProfileHeaderAlerts} from '../util/moderation/ProfileHeaderAlerts'
import {usePalette} from 'lib/hooks/usePalette'
import {useAnalytics} from 'lib/analytics/analytics'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {NavigationProp} from 'lib/routes/types'
import {isDesktopWeb, isNative} from 'platform/detection'
import {isNative} from 'platform/detection'
import {FollowState} from 'state/models/cache/my-follows'
import {shareUrl} from 'lib/sharing'
import {formatCount} from '../util/numeric/format'
@ -108,6 +109,7 @@ const ProfileHeaderLoaded = observer(
const navigation = useNavigation<NavigationProp>()
const {track} = useAnalytics()
const invalidHandle = isInvalidHandle(view.handle)
const {isDesktop} = useWebMediaQueries()
const onPressBack = React.useCallback(() => {
navigation.goBack()
@ -510,7 +512,7 @@ const ProfileHeaderLoaded = observer(
)}
<ProfileHeaderAlerts moderation={view.moderation} />
</View>
{!isDesktopWeb && !hideBackButton && (
{!isDesktop && !hideBackButton && (
<TouchableWithoutFeedback
onPress={onPressBack}
hitSlop={BACK_HITSLOP}

View File

@ -10,6 +10,7 @@ import {useTheme} from 'lib/ThemeContext'
import {usePalette} from 'lib/hooks/usePalette'
import {useStores} from 'state/index'
import {useAnalytics} from 'lib/analytics/analytics'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {HITSLOP_10} from 'lib/constants'
interface Props {
@ -37,6 +38,7 @@ export function HeaderWithInput({
const pal = usePalette('default')
const {track} = useAnalytics()
const textInput = React.useRef<TextInput>(null)
const {isMobile} = useWebMediaQueries()
const onPressMenu = React.useCallback(() => {
track('ViewHeader:MenuButtonClicked')
@ -49,8 +51,14 @@ export function HeaderWithInput({
}, [onPressCancelSearch, textInput])
return (
<View style={[pal.view, pal.border, styles.header]}>
{showMenu ? (
<View
style={[
pal.view,
pal.border,
styles.header,
!isMobile && styles.headerDesktop,
]}>
{showMenu && isMobile ? (
<TouchableOpacity
testID="viewHeaderBackOrMenuBtn"
onPress={onPressMenu}
@ -85,7 +93,7 @@ export function HeaderWithInput({
onBlur={() => setIsInputFocused(false)}
onChangeText={onChangeQuery}
onSubmitEditing={onSubmitQuery}
autoFocus={true}
autoFocus={isMobile}
accessibilityRole="search"
accessibilityLabel="Search"
accessibilityHint=""
@ -127,6 +135,11 @@ const styles = StyleSheet.create({
paddingHorizontal: 12,
paddingVertical: 4,
},
headerDesktop: {
borderWidth: 1,
borderTopWidth: 0,
paddingVertical: 10,
},
headerMenuBtn: {
width: 30,
height: 30,

View File

@ -13,13 +13,14 @@ import {
} from 'view/com/util/LoadingPlaceholder'
import {Text} from 'view/com/util/text/Text'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {s} from 'lib/styles'
import {isDesktopWeb} from 'platform/detection'
const SECTIONS = ['Posts', 'Users']
export const SearchResults = observer(({model}: {model: SearchUIModel}) => {
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
const renderTabBar = React.useCallback(
(props: RenderTabBarFnProps) => {
@ -39,10 +40,16 @@ export const SearchResults = observer(({model}: {model: SearchUIModel}) => {
return (
<Pager renderTabBar={renderTabBar} tabBarPosition="top" initialPage={0}>
<View style={[styles.results]}>
<View
style={{
paddingTop: isMobile ? 42 : 50,
}}>
<PostResults key="0" model={model} />
</View>
<View style={[styles.results]}>
<View
style={{
paddingTop: isMobile ? 42 : 50,
}}>
<Profiles key="1" model={model} />
</View>
</Pager>
@ -128,7 +135,4 @@ const styles = StyleSheet.create({
paddingHorizontal: 14,
paddingVertical: 16,
},
results: {
paddingTop: isDesktopWeb ? 50 : 42,
},
})

View File

@ -8,9 +8,9 @@ import {Text} from './text/Text'
import {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette'
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useAnalytics} from 'lib/analytics/analytics'
import {NavigationProp} from 'lib/routes/types'
import {isDesktopWeb} from 'platform/detection'
const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20}
@ -35,6 +35,7 @@ export const ViewHeader = observer(function ({
const store = useStores()
const navigation = useNavigation<NavigationProp>()
const {track} = useAnalytics()
const {isDesktop, isTablet} = useWebMediaQueries()
const onPressBack = React.useCallback(() => {
if (navigation.canGoBack()) {
@ -49,7 +50,7 @@ export const ViewHeader = observer(function ({
store.shell.openDrawer()
}, [track, store])
if (isDesktopWeb) {
if (isDesktop) {
if (showOnDesktop) {
return (
<DesktopWebHeader
@ -84,13 +85,13 @@ export const ViewHeader = observer(function ({
icon="angle-left"
style={[styles.backIcon, pal.text]}
/>
) : (
) : !isTablet ? (
<FontAwesomeIcon
size={18}
icon="bars"
style={[styles.backIcon, pal.textLight]}
/>
)}
) : null}
</TouchableOpacity>
) : null}
<View style={styles.titleContainer} pointerEvents="none">
@ -122,6 +123,7 @@ function DesktopWebHeader({
<CenteredView
style={[
styles.header,
styles.headerFixed,
styles.desktopHeader,
pal.border,
{
@ -178,6 +180,7 @@ const Container = observer(
<View
style={[
styles.header,
styles.headerFixed,
pal.view,
pal.border,
showBorder && styles.border,
@ -190,9 +193,9 @@ const Container = observer(
<Animated.View
style={[
styles.header,
styles.headerFloating,
pal.view,
pal.border,
styles.headerFloating,
transform,
showBorder && styles.border,
]}>
@ -208,6 +211,12 @@ const styles = StyleSheet.create({
alignItems: 'center',
paddingHorizontal: 12,
paddingVertical: 6,
width: '100%',
},
headerFixed: {
maxWidth: 600,
marginLeft: 'auto',
marginRight: 'auto',
},
headerFloating: {
position: 'absolute',

View File

@ -24,6 +24,7 @@ import {
} from 'react-native'
import {addStyle} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
interface AddedProps {
desktopFixedHeight?: boolean
@ -48,6 +49,7 @@ export const FlatList = React.forwardRef(function <ItemT>(
ref: React.Ref<RNFlatList>,
) {
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
contentContainerStyle = addStyle(
contentContainerStyle,
styles.containerScroll,
@ -67,6 +69,12 @@ export const FlatList = React.forwardRef(function <ItemT>(
}
if (desktopFixedHeight) {
style = addStyle(style, styles.fixedHeight)
if (!isMobile) {
contentContainerStyle = addStyle(
contentContainerStyle,
styles.stableGutters,
)
}
}
return (
<RNFlatList
@ -126,6 +134,9 @@ const styles = StyleSheet.create({
},
fixedHeight: {
height: '100vh',
},
stableGutters: {
// @ts-ignore web only -prf
scrollbarGutter: 'stable both-edges',
},
})

View File

@ -5,7 +5,8 @@ import LinearGradient from 'react-native-linear-gradient'
import {gradients} from 'lib/styles'
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
import {useStores} from 'state/index'
import {isMobileWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {isWeb} from 'platform/detection'
export interface FABProps
extends ComponentProps<typeof TouchableWithoutFeedback> {
@ -14,6 +15,7 @@ export interface FABProps
}
export const FABInner = observer(({testID, icon, ...props}: FABProps) => {
const {isTablet} = useWebMediaQueries()
const store = useStores()
const interp = useAnimatedValue(0)
React.useEffect(() => {
@ -24,18 +26,33 @@ export const FABInner = observer(({testID, icon, ...props}: FABProps) => {
isInteraction: false,
}).start()
}, [interp, store.shell.minimalShellMode])
const transform = {
transform: [{translateY: Animated.multiply(interp, 60)}],
}
const transform = isTablet
? undefined
: {
transform: [{translateY: Animated.multiply(interp, 60)}],
}
const size = isTablet ? styles.sizeLarge : styles.sizeRegular
return (
<TouchableWithoutFeedback testID={testID} {...props}>
<Animated.View
style={[styles.outer, isMobileWeb && styles.mobileWebOuter, transform]}>
style={[
styles.outer,
size,
isWeb && isTablet
? {
right: 50,
bottom: 50,
}
: {
bottom: 114,
},
transform,
]}>
<LinearGradient
colors={[gradients.blueLight.start, gradients.blueLight.end]}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={styles.inner}>
style={[styles.inner, size]}>
{icon}
</LinearGradient>
</Animated.View>
@ -44,22 +61,23 @@ export const FABInner = observer(({testID, icon, ...props}: FABProps) => {
})
const styles = StyleSheet.create({
sizeRegular: {
width: 60,
height: 60,
borderRadius: 30,
},
sizeLarge: {
width: 70,
height: 70,
borderRadius: 35,
},
outer: {
position: 'absolute',
zIndex: 1,
right: 24,
bottom: 94,
width: 60,
height: 60,
borderRadius: 30,
},
mobileWebOuter: {
bottom: 114,
},
inner: {
width: 60,
height: 60,
borderRadius: 30,
justifyContent: 'center',
alignItems: 'center',
},

View File

@ -2,7 +2,7 @@ import React from 'react'
import {Pressable, ViewStyle, StyleProp, StyleSheet} from 'react-native'
import {Text} from '../text/Text'
import {usePalette} from 'lib/hooks/usePalette'
import {isDesktopWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
interface SelectableBtnProps {
testID?: string
@ -28,12 +28,16 @@ export function SelectableBtn({
const pal = usePalette('default')
const palPrimary = usePalette('inverted')
const needsWidthStyles = !style || !('width' in style || 'flex' in style)
const {isMobile} = useWebMediaQueries()
return (
<Pressable
testID={testID}
style={[
styles.btn,
needsWidthStyles && styles.btnWidth,
needsWidthStyles && {
flex: isMobile ? 1 : undefined,
width: !isMobile ? 100 : undefined,
},
left && styles.btnLeft,
right && styles.btnRight,
pal.border,
@ -58,10 +62,6 @@ const styles = StyleSheet.create({
paddingHorizontal: 10,
paddingVertical: 10,
},
btnWidth: {
flex: isDesktopWeb ? undefined : 1,
width: isDesktopWeb ? 100 : undefined,
},
btnLeft: {
borderTopLeftRadius: 8,
borderBottomLeftRadius: 8,

View File

@ -2,18 +2,18 @@ import React from 'react'
import MediaQuery from 'react-responsive'
export const Desktop = ({children}: React.PropsWithChildren<{}>) => (
<MediaQuery minWidth={1224}>{children}</MediaQuery>
<MediaQuery minWidth={1300}>{children}</MediaQuery>
)
export const TabletOrDesktop = ({children}: React.PropsWithChildren<{}>) => (
<MediaQuery minWidth={800}>{children}</MediaQuery>
)
export const Tablet = ({children}: React.PropsWithChildren<{}>) => (
<MediaQuery minWidth={800} maxWidth={1224}>
<MediaQuery minWidth={800} maxWidth={1300}>
{children}
</MediaQuery>
)
export const TabletOrMobile = ({children}: React.PropsWithChildren<{}>) => (
<MediaQuery maxWidth={1224}>{children}</MediaQuery>
<MediaQuery maxWidth={1300}>{children}</MediaQuery>
)
export const Mobile = ({children}: React.PropsWithChildren<{}>) => (
<MediaQuery maxWidth={800}>{children}</MediaQuery>

View File

@ -3,8 +3,8 @@ import {StyleSheet, TouchableOpacity} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Text} from '../text/Text'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {LoadLatestBtn as LoadLatestBtnMobile} from './LoadLatestBtnMobile'
import {isMobileWeb} from 'platform/detection'
import {HITSLOP_20} from 'lib/constants'
export const LoadLatestBtn = ({
@ -19,7 +19,8 @@ export const LoadLatestBtn = ({
minimalShellMode?: boolean
}) => {
const pal = usePalette('default')
if (isMobileWeb) {
const {isMobile} = useWebMediaQueries()
if (isMobile) {
return (
<LoadLatestBtnMobile
onPress={onPress}

View File

@ -1,12 +1,12 @@
import React from 'react'
import {Pressable, StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {ModerationUI} from '@atproto/api'
import {Text} from '../text/Text'
import {ShieldExclamation} from 'lib/icons'
import {describeModerationCause} from 'lib/moderation'
import {useStores} from 'state/index'
import {isDesktopWeb} from 'platform/detection'
export function ContentHider({
testID,
@ -24,6 +24,7 @@ export function ContentHider({
}>) {
const store = useStores()
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
const [override, setOverride] = React.useState(false)
if (!moderation.blur || (ignoreMute && moderation.cause?.type === 'muted')) {
@ -54,6 +55,7 @@ export function ContentHider({
accessibilityLabel=""
style={[
styles.cover,
{paddingRight: isMobile ? 22 : 18},
moderation.noOverride
? {borderWidth: 1, borderColor: pal.colors.borderDark}
: pal.viewLight,
@ -96,7 +98,6 @@ const styles = StyleSheet.create({
marginTop: 4,
paddingVertical: 14,
paddingLeft: 14,
paddingRight: isDesktopWeb ? 18 : 22,
},
showBtn: {
marginLeft: 'auto',

View File

@ -2,13 +2,13 @@ import React, {ComponentProps} from 'react'
import {StyleSheet, Pressable, View} from 'react-native'
import {ModerationUI} from '@atproto/api'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {Link} from '../Link'
import {Text} from '../text/Text'
import {addStyle} from 'lib/styles'
import {describeModerationCause} from 'lib/moderation'
import {ShieldExclamation} from 'lib/icons'
import {useStores} from 'state/index'
import {isDesktopWeb} from 'platform/detection'
interface Props extends ComponentProps<typeof Link> {
// testID?: string
@ -27,6 +27,7 @@ export function PostHider({
}: Props) {
const store = useStores()
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
const [override, setOverride] = React.useState(false)
if (!moderation.blur) {
@ -55,7 +56,11 @@ export function PostHider({
accessibilityRole="button"
accessibilityHint={override ? 'Hide the content' : 'Show the content'}
accessibilityLabel=""
style={[styles.description, pal.viewLight]}>
style={[
styles.description,
{paddingRight: isMobile ? 22 : 18},
pal.viewLight,
]}>
<Pressable
onPress={() => {
store.shell.openModal({
@ -100,7 +105,6 @@ const styles = StyleSheet.create({
gap: 4,
paddingVertical: 14,
paddingLeft: 18,
paddingRight: isDesktopWeb ? 18 : 22,
marginTop: 1,
},
showBtn: {

View File

@ -13,10 +13,10 @@ import {
import {useNavigation} from '@react-navigation/native'
import {ModerationUI} from '@atproto/api'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {NavigationProp} from 'lib/routes/types'
import {Text} from '../text/Text'
import {Button} from '../forms/Button'
import {isDesktopWeb} from 'platform/detection'
import {describeModerationCause} from 'lib/moderation'
import {useStores} from 'state/index'
@ -39,6 +39,7 @@ export function ScreenHider({
const palInverted = usePalette('inverted')
const [override, setOverride] = React.useState(false)
const navigation = useNavigation<NavigationProp>()
const {isMobile} = useWebMediaQueries()
if (!moderation.blur || override) {
return (
@ -85,7 +86,7 @@ export function ScreenHider({
</Text>
</TouchableWithoutFeedback>
</Text>
{!isDesktopWeb && <View style={styles.spacer} />}
{isMobile && <View style={styles.spacer} />}
<View style={styles.btnContainer}>
<Button
type="inverted"

View File

@ -3,8 +3,8 @@ import {Image} from 'expo-image'
import {Text} from '../text/Text'
import {StyleSheet, View} from 'react-native'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {AppBskyEmbedExternal} from '@atproto/api'
import {isDesktopWeb} from 'platform/detection'
import {toNiceDomain} from 'lib/strings/url-helpers'
export const ExternalLinkEmbed = ({
@ -15,10 +15,31 @@ export const ExternalLinkEmbed = ({
imageChild?: React.ReactNode
}) => {
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
return (
<View style={styles.extContainer}>
<View
style={{
flexDirection: isMobile ? 'column' : 'row',
}}>
{link.thumb ? (
<View style={styles.extImageContainer}>
<View
style={
!isMobile
? {
borderTopLeftRadius: 6,
borderBottomLeftRadius: 6,
width: 120,
aspectRatio: 1,
overflow: 'hidden',
}
: {
borderTopLeftRadius: 6,
borderTopRightRadius: 6,
width: '100%',
height: 200,
overflow: 'hidden',
}
}>
<Image
style={styles.extImage}
source={{uri: link.thumb}}
@ -27,7 +48,13 @@ export const ExternalLinkEmbed = ({
{imageChild}
</View>
) : undefined}
<View style={styles.extInner}>
<View
style={{
paddingHorizontal: isMobile ? 10 : 14,
paddingTop: 8,
paddingBottom: 10,
flex: !isMobile ? 1 : undefined,
}}>
<Text
type="sm"
numberOfLines={1}
@ -36,14 +63,14 @@ export const ExternalLinkEmbed = ({
</Text>
<Text
type="lg-bold"
numberOfLines={isDesktopWeb ? 2 : 4}
numberOfLines={isMobile ? 4 : 2}
style={[pal.text]}>
{link.title || link.uri}
</Text>
{link.description ? (
<Text
type="md"
numberOfLines={isDesktopWeb ? 2 : 4}
numberOfLines={isMobile ? 4 : 2}
style={[pal.text, styles.extDescription]}>
{link.description}
</Text>
@ -54,30 +81,6 @@ export const ExternalLinkEmbed = ({
}
const styles = StyleSheet.create({
extContainer: {
flexDirection: isDesktopWeb ? 'row' : 'column',
},
extInner: {
paddingHorizontal: isDesktopWeb ? 14 : 10,
paddingTop: 8,
paddingBottom: 10,
flex: isDesktopWeb ? 1 : undefined,
},
extImageContainer: isDesktopWeb
? {
borderTopLeftRadius: 6,
borderBottomLeftRadius: 6,
width: 120,
aspectRatio: 1,
overflow: 'hidden',
}
: {
borderTopLeftRadius: 6,
borderTopRightRadius: 6,
width: '100%',
height: 200,
overflow: 'hidden',
},
extImage: {
width: '100%',
height: 200,

View File

@ -22,6 +22,7 @@ import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
import {ImagesLightbox} from 'state/models/ui/shell'
import {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {YoutubeEmbed} from './YoutubeEmbed'
import {ExternalLinkEmbed} from './ExternalLinkEmbed'
import {getYoutubeVideoId} from 'lib/strings/url-helpers'
@ -29,7 +30,6 @@ import {MaybeQuoteEmbed} from './QuoteEmbed'
import {AutoSizedImage} from '../images/AutoSizedImage'
import {CustomFeedEmbed} from './CustomFeedEmbed'
import {ListEmbed} from './ListEmbed'
import {isDesktopWeb} from 'platform/detection'
import {isCauseALabelOnUri} from 'lib/moderation'
type Embed =
@ -50,6 +50,7 @@ export function PostEmbeds({
}) {
const pal = usePalette('default')
const store = useStores()
const {isMobile} = useWebMediaQueries()
// quote post with media
// =
@ -111,7 +112,10 @@ export function PostEmbeds({
uri={thumb}
onPress={() => openLightbox(0)}
onPressIn={() => onPressIn(0)}
style={styles.singleImage}>
style={[
styles.singleImage,
isMobile && styles.singleImageMobile,
]}>
{alt === '' ? null : (
<View style={styles.altContainer}>
<Text style={styles.alt} accessible={false}>
@ -130,7 +134,11 @@ export function PostEmbeds({
images={embed.images}
onPress={openLightbox}
onPressIn={onPressIn}
style={embed.images.length === 1 ? styles.singleImage : undefined}
style={
embed.images.length === 1
? [styles.singleImage, isMobile && styles.singleImageMobile]
: undefined
}
/>
</View>
)
@ -169,7 +177,10 @@ const styles = StyleSheet.create({
},
singleImage: {
borderRadius: 8,
maxHeight: isDesktopWeb ? 1000 : 500,
maxHeight: 1000,
},
singleImageMobile: {
maxHeight: 500,
},
extOuter: {
borderWidth: 1,

View File

@ -7,7 +7,7 @@ import {Button} from '../com/util/forms/Button'
import * as Toast from '../com/util/Toast'
import {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette'
import {isDesktopWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {withAuthRequired} from 'view/com/auth/withAuthRequired'
import {observer} from 'mobx-react-lite'
import {NativeStackScreenProps} from '@react-navigation/native-stack'
@ -23,6 +23,7 @@ export const AppPasswords = withAuthRequired(
const pal = usePalette('default')
const store = useStores()
const {screen} = useAnalytics()
const {isTabletOrDesktop} = useWebMediaQueries()
useFocusEffect(
React.useCallback(() => {
@ -41,7 +42,7 @@ export const AppPasswords = withAuthRequired(
<CenteredView
style={[
styles.container,
isDesktopWeb && styles.containerDesktop,
isTabletOrDesktop && styles.containerDesktop,
pal.view,
pal.border,
]}
@ -53,11 +54,11 @@ export const AppPasswords = withAuthRequired(
pressing the button below.
</Text>
</View>
{!isDesktopWeb && <View style={styles.flex1} />}
{!isTabletOrDesktop && <View style={styles.flex1} />}
<View
style={[
styles.btnContainer,
isDesktopWeb && styles.btnContainerDesktop,
isTabletOrDesktop && styles.btnContainerDesktop,
]}>
<Button
testID="appPasswordBtn"
@ -77,7 +78,7 @@ export const AppPasswords = withAuthRequired(
<CenteredView
style={[
styles.container,
isDesktopWeb && styles.containerDesktop,
isTabletOrDesktop && styles.containerDesktop,
pal.view,
pal.border,
]}
@ -87,7 +88,7 @@ export const AppPasswords = withAuthRequired(
style={[
styles.scrollContainer,
pal.border,
!isDesktopWeb && styles.flex1,
!isTabletOrDesktop && styles.flex1,
]}>
{store.me.appPasswords.map((password, i) => (
<AppPassword
@ -97,7 +98,7 @@ export const AppPasswords = withAuthRequired(
createdAt={password.createdAt}
/>
))}
{isDesktopWeb && (
{isTabletOrDesktop && (
<View style={[styles.btnContainer, styles.btnContainerDesktop]}>
<Button
testID="appPasswordBtn"
@ -110,7 +111,7 @@ export const AppPasswords = withAuthRequired(
</View>
)}
</ScrollView>
{!isDesktopWeb && (
{!isTabletOrDesktop && (
<View style={styles.btnContainer}>
<Button
testID="appPasswordBtn"
@ -128,6 +129,7 @@ export const AppPasswords = withAuthRequired(
)
function AppPasswordsHeader() {
const {isTabletOrDesktop} = useWebMediaQueries()
const pal = usePalette('default')
return (
<>
@ -137,7 +139,7 @@ function AppPasswordsHeader() {
style={[
styles.description,
pal.text,
isDesktopWeb && styles.descriptionDesktop,
isTabletOrDesktop && styles.descriptionDesktop,
]}>
Use app passwords to login to other Bluesky clients without giving full
access to your account or password.
@ -207,11 +209,12 @@ function AppPassword({
const styles = StyleSheet.create({
container: {
flex: 1,
paddingBottom: isDesktopWeb ? 0 : 100,
paddingBottom: 100,
},
containerDesktop: {
borderLeftWidth: 1,
borderRightWidth: 1,
paddingBottom: 0,
},
title: {
textAlign: 'center',

View File

@ -22,7 +22,7 @@ import {ViewHeader} from 'view/com/util/ViewHeader'
import {Button} from 'view/com/util/forms/Button'
import {Text} from 'view/com/util/text/Text'
import * as Toast from 'view/com/util/Toast'
import {isDesktopWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useSetTitle} from 'lib/hooks/useSetTitle'
import {shareUrl} from 'lib/sharing'
import {toShareUrl} from 'lib/strings/url-helpers'
@ -122,6 +122,7 @@ export const CustomFeedScreenInner = observer(
({route, feedOwnerDid}: Props & {feedOwnerDid: string}) => {
const store = useStores()
const pal = usePalette('default')
const {isTabletOrDesktop} = useWebMediaQueries()
const {track} = useAnalytics()
const {rkey, name: handleOrDid} = route.params
const uri = useMemo(
@ -357,7 +358,7 @@ export const CustomFeedScreenInner = observer(
)}
</Text>
)}
{isDesktopWeb && (
{isTabletOrDesktop && (
<View style={[styles.headerBtns, styles.headerBtnsDesktop]}>
<Button
type={currentFeed?.isSaved ? 'default' : 'inverted'}
@ -452,7 +453,14 @@ export const CustomFeedScreenInner = observer(
) : null}
</View>
</View>
<View style={[styles.fakeSelector, pal.border]}>
<View
style={[
styles.fakeSelector,
{
paddingHorizontal: isTabletOrDesktop ? 16 : 6,
},
pal.border,
]}>
<View
style={[styles.fakeSelectorItem, {borderColor: pal.colors.link}]}>
<Text type="md-medium" style={[pal.text]}>
@ -474,6 +482,7 @@ export const CustomFeedScreenInner = observer(
rkey,
isPinned,
onTogglePinned,
isTabletOrDesktop,
])
const renderEmptyState = React.useCallback(() => {
@ -486,7 +495,9 @@ export const CustomFeedScreenInner = observer(
return (
<View style={s.hContentRegion}>
<ViewHeader title="" renderButton={currentFeed && renderHeaderBtns} />
{!isTabletOrDesktop && (
<ViewHeader title="" renderButton={currentFeed && renderHeaderBtns} />
)}
<Feed
scrollElRef={scrollElRef}
feed={algoFeed}
@ -495,6 +506,7 @@ export const CustomFeedScreenInner = observer(
ListHeaderComponent={renderListHeaderComponent}
renderEmptyState={renderEmptyState}
extraData={[uri, isPinned]}
style={!isTabletOrDesktop ? {flex: 1} : undefined}
/>
{isScrolledDown ? (
<LoadLatestBtn
@ -550,7 +562,6 @@ const styles = StyleSheet.create({
},
fakeSelector: {
flexDirection: 'row',
paddingHorizontal: isDesktopWeb ? 16 : 6,
},
fakeSelectorItem: {
paddingHorizontal: 12,

View File

@ -10,8 +10,8 @@ import {FeedsDiscoveryModel} from 'state/models/discovery/feeds'
import {CenteredView, FlatList} from 'view/com/util/Views'
import {CustomFeed} from 'view/com/feeds/CustomFeed'
import {Text} from 'view/com/util/text/Text'
import {isDesktopWeb} from 'platform/detection'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {s} from 'lib/styles'
import {CustomFeedModel} from 'state/models/feeds/custom-feed'
import {HeaderWithInput} from 'view/com/search/HeaderWithInput'
@ -23,6 +23,7 @@ export const DiscoverFeedsScreen = withAuthRequired(
const store = useStores()
const pal = usePalette('default')
const feeds = React.useMemo(() => new FeedsDiscoveryModel(store), [store])
const {isTabletOrDesktop} = useWebMediaQueries()
// search stuff
const [isInputFocused, setIsInputFocused] = React.useState<boolean>(false)
@ -74,7 +75,7 @@ export const DiscoverFeedsScreen = withAuthRequired(
<View style={styles.empty}>
<Text type="lg" style={pal.textLight}>
{feeds.isLoading
? isDesktopWeb
? isTabletOrDesktop
? 'Loading...'
: ''
: query
@ -100,23 +101,22 @@ export const DiscoverFeedsScreen = withAuthRequired(
return (
<CenteredView style={[styles.container, pal.view]}>
<View style={[isDesktopWeb && styles.containerDesktop, pal.border]}>
<View
style={[isTabletOrDesktop && styles.containerDesktop, pal.border]}>
<ViewHeader title="Discover Feeds" showOnDesktop />
<View style={{marginTop: isDesktopWeb ? 5 : 0, marginBottom: 4}}>
<HeaderWithInput
isInputFocused={isInputFocused}
query={query}
setIsInputFocused={setIsInputFocused}
onChangeQuery={onChangeQuery}
onPressClearQuery={onPressClearQuery}
onPressCancelSearch={onPressCancelSearch}
onSubmitQuery={onSubmitQuery}
showMenu={false}
/>
</View>
</View>
<HeaderWithInput
isInputFocused={isInputFocused}
query={query}
setIsInputFocused={setIsInputFocused}
onChangeQuery={onChangeQuery}
onPressClearQuery={onPressClearQuery}
onPressCancelSearch={onPressCancelSearch}
onSubmitQuery={onSubmitQuery}
showMenu={false}
/>
<FlatList
style={[!isDesktopWeb && s.flex1]}
style={[!isTabletOrDesktop && s.flex1]}
data={feeds.feeds}
keyExtractor={item => item.data.uri}
contentContainerStyle={styles.contentContainer}

View File

@ -12,22 +12,23 @@ import {NativeStackScreenProps, FeedsTabNavigatorParams} from 'lib/routes/types'
import {observer} from 'mobx-react-lite'
import {PostsMultiFeedModel} from 'state/models/feeds/multi-feed'
import {MultiFeed} from 'view/com/posts/MultiFeed'
import {isDesktopWeb} from 'platform/detection'
import {usePalette} from 'lib/hooks/usePalette'
import {useTimer} from 'lib/hooks/useTimer'
import {useStores} from 'state/index'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
import {ComposeIcon2, CogIcon} from 'lib/icons'
import {s} from 'lib/styles'
const LOAD_NEW_PROMPT_TIME = 60e3 // 60 seconds
const HEADER_OFFSET = isDesktopWeb ? 0 : 40
const MOBILE_HEADER_OFFSET = 40
type Props = NativeStackScreenProps<FeedsTabNavigatorParams, 'Feeds'>
export const FeedsScreen = withAuthRequired(
observer<Props>(({}: Props) => {
const pal = usePalette('default')
const store = useStores()
const {isMobile} = useWebMediaQueries()
const flatListRef = React.useRef<FlatList>(null)
const multifeed = React.useMemo<PostsMultiFeedModel>(
() => new PostsMultiFeedModel(store),
@ -105,14 +106,16 @@ export const FeedsScreen = withAuthRequired(
multifeed={multifeed}
onScroll={onMainScroll}
scrollEventThrottle={100}
headerOffset={HEADER_OFFSET}
/>
<ViewHeader
title="My Feeds"
canGoBack={false}
hideOnScroll
renderButton={renderHeaderBtn}
headerOffset={isMobile ? MOBILE_HEADER_OFFSET : undefined}
/>
{isMobile && (
<ViewHeader
title="My Feeds"
canGoBack={false}
hideOnScroll
renderButton={renderHeaderBtn}
/>
)}
{isScrolledDown || loadPromptVisible ? (
<LoadLatestBtn
onPress={onSoftReset}

View File

@ -19,14 +19,11 @@ import {useStores} from 'state/index'
import {s} from 'lib/styles'
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
import {useAnalytics} from 'lib/analytics/analytics'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {ComposeIcon2} from 'lib/icons'
import {isDesktopWeb, isMobileWebMediaQuery, isWeb} from 'platform/detection'
const HEADER_OFFSET_MOBILE = 78
const HEADER_OFFSET_DESKTOP = 50
const HEADER_OFFSET = isDesktopWeb
? HEADER_OFFSET_DESKTOP
: HEADER_OFFSET_MOBILE
const POLL_FREQ = 30e3 // 30sec
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
@ -158,10 +155,13 @@ const FeedPage = observer(
renderEmptyState?: () => JSX.Element
}) => {
const store = useStores()
const {isMobile} = useWebMediaQueries()
const [onMainScroll, isScrolledDown, resetMainScroll] =
useOnMainScroll(store)
const {screen, track} = useAnalytics()
const [headerOffset, setHeaderOffset] = React.useState(HEADER_OFFSET)
const [headerOffset, setHeaderOffset] = React.useState(
isMobile ? HEADER_OFFSET_MOBILE : HEADER_OFFSET_DESKTOP,
)
const scrollElRef = React.useRef<FlatList>(null)
const {appState} = useAppState({
onForeground: () => doPoll(true),
@ -206,15 +206,9 @@ const FeedPage = observer(
}, [isPageFocused, scrollToTop, feed])
// listens for resize events
const listenForResize = React.useCallback(() => {
// @ts-ignore we know window exists -prf
const isMobileWeb = global.window.matchMedia(
isMobileWebMediaQuery,
)?.matches
setHeaderOffset(
isMobileWeb ? HEADER_OFFSET_MOBILE : HEADER_OFFSET_DESKTOP,
)
}, [])
React.useEffect(() => {
setHeaderOffset(isMobile ? HEADER_OFFSET_MOBILE : HEADER_OFFSET_DESKTOP)
}, [isMobile])
// fires when page within screen is activated/deactivated
// - check for latest
@ -234,17 +228,10 @@ const FeedPage = observer(
feed.update()
}
if (isWeb) {
window.addEventListener('resize', listenForResize)
}
return () => {
clearInterval(pollInterval)
softResetSub.remove()
feedCleanup()
if (isWeb) {
isWeb && window.removeEventListener('resize', listenForResize)
}
}
}, [
store,
@ -254,7 +241,6 @@ const FeedPage = observer(
feed,
isPageFocused,
isScreenFocused,
listenForResize,
])
const onPressCompose = React.useCallback(() => {

View File

@ -16,7 +16,7 @@ import {Link} from '../com/util/Link'
import {Text} from '../com/util/text/Text'
import {usePalette} from 'lib/hooks/usePalette'
import {useAnalytics} from 'lib/analytics/analytics'
import {isDesktopWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Moderation'>
export const ModerationScreen = withAuthRequired(
@ -24,6 +24,7 @@ export const ModerationScreen = withAuthRequired(
const pal = usePalette('default')
const store = useStores()
const {screen, track} = useAnalytics()
const {isTabletOrDesktop} = useWebMediaQueries()
useFocusEffect(
React.useCallback(() => {
@ -42,7 +43,7 @@ export const ModerationScreen = withAuthRequired(
style={[
s.hContentRegion,
pal.border,
isDesktopWeb ? styles.desktopContainer : pal.viewLight,
isTabletOrDesktop ? styles.desktopContainer : pal.viewLight,
]}
testID="moderationScreen">
<ViewHeader title="Moderation" showOnDesktop />

View File

@ -10,7 +10,7 @@ import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
import {Text} from '../com/util/text/Text'
import {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette'
import {isDesktopWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {withAuthRequired} from 'view/com/auth/withAuthRequired'
import {observer} from 'mobx-react-lite'
import {NativeStackScreenProps} from '@react-navigation/native-stack'
@ -30,6 +30,7 @@ export const ModerationBlockedAccounts = withAuthRequired(
observer(({}: Props) => {
const pal = usePalette('default')
const store = useStores()
const {isTabletOrDesktop} = useWebMediaQueries()
const {screen} = useAnalytics()
const blockedAccounts = useMemo(
() => new BlockedAccountsModel(store),
@ -72,7 +73,7 @@ export const ModerationBlockedAccounts = withAuthRequired(
<CenteredView
style={[
styles.container,
isDesktopWeb && styles.containerDesktop,
isTabletOrDesktop && styles.containerDesktop,
pal.view,
pal.border,
]}
@ -83,14 +84,14 @@ export const ModerationBlockedAccounts = withAuthRequired(
style={[
styles.description,
pal.text,
isDesktopWeb && styles.descriptionDesktop,
isTabletOrDesktop && styles.descriptionDesktop,
]}>
Blocked accounts cannot reply in your threads, mention you, or
otherwise interact with you. You will not see their content and they
will be prevented from seeing yours.
</Text>
{!blockedAccounts.hasContent ? (
<View style={[pal.border, !isDesktopWeb && styles.flex1]}>
<View style={[pal.border, !isTabletOrDesktop && styles.flex1]}>
<View style={[styles.empty, pal.viewLight]}>
<Text type="lg" style={[pal.text, styles.emptyText]}>
You have not blocked any accounts yet. To block an account, go
@ -101,7 +102,7 @@ export const ModerationBlockedAccounts = withAuthRequired(
</View>
) : (
<FlatList
style={[!isDesktopWeb && styles.flex1]}
style={[!isTabletOrDesktop && styles.flex1]}
data={blockedAccounts.blocks}
keyExtractor={(item: ActorDefs.ProfileView) => item.did}
refreshControl={
@ -133,11 +134,12 @@ export const ModerationBlockedAccounts = withAuthRequired(
const styles = StyleSheet.create({
container: {
flex: 1,
paddingBottom: isDesktopWeb ? 0 : 100,
paddingBottom: 100,
},
containerDesktop: {
borderLeftWidth: 1,
borderRightWidth: 1,
paddingBottom: 0,
},
title: {
textAlign: 'center',

View File

@ -15,9 +15,9 @@ import {ListsList} from 'view/com/lists/ListsList'
import {Button} from 'view/com/util/forms/Button'
import {NavigationProp} from 'lib/routes/types'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {CenteredView} from 'view/com/util/Views'
import {ViewHeader} from 'view/com/util/ViewHeader'
import {isDesktopWeb} from 'platform/detection'
type Props = NativeStackScreenProps<
CommonNavigatorParams,
@ -26,6 +26,7 @@ type Props = NativeStackScreenProps<
export const ModerationMuteListsScreen = withAuthRequired(({}: Props) => {
const pal = usePalette('default')
const store = useStores()
const {isTabletOrDesktop} = useWebMediaQueries()
const navigation = useNavigation<NavigationProp>()
const mutelists: ListsListModel = React.useMemo(
@ -89,7 +90,7 @@ export const ModerationMuteListsScreen = withAuthRequired(({}: Props) => {
styles.container,
pal.view,
pal.border,
isDesktopWeb && styles.containerDesktop,
isTabletOrDesktop && styles.containerDesktop,
]}
testID="moderationMutelistsScreen">
<ViewHeader
@ -99,7 +100,7 @@ export const ModerationMuteListsScreen = withAuthRequired(({}: Props) => {
/>
<ListsList
listsList={mutelists}
showAddBtns={isDesktopWeb}
showAddBtns={isTabletOrDesktop}
renderEmptyState={renderEmptyState}
onPressCreateNew={onPressNewMuteList}
/>
@ -110,11 +111,12 @@ export const ModerationMuteListsScreen = withAuthRequired(({}: Props) => {
const styles = StyleSheet.create({
container: {
flex: 1,
paddingBottom: isDesktopWeb ? 0 : 100,
paddingBottom: 100,
},
containerDesktop: {
borderLeftWidth: 1,
borderRightWidth: 1,
paddingBottom: 0,
},
createBtn: {
width: 40,

View File

@ -10,7 +10,7 @@ import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
import {Text} from '../com/util/text/Text'
import {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette'
import {isDesktopWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {withAuthRequired} from 'view/com/auth/withAuthRequired'
import {observer} from 'mobx-react-lite'
import {NativeStackScreenProps} from '@react-navigation/native-stack'
@ -30,6 +30,7 @@ export const ModerationMutedAccounts = withAuthRequired(
observer(({}: Props) => {
const pal = usePalette('default')
const store = useStores()
const {isTabletOrDesktop} = useWebMediaQueries()
const {screen} = useAnalytics()
const mutedAccounts = useMemo(() => new MutedAccountsModel(store), [store])
@ -69,7 +70,7 @@ export const ModerationMutedAccounts = withAuthRequired(
<CenteredView
style={[
styles.container,
isDesktopWeb && styles.containerDesktop,
isTabletOrDesktop && styles.containerDesktop,
pal.view,
pal.border,
]}
@ -80,13 +81,13 @@ export const ModerationMutedAccounts = withAuthRequired(
style={[
styles.description,
pal.text,
isDesktopWeb && styles.descriptionDesktop,
isTabletOrDesktop && styles.descriptionDesktop,
]}>
Muted accounts have their posts removed from your feed and from your
notifications. Mutes are completely private.
</Text>
{!mutedAccounts.hasContent ? (
<View style={[pal.border, !isDesktopWeb && styles.flex1]}>
<View style={[pal.border, !isTabletOrDesktop && styles.flex1]}>
<View style={[styles.empty, pal.viewLight]}>
<Text type="lg" style={[pal.text, styles.emptyText]}>
You have not muted any accounts yet. To mute an account, go to
@ -97,7 +98,7 @@ export const ModerationMutedAccounts = withAuthRequired(
</View>
) : (
<FlatList
style={[!isDesktopWeb && styles.flex1]}
style={[!isTabletOrDesktop && styles.flex1]}
data={mutedAccounts.mutes}
keyExtractor={item => item.did}
refreshControl={
@ -129,11 +130,12 @@ export const ModerationMutedAccounts = withAuthRequired(
const styles = StyleSheet.create({
container: {
flex: 1,
paddingBottom: isDesktopWeb ? 0 : 100,
paddingBottom: 100,
},
containerDesktop: {
borderLeftWidth: 1,
borderRightWidth: 1,
paddingBottom: 0,
},
title: {
textAlign: 'center',

View File

@ -12,7 +12,7 @@ import {useStores} from 'state/index'
import {s} from 'lib/styles'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {clamp} from 'lodash'
import {isDesktopWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
const SHELL_FOOTER_HEIGHT = 44
@ -26,6 +26,7 @@ export const PostThreadScreen = withAuthRequired(({route}: Props) => {
() => new PostThreadModel(store, {uri}),
[store, uri],
)
const {isMobile} = useWebMediaQueries()
useFocusEffect(
React.useCallback(() => {
@ -67,15 +68,15 @@ export const PostThreadScreen = withAuthRequired(({route}: Props) => {
return (
<View style={s.hContentRegion}>
<ViewHeader title="Post" />
<View style={s.hContentRegion}>
{isMobile && <ViewHeader title="Post" />}
<View style={s.flex1}>
<PostThreadComponent
uri={uri}
view={view}
onPressReply={onPressReply}
/>
</View>
{!isDesktopWeb && (
{isMobile && (
<View
style={[
styles.prompt,

View File

@ -6,7 +6,8 @@ import {Text} from '../com/util/text/Text'
import {useStores} from 'state/index'
import {s, colors} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {isWeb, isDesktopWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {isWeb} from 'platform/detection'
import {ToggleButton} from 'view/com/util/forms/ToggleButton'
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
import {ViewHeader} from 'view/com/util/ViewHeader'
@ -50,6 +51,7 @@ type Props = NativeStackScreenProps<
export const PreferencesHomeFeed = observer(({navigation}: Props) => {
const pal = usePalette('default')
const store = useStores()
const {isTabletOrDesktop} = useWebMediaQueries()
return (
<CenteredView
@ -58,10 +60,11 @@ export const PreferencesHomeFeed = observer(({navigation}: Props) => {
pal.view,
pal.border,
styles.container,
isDesktopWeb && styles.desktopContainer,
isTabletOrDesktop && styles.desktopContainer,
]}>
<ViewHeader title="Home Feed Preferences" showOnDesktop />
<View style={styles.titleSection}>
<View
style={[styles.titleSection, isTabletOrDesktop && {paddingTop: 20}]}>
<Text type="xl" style={[pal.textLight, styles.description]}>
Fine-tune the content you see on your home screen.
</Text>
@ -122,7 +125,12 @@ export const PreferencesHomeFeed = observer(({navigation}: Props) => {
</View>
</ScrollView>
<View style={[styles.btnContainer, pal.borderDark]}>
<View
style={[
styles.btnContainer,
!isTabletOrDesktop && {borderTopWidth: 1, paddingHorizontal: 20},
pal.borderDark,
]}>
<TouchableOpacity
testID="confirmBtn"
onPress={() => {
@ -130,7 +138,7 @@ export const PreferencesHomeFeed = observer(({navigation}: Props) => {
? navigation.goBack()
: navigation.navigate('Settings')
}}
style={[styles.btn, isDesktopWeb && styles.btnDesktop]}
style={[styles.btn, isTabletOrDesktop && styles.btnDesktop]}
accessibilityRole="button"
accessibilityLabel="Confirm"
accessibilityHint="">
@ -144,15 +152,15 @@ export const PreferencesHomeFeed = observer(({navigation}: Props) => {
const styles = StyleSheet.create({
container: {
flex: 1,
paddingBottom: isDesktopWeb ? 40 : 90,
paddingBottom: 90,
},
desktopContainer: {
borderLeftWidth: 1,
borderRightWidth: 1,
paddingBottom: 40,
},
titleSection: {
paddingBottom: 30,
paddingTop: isDesktopWeb ? 20 : 0,
},
title: {
textAlign: 'center',
@ -184,7 +192,6 @@ const styles = StyleSheet.create({
},
btnContainer: {
paddingTop: 20,
borderTopWidth: isDesktopWeb ? 0 : 1,
},
dimmed: {
opacity: 0.3,

View File

@ -14,8 +14,8 @@ import {ListModel} from 'state/models/content/list'
import {useStores} from 'state/index'
import {usePalette} from 'lib/hooks/usePalette'
import {useSetTitle} from 'lib/hooks/useSetTitle'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {NavigationProp} from 'lib/routes/types'
import {isDesktopWeb} from 'platform/detection'
import {toShareUrl} from 'lib/strings/url-helpers'
import {shareUrl} from 'lib/sharing'
import {ListActions} from 'view/com/lists/ListActions'
@ -26,6 +26,7 @@ export const ProfileListScreen = withAuthRequired(
observer(({route}: Props) => {
const store = useStores()
const navigation = useNavigation<NavigationProp>()
const {isTabletOrDesktop} = useWebMediaQueries()
const pal = usePalette('default')
const {name, rkey} = route.params
@ -131,7 +132,7 @@ export const ProfileListScreen = withAuthRequired(
<CenteredView
style={[
styles.container,
isDesktopWeb && styles.containerDesktop,
isTabletOrDesktop && styles.containerDesktop,
pal.view,
pal.border,
]}
@ -155,10 +156,11 @@ export const ProfileListScreen = withAuthRequired(
const styles = StyleSheet.create({
container: {
flex: 1,
paddingBottom: isDesktopWeb ? 0 : 100,
paddingBottom: 100,
},
containerDesktop: {
borderLeftWidth: 1,
borderRightWidth: 1,
paddingBottom: 0,
},
})

View File

@ -14,11 +14,12 @@ import {usePalette} from 'lib/hooks/usePalette'
import {CommonNavigatorParams} from 'lib/routes/types'
import {observer} from 'mobx-react-lite'
import {useStores} from 'state/index'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {withAuthRequired} from 'view/com/auth/withAuthRequired'
import {ViewHeader} from 'view/com/util/ViewHeader'
import {CenteredView} from 'view/com/util/Views'
import {Text} from 'view/com/util/text/Text'
import {isDesktopWeb, isWeb} from 'platform/detection'
import {isWeb} from 'platform/detection'
import {s, colors} from 'lib/styles'
import DraggableFlatList, {
ShadowDecorator,
@ -37,6 +38,7 @@ export const SavedFeeds = withAuthRequired(
observer(({}: Props) => {
const pal = usePalette('default')
const store = useStores()
const {isMobile, isTabletOrDesktop} = useWebMediaQueries()
const {screen} = useAnalytics()
const savedFeeds = useMemo(() => store.me.savedFeeds, [store])
@ -53,7 +55,7 @@ export const SavedFeeds = withAuthRequired(
<View
style={[
pal.border,
!isDesktopWeb && s.flex1,
isMobile && s.flex1,
pal.viewLight,
styles.empty,
]}>
@ -62,7 +64,7 @@ export const SavedFeeds = withAuthRequired(
</Text>
</View>
)
}, [pal])
}, [pal, isMobile])
const renderListFooterComponent = useCallback(() => {
return (
@ -116,15 +118,11 @@ export const SavedFeeds = withAuthRequired(
style={[
s.hContentRegion,
pal.border,
isDesktopWeb && styles.desktopContainer,
isTabletOrDesktop && styles.desktopContainer,
]}>
<ViewHeader
title="Edit My Feeds"
showOnDesktop
showBorder={!isDesktopWeb}
/>
<ViewHeader title="Edit My Feeds" showOnDesktop showBorder />
<DraggableFlatList
containerStyle={[isDesktopWeb ? s.hContentRegion : s.flex1]}
containerStyle={[isTabletOrDesktop ? s.hContentRegion : s.flex1]}
data={savedFeeds.all}
keyExtractor={item => item.data.uri}
refreshing={savedFeeds.isRefreshing}

View File

@ -12,6 +12,7 @@ import {
SearchTabNavigatorParams,
} from 'lib/routes/types'
import {useStores} from 'state/index'
import {CenteredView} from 'view/com/util/Views'
import * as Mobile from './SearchMobile'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
@ -57,9 +58,9 @@ export const SearchScreen = withAuthRequired(
if (!isDesktop) {
return (
<View style={styles.scrollContainer}>
<CenteredView style={styles.scrollContainer}>
<Mobile.SearchScreen navigation={navigation} route={route} />
</View>
</CenteredView>
)
}

View File

@ -35,10 +35,10 @@ import {ToggleButton} from 'view/com/util/forms/ToggleButton'
import {SelectableBtn} from 'view/com/util/forms/SelectableBtn'
import {usePalette} from 'lib/hooks/usePalette'
import {useCustomPalette} from 'lib/hooks/useCustomPalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {AccountData} from 'state/models/session'
import {useAnalytics} from 'lib/analytics/analytics'
import {NavigationProp} from 'lib/routes/types'
import {isDesktopWeb} from 'platform/detection'
import {pluralize} from 'lib/strings/helpers'
import {formatCount} from 'view/com/util/numeric/format'
import Clipboard from '@react-native-clipboard/clipboard'
@ -58,6 +58,7 @@ export const SettingsScreen = withAuthRequired(
const pal = usePalette('default')
const store = useStores()
const navigation = useNavigation<NavigationProp>()
const {isMobile} = useWebMediaQueries()
const {screen, track} = useAnalytics()
const [isSwitching, setIsSwitching] = React.useState(false)
const [debugHeaderEnabled, toggleDebugHeader] = useDebugHeaderSetting(
@ -203,7 +204,7 @@ export const SettingsScreen = withAuthRequired(
<ViewHeader title="Settings" />
<ScrollView
style={[s.hContentRegion]}
contentContainerStyle={!isDesktopWeb && pal.viewLight}
contentContainerStyle={isMobile && pal.viewLight}
scrollIndicatorInsets={{right: 1}}>
<View style={styles.spacer20} />
{store.session.currentSession !== undefined ? (
@ -508,7 +509,7 @@ export const SettingsScreen = withAuthRequired(
System log
</Text>
</TouchableOpacity>
{isDesktopWeb || __DEV__ ? (
{__DEV__ ? (
<ToggleButton
type="default-light"
label="Experiment: Use AppView Proxy"

View File

@ -4,7 +4,7 @@ import {StyleSheet, View} from 'react-native'
import {ComposePost} from '../com/composer/Composer'
import {ComposerOpts} from 'state/models/ui/shell'
import {usePalette} from 'lib/hooks/usePalette'
import {isMobileWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
const BOTTOM_BAR_HEIGHT = 61
@ -26,6 +26,7 @@ export const Composer = observer(
mention?: ComposerOpts['mention']
}) => {
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
// rendering
// =
@ -36,7 +37,13 @@ export const Composer = observer(
return (
<View style={styles.mask} aria-modal accessibilityViewIsModal>
<View style={[styles.container, pal.view, pal.border]}>
<View
style={[
styles.container,
isMobile && styles.containerMobile,
pal.view,
pal.border,
]}>
<ComposePost
replyTo={replyTo}
quote={quote}
@ -66,11 +73,14 @@ const styles = StyleSheet.create({
width: '100%',
paddingVertical: 0,
paddingHorizontal: 2,
borderRadius: isMobileWeb ? 0 : 8,
marginBottom: isMobileWeb ? BOTTOM_BAR_HEIGHT : 0,
borderRadius: 8,
marginBottom: 0,
borderWidth: 1,
maxHeight: isMobileWeb
? `calc(100% - ${BOTTOM_BAR_HEIGHT}px)`
: 'calc(100% - (40px * 2))',
maxHeight: 'calc(100% - (40px * 2))',
},
containerMobile: {
borderRadius: 0,
marginBottom: BOTTOM_BAR_HEIGHT,
maxHeight: `calc(100% - ${BOTTOM_BAR_HEIGHT}px)`,
},
})

View File

@ -17,6 +17,7 @@ import {Link} from 'view/com/util/Link'
import {LoadingPlaceholder} from 'view/com/util/LoadingPlaceholder'
import {usePalette} from 'lib/hooks/usePalette'
import {useStores} from 'state/index'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {s, colors} from 'lib/styles'
import {
HomeIcon,
@ -41,18 +42,28 @@ import {makeProfileLink} from 'lib/routes/links'
const ProfileCard = observer(() => {
const store = useStores()
const {isDesktop} = useWebMediaQueries()
const size = isDesktop ? 64 : 48
return store.me.handle ? (
<Link href={makeProfileLink(store.me)} style={styles.profileCard} asAnchor>
<UserAvatar avatar={store.me.avatar} size={64} />
<Link
href={makeProfileLink(store.me)}
style={[styles.profileCard, !isDesktop && styles.profileCardTablet]}
asAnchor>
<UserAvatar avatar={store.me.avatar} size={size} />
</Link>
) : (
<View style={styles.profileCard}>
<LoadingPlaceholder width={64} height={64} style={{borderRadius: 64}} />
<View style={[styles.profileCard, !isDesktop && styles.profileCardTablet]}>
<LoadingPlaceholder
width={size}
height={size}
style={{borderRadius: size}}
/>
</View>
)
})
function BackBtn() {
const {isTablet} = useWebMediaQueries()
const pal = usePalette('default')
const navigation = useNavigation<NavigationProp>()
const shouldShow = useNavigationState(state => !isStateAtTabRoot(state))
@ -65,7 +76,7 @@ function BackBtn() {
}
}, [navigation])
if (!shouldShow) {
if (!shouldShow || isTablet) {
return <></>
}
return (
@ -96,6 +107,7 @@ const NavItem = observer(
({count, href, icon, iconFilled, label}: NavItemProps) => {
const pal = usePalette('default')
const store = useStores()
const {isDesktop, isTablet} = useWebMediaQueries()
const [pathName] = React.useMemo(() => router.matchPath(href), [href])
const currentRouteInfo = useNavigationState(state => {
if (!state) {
@ -137,17 +149,28 @@ const NavItem = observer(
accessibilityRole="tab"
accessibilityLabel={label}
accessibilityHint="">
<View style={[styles.navItemIconWrapper]}>
<View
style={[
styles.navItemIconWrapper,
isTablet && styles.navItemIconWrapperTablet,
]}>
{isCurrent ? iconFilled : icon}
{typeof count === 'string' && count ? (
<Text type="button" style={styles.navItemCount}>
<Text
type="button"
style={[
styles.navItemCount,
isTablet && styles.navItemCountTablet,
]}>
{count}
</Text>
) : null}
</View>
<Text type="title" style={[isCurrent ? s.bold : s.normal, pal.text]}>
{label}
</Text>
{isDesktop && (
<Text type="title" style={[isCurrent ? s.bold : s.normal, pal.text]}>
{label}
</Text>
)}
</PressableWithHover>
)
},
@ -156,6 +179,7 @@ const NavItem = observer(
function ComposeBtn() {
const store = useStores()
const {getState} = useNavigation()
const {isTablet} = useWebMediaQueries()
const getProfileHandle = () => {
const {routes} = getState()
@ -172,6 +196,9 @@ function ComposeBtn() {
const onPressCompose = () =>
store.shell.openComposer({mention: getProfileHandle()})
if (isTablet) {
return null
}
return (
<TouchableOpacity
style={[styles.newPostBtn]}
@ -196,28 +223,43 @@ function ComposeBtn() {
export const DesktopLeftNav = observer(function DesktopLeftNav() {
const store = useStores()
const pal = usePalette('default')
const {isDesktop, isTablet} = useWebMediaQueries()
return (
<View style={[styles.leftNav, pal.view]}>
<View
style={[
styles.leftNav,
isTablet && styles.leftNavTablet,
pal.view,
pal.border,
]}>
{store.session.hasSession && <ProfileCard />}
<BackBtn />
<NavItem
href="/"
icon={<HomeIcon size={24} style={pal.text} />}
icon={<HomeIcon size={isDesktop ? 24 : 28} style={pal.text} />}
iconFilled={
<HomeIconSolid strokeWidth={4} size={24} style={pal.text} />
<HomeIconSolid
strokeWidth={4}
size={isDesktop ? 24 : 28}
style={pal.text}
/>
}
label="Home"
/>
<NavItem
href="/search"
icon={
<MagnifyingGlassIcon2 strokeWidth={2} size={24} style={pal.text} />
<MagnifyingGlassIcon2
strokeWidth={2}
size={isDesktop ? 24 : 26}
style={pal.text}
/>
}
iconFilled={
<MagnifyingGlassIcon2Solid
strokeWidth={2}
size={24}
size={isDesktop ? 24 : 26}
style={pal.text}
/>
}
@ -229,14 +271,14 @@ export const DesktopLeftNav = observer(function DesktopLeftNav() {
<SatelliteDishIcon
strokeWidth={1.75}
style={pal.text as FontAwesomeIconStyle}
size={24}
size={isDesktop ? 24 : 28}
/>
}
iconFilled={
<SatelliteDishIconSolid
strokeWidth={1.75}
style={pal.text as FontAwesomeIconStyle}
size={24}
size={isDesktop ? 24 : 28}
/>
}
label="My Feeds"
@ -244,9 +286,19 @@ export const DesktopLeftNav = observer(function DesktopLeftNav() {
<NavItem
href="/notifications"
count={store.me.notifications.unreadCountLabel}
icon={<BellIcon strokeWidth={2} size={24} style={pal.text} />}
icon={
<BellIcon
strokeWidth={2}
size={isDesktop ? 24 : 26}
style={pal.text}
/>
}
iconFilled={
<BellIconSolid strokeWidth={1.5} size={24} style={pal.text} />
<BellIconSolid
strokeWidth={1.5}
size={isDesktop ? 24 : 26}
style={pal.text}
/>
}
label="Notifications"
/>
@ -256,14 +308,14 @@ export const DesktopLeftNav = observer(function DesktopLeftNav() {
<HandIcon
strokeWidth={5.5}
style={pal.text as FontAwesomeIconStyle}
size={24}
size={isDesktop ? 24 : 27}
/>
}
iconFilled={
<FontAwesomeIcon
icon="hand"
style={pal.text as FontAwesomeIconStyle}
size={20}
size={isDesktop ? 20 : 26}
/>
}
label="Moderation"
@ -271,18 +323,38 @@ export const DesktopLeftNav = observer(function DesktopLeftNav() {
{store.session.hasSession && (
<NavItem
href={makeProfileLink(store.me)}
icon={<UserIcon strokeWidth={1.75} size={28} style={pal.text} />}
icon={
<UserIcon
strokeWidth={1.75}
size={isDesktop ? 28 : 30}
style={pal.text}
/>
}
iconFilled={
<UserIconSolid strokeWidth={1.75} size={28} style={pal.text} />
<UserIconSolid
strokeWidth={1.75}
size={isDesktop ? 28 : 30}
style={pal.text}
/>
}
label="Profile"
/>
)}
<NavItem
href="/settings"
icon={<CogIcon strokeWidth={1.75} size={28} style={pal.text} />}
icon={
<CogIcon
strokeWidth={1.75}
size={isDesktop ? 28 : 32}
style={pal.text}
/>
}
iconFilled={
<CogIconSolid strokeWidth={1.5} size={28} style={pal.text} />
<CogIconSolid
strokeWidth={1.5}
size={isDesktop ? 28 : 32}
style={pal.text}
/>
}
label="Settings"
/>
@ -300,12 +372,24 @@ const styles = StyleSheet.create({
maxHeight: 'calc(100vh - 10px)',
overflowY: 'auto',
},
leftNavTablet: {
top: 0,
left: 0,
right: 'auto',
borderRightWidth: 1,
height: '100%',
width: 76,
alignItems: 'center',
},
profileCard: {
marginVertical: 10,
width: 90,
paddingLeft: 12,
},
profileCardTablet: {
width: 70,
},
backBtn: {
position: 'absolute',
@ -330,6 +414,10 @@ const styles = StyleSheet.create({
height: 28,
marginTop: 2,
},
navItemIconWrapperTablet: {
width: 40,
height: 40,
},
navItemCount: {
position: 'absolute',
top: 0,
@ -341,6 +429,10 @@ const styles = StyleSheet.create({
paddingHorizontal: 4,
borderRadius: 6,
},
navItemCountTablet: {
left: 18,
fontSize: 14,
},
newPostBtn: {
flexDirection: 'row',
@ -354,10 +446,9 @@ const styles = StyleSheet.create({
marginLeft: 12,
marginTop: 20,
marginBottom: 10,
gap: 8,
},
newPostBtnIconWrapper: {
marginRight: 8,
},
newPostBtnIconWrapper: {},
newPostBtnLabel: {
color: colors.white,
fontSize: 16,

View File

@ -9,6 +9,7 @@ import {TextLink} from 'view/com/util/Link'
import {FEEDBACK_FORM_URL, HELP_DESK_URL} from 'lib/constants'
import {s} from 'lib/styles'
import {useStores} from 'state/index'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {pluralize} from 'lib/strings/helpers'
import {formatCount} from 'view/com/util/numeric/format'
@ -17,6 +18,11 @@ export const DesktopRightNav = observer(function DesktopRightNav() {
const pal = usePalette('default')
const palError = usePalette('error')
const {isTablet} = useWebMediaQueries()
if (isTablet) {
return null
}
return (
<View style={[styles.rightNav, pal.view]}>
{store.session.hasSession && <DesktopSearch />}

View File

@ -19,7 +19,7 @@ import {NavigationProp} from 'lib/routes/types'
const ShellInner = observer(() => {
const store = useStores()
const {isDesktop} = useWebMediaQueries()
const {isDesktop, isMobile} = useWebMediaQueries()
const navigator = useNavigation<NavigationProp>()
useEffect(() => {
@ -28,11 +28,11 @@ const ShellInner = observer(() => {
})
}, [navigator, store.shell])
const showBottomBar = !isDesktop && !store.onboarding.isActive
const showBottomBar = isMobile && !store.onboarding.isActive
const showSideNavs =
isDesktop && store.session.hasSession && !store.onboarding.isActive
!isMobile && store.session.hasSession && !store.onboarding.isActive
return (
<>
<View style={[s.hContentRegion, {overflow: 'hidden'}]}>
<View style={s.hContentRegion}>
<ErrorBoundary>
<FlatNavigator />
@ -67,7 +67,7 @@ const ShellInner = observer(() => {
</View>
</TouchableOpacity>
)}
</>
</View>
)
})