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' import {isNative} from 'platform/detection'
export function useWebMediaQueries() { export function useWebMediaQueries() {
const isDesktop = useMediaQuery({ const isDesktop = useMediaQuery({minWidth: 1300})
query: '(min-width: 1224px)', const isTablet = useMediaQuery({minWidth: 800, maxWidth: 1300})
}) const isMobile = useMediaQuery({maxWidth: 800})
const isTabletOrMobile = useMediaQuery({query: '(max-width: 1224px)'}) const isTabletOrMobile = isMobile || isTablet
const isMobile = useMediaQuery({query: '(max-width: 800px)'}) const isTabletOrDesktop = isDesktop || isTablet
if (isNative) { 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 isNative = isIOS || isAndroid
export const devicePlatform = isIOS ? 'ios' : isAndroid ? 'android' : 'web' export const devicePlatform = isIOS ? 'ios' : isAndroid ? 'android' : 'web'
export const isWeb = !isNative 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 = export const isMobileWeb =
isWeb && isWeb &&
// @ts-ignore we know window exists -prf // @ts-ignore we know window exists -prf

View File

@ -111,7 +111,8 @@ export class PostsMultiFeedModel {
uri: makeProfileLink(feedInfo.data.creator, 'feed', urip.rkey), 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'}) items.push({_reactKey: '__footer__', type: 'footer'})
} }
return items return items

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,18 +2,18 @@ import React from 'react'
import MediaQuery from 'react-responsive' import MediaQuery from 'react-responsive'
export const Desktop = ({children}: React.PropsWithChildren<{}>) => ( export const Desktop = ({children}: React.PropsWithChildren<{}>) => (
<MediaQuery minWidth={1224}>{children}</MediaQuery> <MediaQuery minWidth={1300}>{children}</MediaQuery>
) )
export const TabletOrDesktop = ({children}: React.PropsWithChildren<{}>) => ( export const TabletOrDesktop = ({children}: React.PropsWithChildren<{}>) => (
<MediaQuery minWidth={800}>{children}</MediaQuery> <MediaQuery minWidth={800}>{children}</MediaQuery>
) )
export const Tablet = ({children}: React.PropsWithChildren<{}>) => ( export const Tablet = ({children}: React.PropsWithChildren<{}>) => (
<MediaQuery minWidth={800} maxWidth={1224}> <MediaQuery minWidth={800} maxWidth={1300}>
{children} {children}
</MediaQuery> </MediaQuery>
) )
export const TabletOrMobile = ({children}: React.PropsWithChildren<{}>) => ( export const TabletOrMobile = ({children}: React.PropsWithChildren<{}>) => (
<MediaQuery maxWidth={1224}>{children}</MediaQuery> <MediaQuery maxWidth={1300}>{children}</MediaQuery>
) )
export const Mobile = ({children}: React.PropsWithChildren<{}>) => ( export const Mobile = ({children}: React.PropsWithChildren<{}>) => (
<MediaQuery maxWidth={800}>{children}</MediaQuery> <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 {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Text} from '../text/Text' import {Text} from '../text/Text'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {LoadLatestBtn as LoadLatestBtnMobile} from './LoadLatestBtnMobile' import {LoadLatestBtn as LoadLatestBtnMobile} from './LoadLatestBtnMobile'
import {isMobileWeb} from 'platform/detection'
import {HITSLOP_20} from 'lib/constants' import {HITSLOP_20} from 'lib/constants'
export const LoadLatestBtn = ({ export const LoadLatestBtn = ({
@ -19,7 +19,8 @@ export const LoadLatestBtn = ({
minimalShellMode?: boolean minimalShellMode?: boolean
}) => { }) => {
const pal = usePalette('default') const pal = usePalette('default')
if (isMobileWeb) { const {isMobile} = useWebMediaQueries()
if (isMobile) {
return ( return (
<LoadLatestBtnMobile <LoadLatestBtnMobile
onPress={onPress} onPress={onPress}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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