[Layout] Base (#6907)
* Add common gutter styles as hook * Add computed scrollbar gutter CSS vars * Add new layout components * Replace layout components in settings screens * Remove old back button * Invert web border logic for easier migration * Clean up Slot API * Port over FF handling of scrollbar offset * Trade boilerplate for ease of use * Limit to one line * Allow two lines, fix wrapping * Fix alignment * sticky headers * set max with on header and center * [Layout] Notifications Header (#6910) * Replace notifications screen header * fix cropped indicator --------- Co-authored-by: Samuel Newman <mozzius@protonmail.com> * Replace Hashtag header (#6928) * [Layout] ChatList header (#6929) * Replace ChatList header * update chat settings as well --------- Co-authored-by: Samuel Newman <mozzius@protonmail.com> * Add web borders to Chat settings * Remove unused var * Move ChatList header outside center * Replace empty chat layout * fix breakpoints * [Layout] Scrollbar gutters (#6908) * Fix sidebar alignment * Make sure scrollbars don't hide * Gift left nav more space * Use stable one-edge, update logic in RightNav * Ope * Increase width * Reset * Add transform to sidebars * Remove bg in sidebars * Handle shifts in layout components * Replace scroll-removal handling * Make react-remove-scroll an explicit dep * Remove unused script * use correct scroll insets (#6950) * [Layout] Feeds headers (#6913) * Replace ViewHeader internals, duplicate old ViewHeader * Replace Feeds header * Replace SavedFeeds header * Visual alignment * Uglier but clear * Use old ViewHeader for SavedFeeds * use Layout.Center instead of Layout.Content * use left-aligned header for feed edit * delete unused old view header --------- Co-authored-by: Samuel Newman <mozzius@protonmail.com> * [Layout] Every other screen (#6953) * attempt to fix double borders on every other screen * delete ListHeaderDesktop * delete `SimpleViewHeader` and fix screens (#6956) * Make Layout.Center not full height * Refactor List to use Layout.Center, remove built-in borders * Fix Home screen * Refactor PagerWithHeader to use Layout components * Replace components in ProfileFeed and ProfileList * Borders on Profile * Search screen replacements * use new header for profile subpage header (#6958) * Search AutocompleteResults * use new header for starter pack wizard (#6957) * Fix post thread * Enable borders by default * Moderation muted and blocked accounts * Fix scrollbar offset on Labeler * Remove ScrollView from Moderation * Remove ScrollView from Deactivated * Remove ScrollView from onboarding * Remove ScrollView from SignupQueued * Mark deprecations * fix lint * Fix double borders on profile load * Remove unneeded CenteredView from noty Feed * Remove double Center layout on Notifications screen * Remove double Center layout on ChatList screen * Handle scrollbar offset in chat * Use new atom for other scrollbar offsets * Remove borders from old views * Better doc * Remove temp migration prop * Fix new atom usage on native * Clean up Hashtag screen * Layout docs * Clarify usage in Pager * Handle nested offset contexts * Clean up Layout * fix feeds page * asymmetric header on native (#6969) * Reusable header const * Fix up home header * Add back button to convo * Add hitslop to header buttons * Comment * Better handling on native for new atom * Format * Fix nested flatlist on mod screens * Use react-remove-scroll-bar directly * Fix notification count overflow on web * Clarify doc --------- Co-authored-by: Samuel Newman <mozzius@protonmail.com>
This commit is contained in:
@@ -108,7 +108,7 @@ export function FeedPage({
|
||||
}, [scrollToTop, feed, queryClient, setHasNew])
|
||||
|
||||
return (
|
||||
<View testID={testID} style={s.h100pct}>
|
||||
<View testID={testID}>
|
||||
<MainScrollProvider>
|
||||
<FeedFeedbackProvider value={feedFeedback}>
|
||||
<Feed
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
import React from 'react'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
import {View} from 'react-native'
|
||||
import Animated from 'react-native-reanimated'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useMinimalShellHeaderTransform} from '#/lib/hooks/useMinimalShellTransform'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {useKawaiiMode} from '#/state/preferences/kawaii'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||
import {HomeHeaderLayoutMobile} from '#/view/com/home/HomeHeaderLayoutMobile'
|
||||
import {Logo} from '#/view/icons/Logo'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {atoms as a, useBreakpoints, useGutterStyles, useTheme} from '#/alf'
|
||||
import {ButtonIcon} from '#/components/Button'
|
||||
import {Hashtag_Stroke2_Corner0_Rounded as FeedsIcon} from '#/components/icons/Hashtag'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Link} from '#/components/Link'
|
||||
import {HomeHeaderLayoutMobile} from './HomeHeaderLayoutMobile'
|
||||
|
||||
export function HomeHeaderLayout(props: {
|
||||
children: React.ReactNode
|
||||
tabBarAnchor: JSX.Element | null | undefined
|
||||
}) {
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
if (isMobile) {
|
||||
const {gtMobile} = useBreakpoints()
|
||||
if (!gtMobile) {
|
||||
return <HomeHeaderLayoutMobile {...props} />
|
||||
} else {
|
||||
return <HomeHeaderLayoutDesktopAndTablet {...props} />
|
||||
@@ -40,98 +41,43 @@ function HomeHeaderLayoutDesktopAndTablet({
|
||||
const {hasSession} = useSession()
|
||||
const {_} = useLingui()
|
||||
const kawaii = useKawaiiMode()
|
||||
const gutter = useGutterStyles()
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasSession && (
|
||||
<View
|
||||
style={[
|
||||
a.relative,
|
||||
a.flex_row,
|
||||
a.justify_end,
|
||||
a.align_center,
|
||||
a.pt_lg,
|
||||
a.px_md,
|
||||
a.pb_2xs,
|
||||
t.atoms.bg,
|
||||
t.atoms.border_contrast_low,
|
||||
styles.bar,
|
||||
kawaii && {paddingTop: 22, paddingBottom: 16},
|
||||
]}>
|
||||
<Layout.Center>
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.inset_0,
|
||||
a.pt_lg,
|
||||
a.m_auto,
|
||||
kawaii && {paddingTop: 4, paddingBottom: 0},
|
||||
{
|
||||
width: kawaii ? 84 : 28,
|
||||
},
|
||||
]}>
|
||||
<Logo width={kawaii ? 60 : 28} />
|
||||
style={[a.flex_row, a.align_center, a.pt_md, gutter, t.atoms.bg]}>
|
||||
<View style={{width: 34}} />
|
||||
<View style={[a.flex_1, a.align_center, a.justify_center]}>
|
||||
<Logo width={kawaii ? 60 : 28} />
|
||||
</View>
|
||||
<Link
|
||||
to="/feeds"
|
||||
hitSlop={10}
|
||||
label={_(msg`View your feeds and explore more`)}
|
||||
size="small"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
shape="square"
|
||||
style={[a.justify_center]}>
|
||||
<ButtonIcon icon={FeedsIcon} size="lg" />
|
||||
</Link>
|
||||
</View>
|
||||
|
||||
<Link
|
||||
to="/feeds"
|
||||
hitSlop={10}
|
||||
label={_(msg`View your feeds and explore more`)}
|
||||
size="small"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
shape="square"
|
||||
style={[
|
||||
a.justify_center,
|
||||
{
|
||||
marginTop: -4,
|
||||
},
|
||||
]}>
|
||||
<FeedsIcon size="md" fill={t.atoms.text_contrast_medium.color} />
|
||||
</Link>
|
||||
</View>
|
||||
</Layout.Center>
|
||||
)}
|
||||
{tabBarAnchor}
|
||||
<Animated.View
|
||||
onLayout={e => {
|
||||
headerHeight.set(e.nativeEvent.layout.height)
|
||||
}}
|
||||
style={[
|
||||
t.atoms.bg,
|
||||
t.atoms.border_contrast_low,
|
||||
styles.bar,
|
||||
styles.tabBar,
|
||||
headerMinimalShellTransform,
|
||||
]}>
|
||||
{children}
|
||||
</Animated.View>
|
||||
<Layout.Center
|
||||
style={[a.sticky, a.z_10, a.align_center, t.atoms.bg, {top: 0}]}>
|
||||
<Animated.View
|
||||
onLayout={e => {
|
||||
headerHeight.set(e.nativeEvent.layout.height)
|
||||
}}
|
||||
style={[headerMinimalShellTransform]}>
|
||||
{children}
|
||||
</Animated.View>
|
||||
</Layout.Center>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
bar: {
|
||||
// @ts-ignore Web only
|
||||
left: 'calc(50% - 300px)',
|
||||
width: 600,
|
||||
borderLeftWidth: 1,
|
||||
borderRightWidth: 1,
|
||||
},
|
||||
topBar: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 18,
|
||||
paddingTop: 16,
|
||||
paddingBottom: 8,
|
||||
},
|
||||
tabBar: {
|
||||
// @ts-ignore Web only
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
borderLeftWidth: 1,
|
||||
borderRightWidth: 1,
|
||||
zIndex: 1,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
import React from 'react'
|
||||
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||
import {View} from 'react-native'
|
||||
import Animated from 'react-native-reanimated'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {HITSLOP_10} from '#/lib/constants'
|
||||
import {PressableScale} from '#/lib/custom-animations/PressableScale'
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {useMinimalShellHeaderTransform} from '#/lib/hooks/useMinimalShellTransform'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {emitSoftReset} from '#/state/events'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useSetDrawerOpen} from '#/state/shell/drawer-open'
|
||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||
import {Logo} from '#/view/icons/Logo'
|
||||
import {atoms} from '#/alf'
|
||||
import {useTheme} from '#/alf'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {ColorPalette_Stroke2_Corner0_Rounded as ColorPalette} from '#/components/icons/ColorPalette'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {ButtonIcon} from '#/components/Button'
|
||||
import {Hashtag_Stroke2_Corner0_Rounded as FeedsIcon} from '#/components/icons/Hashtag'
|
||||
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Link} from '#/components/Link'
|
||||
import {IS_DEV} from '#/env'
|
||||
|
||||
export function HomeHeaderLayoutMobile({
|
||||
children,
|
||||
@@ -28,58 +25,50 @@ export function HomeHeaderLayoutMobile({
|
||||
tabBarAnchor: JSX.Element | null | undefined
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const setDrawerOpen = useSetDrawerOpen()
|
||||
const {headerHeight} = useShellLayout()
|
||||
const headerMinimalShellTransform = useMinimalShellHeaderTransform()
|
||||
const {hasSession} = useSession()
|
||||
|
||||
const onPressAvi = React.useCallback(() => {
|
||||
setDrawerOpen(true)
|
||||
}, [setDrawerOpen])
|
||||
const playHaptic = useHaptics()
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
style={[pal.border, styles.tabBar, headerMinimalShellTransform]}
|
||||
style={[
|
||||
a.fixed,
|
||||
a.z_10,
|
||||
t.atoms.bg,
|
||||
{
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
},
|
||||
headerMinimalShellTransform,
|
||||
]}
|
||||
onLayout={e => {
|
||||
headerHeight.set(e.nativeEvent.layout.height)
|
||||
}}>
|
||||
<View style={[pal.view, styles.topBar]}>
|
||||
<View style={[{width: 100}]}>
|
||||
<TouchableOpacity
|
||||
testID="viewHeaderDrawerBtn"
|
||||
onPress={onPressAvi}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Open navigation`)}
|
||||
accessibilityHint={_(
|
||||
msg`Access profile and other navigation links`,
|
||||
)}
|
||||
hitSlop={HITSLOP_10}>
|
||||
<Menu size="lg" fill={t.atoms.text_contrast_medium.color} />
|
||||
</TouchableOpacity>
|
||||
<Layout.Header.Outer noBottomBorder>
|
||||
<Layout.Header.Slot>
|
||||
<Layout.Header.MenuButton />
|
||||
</Layout.Header.Slot>
|
||||
|
||||
<View style={[a.flex_1, a.align_center]}>
|
||||
<PressableScale
|
||||
targetScale={0.9}
|
||||
onPress={() => {
|
||||
emitSoftReset()
|
||||
}}
|
||||
onPressIn={() => {
|
||||
playHaptic('Heavy')
|
||||
}}
|
||||
onPressOut={() => {
|
||||
playHaptic('Light')
|
||||
}}>
|
||||
<Logo width={30} />
|
||||
</PressableScale>
|
||||
</View>
|
||||
<View>
|
||||
<Logo width={30} />
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
atoms.flex_row,
|
||||
atoms.justify_end,
|
||||
atoms.align_center,
|
||||
atoms.gap_md,
|
||||
{width: 100},
|
||||
]}>
|
||||
{IS_DEV && (
|
||||
<>
|
||||
<Link
|
||||
label="View storybook"
|
||||
to="/sys/debug"
|
||||
testID="storybookBtn">
|
||||
<ColorPalette size="md" />
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Layout.Header.Slot>
|
||||
{hasSession && (
|
||||
<Link
|
||||
testID="viewHeaderHomeFeedPrefsBtn"
|
||||
@@ -93,40 +82,15 @@ export function HomeHeaderLayoutMobile({
|
||||
style={[
|
||||
a.justify_center,
|
||||
{
|
||||
marginTop: 2,
|
||||
marginRight: -6,
|
||||
marginRight: -Layout.BUTTON_VISUAL_ALIGNMENT_OFFSET,
|
||||
},
|
||||
]}>
|
||||
<FeedsIcon size="lg" fill={t.atoms.text_contrast_medium.color} />
|
||||
<ButtonIcon icon={FeedsIcon} size="lg" />
|
||||
</Link>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</Layout.Header.Slot>
|
||||
</Layout.Header.Outer>
|
||||
{children}
|
||||
</Animated.View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
tabBar: {
|
||||
// @ts-ignore web-only
|
||||
position: isWeb ? 'fixed' : 'absolute',
|
||||
zIndex: 1,
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
flexDirection: 'column',
|
||||
},
|
||||
topBar: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 5,
|
||||
width: '100%',
|
||||
minHeight: 46,
|
||||
},
|
||||
title: {
|
||||
fontSize: 21,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
} from '@fortawesome/react-native-fontawesome'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {RemoveScrollBar} from 'react-remove-scroll-bar'
|
||||
|
||||
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {colors, s} from '#/lib/styles'
|
||||
import {useLightbox, useLightboxControls} from '#/state/lightbox'
|
||||
@@ -28,7 +28,6 @@ export function Lightbox() {
|
||||
const {activeLightbox} = useLightbox()
|
||||
const {closeLightbox} = useLightboxControls()
|
||||
const isActive = !!activeLightbox
|
||||
useWebBodyScrollLock(isActive)
|
||||
|
||||
if (!isActive) {
|
||||
return null
|
||||
@@ -37,11 +36,14 @@ export function Lightbox() {
|
||||
const initialIndex = activeLightbox.index
|
||||
const imgs = activeLightbox.images
|
||||
return (
|
||||
<LightboxInner
|
||||
imgs={imgs}
|
||||
initialIndex={initialIndex}
|
||||
onClose={closeLightbox}
|
||||
/>
|
||||
<>
|
||||
<RemoveScrollBar />
|
||||
<LightboxInner
|
||||
imgs={imgs}
|
||||
initialIndex={initialIndex}
|
||||
onClose={closeLightbox}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {s} from '#/lib/styles'
|
||||
import {logger} from '#/logger'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {MyListsFilter, useMyListsQuery} from '#/state/queries/my-lists'
|
||||
import {EmptyState} from '#/view/com/util/EmptyState'
|
||||
@@ -110,7 +109,7 @@ export function MyLists({
|
||||
) : (
|
||||
<View
|
||||
style={[
|
||||
(index !== 0 || isWeb) && a.border_t,
|
||||
index !== 0 && a.border_t,
|
||||
t.atoms.border_contrast_low,
|
||||
a.px_lg,
|
||||
a.py_lg,
|
||||
@@ -141,8 +140,6 @@ export function MyLists({
|
||||
}
|
||||
contentContainerStyle={[s.contentContainer]}
|
||||
removeClippedSubviews={true}
|
||||
// @ts-ignore our .web version only -prf
|
||||
desktopFixedHeight
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
@@ -160,8 +157,8 @@ export function MyLists({
|
||||
onRefresh={onRefresh}
|
||||
contentContainerStyle={[s.contentContainer]}
|
||||
removeClippedSubviews={true}
|
||||
// @ts-ignore our .web version only -prf
|
||||
desktopFixedHeight
|
||||
sideBorders={false}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
|
||||
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
|
||||
import {RemoveScrollBar} from 'react-remove-scroll-bar'
|
||||
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import type {Modal as ModalIface} from '#/state/modals'
|
||||
import {useModalControls, useModals} from '#/state/modals'
|
||||
@@ -22,7 +22,6 @@ import * as VerifyEmailModal from './VerifyEmail'
|
||||
|
||||
export function ModalsContainer() {
|
||||
const {isModalActive, activeModals} = useModals()
|
||||
useWebBodyScrollLock(isModalActive)
|
||||
|
||||
if (!isModalActive) {
|
||||
return null
|
||||
@@ -30,6 +29,7 @@ export function ModalsContainer() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<RemoveScrollBar />
|
||||
{activeModals.map((modal, i) => (
|
||||
<Modal key={`modal-${i}`} modal={modal} />
|
||||
))}
|
||||
|
||||
@@ -10,7 +10,6 @@ import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {s} from '#/lib/styles'
|
||||
import {logger} from '#/logger'
|
||||
@@ -22,7 +21,6 @@ import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
||||
import {List, ListRef} from '#/view/com/util/List'
|
||||
import {NotificationFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import {FeedItem} from './FeedItem'
|
||||
|
||||
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
||||
@@ -46,7 +44,6 @@ export function Feed({
|
||||
|
||||
const [isPTRing, setIsPTRing] = React.useState(false)
|
||||
const pal = usePalette('default')
|
||||
const {isTabletOrMobile} = useWebMediaQueries()
|
||||
|
||||
const {_} = useLingui()
|
||||
const moderationOpts = useModerationOpts()
|
||||
@@ -133,11 +130,7 @@ export function Feed({
|
||||
)
|
||||
} else if (item === LOADING_ITEM) {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
pal.border,
|
||||
!isTabletOrMobile && {borderTopWidth: StyleSheet.hairlineWidth},
|
||||
]}>
|
||||
<View style={[pal.border]}>
|
||||
<NotificationFeedLoadingPlaceholder />
|
||||
</View>
|
||||
)
|
||||
@@ -146,11 +139,11 @@ export function Feed({
|
||||
<FeedItem
|
||||
item={item}
|
||||
moderationOpts={moderationOpts!}
|
||||
hideTopBorder={index === 0 && isTabletOrMobile}
|
||||
hideTopBorder={index === 0}
|
||||
/>
|
||||
)
|
||||
},
|
||||
[moderationOpts, isTabletOrMobile, _, onPressRetryLoadMore, pal.border],
|
||||
[moderationOpts, _, onPressRetryLoadMore, pal.border],
|
||||
)
|
||||
|
||||
const FeedFooter = React.useCallback(
|
||||
@@ -168,12 +161,10 @@ export function Feed({
|
||||
return (
|
||||
<View style={s.hContentRegion}>
|
||||
{error && (
|
||||
<CenteredView>
|
||||
<ErrorMessage
|
||||
message={cleanError(error)}
|
||||
onPressTryAgain={onPressTryAgain}
|
||||
/>
|
||||
</CenteredView>
|
||||
<ErrorMessage
|
||||
message={cleanError(error)}
|
||||
onPressTryAgain={onPressTryAgain}
|
||||
/>
|
||||
)}
|
||||
<List
|
||||
testID="notifsFeed"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import * as React from 'react'
|
||||
import {ScrollView, StyleSheet, View} from 'react-native'
|
||||
import {ScrollView, View} from 'react-native'
|
||||
import {useAnimatedRef} from 'react-native-reanimated'
|
||||
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {Pager, PagerRef, RenderTabBarFnProps} from '#/view/com/pager/Pager'
|
||||
import {atoms as a, web} from '#/alf'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ListMethods} from '../util/List'
|
||||
import {TabBar} from './TabBar'
|
||||
|
||||
@@ -121,30 +121,19 @@ let PagerTabBar = ({
|
||||
onSelect?: (index: number) => void
|
||||
tabBarAnchor?: JSX.Element | null | undefined
|
||||
}): React.ReactNode => {
|
||||
const pal = usePalette('default')
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
return (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
!isMobile && styles.headerContainerDesktop,
|
||||
pal.border,
|
||||
!isHeaderReady && styles.loadingHeader,
|
||||
]}>
|
||||
{renderHeader?.()}
|
||||
</View>
|
||||
<Layout.Center>{renderHeader?.()}</Layout.Center>
|
||||
{tabBarAnchor}
|
||||
<View
|
||||
style={[
|
||||
styles.tabBarContainer,
|
||||
isMobile
|
||||
? styles.tabBarContainerMobile
|
||||
: styles.tabBarContainerDesktop,
|
||||
pal.border,
|
||||
<Layout.Center
|
||||
style={web([
|
||||
a.sticky,
|
||||
a.z_10,
|
||||
{
|
||||
top: 0,
|
||||
display: isHeaderReady ? undefined : 'none',
|
||||
},
|
||||
]}>
|
||||
])}>
|
||||
<TabBar
|
||||
testID={testID}
|
||||
items={items}
|
||||
@@ -154,7 +143,7 @@ let PagerTabBar = ({
|
||||
dragProgress={undefined as any /* native-only */}
|
||||
dragState={undefined as any /* native-only */}
|
||||
/>
|
||||
</View>
|
||||
</Layout.Center>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -180,33 +169,6 @@ function PagerItem({
|
||||
})
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
headerContainerDesktop: {
|
||||
marginHorizontal: 'auto',
|
||||
width: 600,
|
||||
borderLeftWidth: 1,
|
||||
borderRightWidth: 1,
|
||||
},
|
||||
tabBarContainer: {
|
||||
// @ts-ignore web-only
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 1,
|
||||
},
|
||||
tabBarContainerDesktop: {
|
||||
marginHorizontal: 'auto',
|
||||
width: 600,
|
||||
borderLeftWidth: 1,
|
||||
borderRightWidth: 1,
|
||||
},
|
||||
tabBarContainerMobile: {
|
||||
paddingHorizontal: 0,
|
||||
},
|
||||
loadingHeader: {
|
||||
borderColor: 'transparent',
|
||||
},
|
||||
})
|
||||
|
||||
function toArray<T>(v: T | T[]): T[] {
|
||||
if (Array.isArray(v)) {
|
||||
return v
|
||||
|
||||
@@ -6,7 +6,6 @@ import {useLingui} from '@lingui/react'
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useLikedByQuery} from '#/state/queries/post-liked-by'
|
||||
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
|
||||
import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
|
||||
@@ -18,7 +17,7 @@ function renderItem({item, index}: {item: GetLikes.Like; index: number}) {
|
||||
<ProfileCardWithFollowBtn
|
||||
key={item.actor.did}
|
||||
profile={item.actor}
|
||||
noBorder={index === 0 && !isWeb}
|
||||
noBorder={index === 0}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -88,6 +87,7 @@ export function PostLikedBy({uri}: {uri: string}) {
|
||||
)}
|
||||
errorMessage={cleanError(resolveError || error)}
|
||||
sideBorders={false}
|
||||
topBorder={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -108,7 +108,6 @@ export function PostLikedBy({uri}: {uri: string}) {
|
||||
onRetry={fetchNextPage}
|
||||
/>
|
||||
}
|
||||
// @ts-ignore our .web version only -prf
|
||||
desktopFixedHeight
|
||||
initialNumToRender={initialNumToRender}
|
||||
windowSize={11}
|
||||
|
||||
@@ -11,7 +11,6 @@ import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {usePostQuotesQuery} from '#/state/queries/post-quotes'
|
||||
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
|
||||
@@ -30,7 +29,7 @@ function renderItem({
|
||||
}
|
||||
index: number
|
||||
}) {
|
||||
return <Post post={item.post} hideTopBorder={index === 0 && !isWeb} />
|
||||
return <Post post={item.post} hideTopBorder={index === 0} />
|
||||
}
|
||||
|
||||
function keyExtractor(item: {
|
||||
|
||||
@@ -12,8 +12,20 @@ import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
|
||||
import {List} from '#/view/com/util/List'
|
||||
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||
|
||||
function renderItem({item}: {item: ActorDefs.ProfileViewBasic}) {
|
||||
return <ProfileCardWithFollowBtn key={item.did} profile={item} />
|
||||
function renderItem({
|
||||
item,
|
||||
index,
|
||||
}: {
|
||||
item: ActorDefs.ProfileViewBasic
|
||||
index: number
|
||||
}) {
|
||||
return (
|
||||
<ProfileCardWithFollowBtn
|
||||
key={item.did}
|
||||
profile={item}
|
||||
noBorder={index === 0}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function keyExtractor(item: ActorDefs.ProfileViewBasic) {
|
||||
|
||||
@@ -32,7 +32,6 @@ import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useComposerControls} from '#/state/shell'
|
||||
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||
import {Text} from '#/components/Typography'
|
||||
@@ -484,7 +483,7 @@ export function PostThread({uri}: {uri: string | undefined}) {
|
||||
}
|
||||
|
||||
return (
|
||||
<CenteredView style={[a.flex_1]} sideBorders={true}>
|
||||
<>
|
||||
{showHeader && (
|
||||
<ViewHeader
|
||||
title={_(msg({message: `Post`, context: 'description'}))}
|
||||
@@ -531,7 +530,7 @@ export function PostThread({uri}: {uri: string | undefined}) {
|
||||
{isMobile && canReply && hasSession && (
|
||||
<MobileComposePrompt onPressReply={onPressReply} />
|
||||
)}
|
||||
</CenteredView>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import {useLingui} from '@lingui/react'
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useProfileFollowersQuery} from '#/state/queries/profile-followers'
|
||||
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
||||
import {useSession} from '#/state/session'
|
||||
@@ -25,7 +24,7 @@ function renderItem({
|
||||
<ProfileCardWithFollowBtn
|
||||
key={item.did}
|
||||
profile={item}
|
||||
noBorder={index === 0 && !isWeb}
|
||||
noBorder={index === 0}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import {useLingui} from '@lingui/react'
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useProfileFollowsQuery} from '#/state/queries/profile-follows'
|
||||
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
||||
import {useSession} from '#/state/session'
|
||||
@@ -25,7 +24,7 @@ function renderItem({
|
||||
<ProfileCardWithFollowBtn
|
||||
key={item.did}
|
||||
profile={item}
|
||||
noBorder={index === 0 && !isWeb}
|
||||
noBorder={index === 0}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,29 +1,24 @@
|
||||
import React from 'react'
|
||||
import {Pressable, StyleSheet, View} from 'react-native'
|
||||
import {Pressable, View} from 'react-native'
|
||||
import {MeasuredDimensions, runOnJS, runOnUI} from 'react-native-reanimated'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {BACK_HITSLOP} from '#/lib/constants'
|
||||
import {measureHandle, useHandleRef} from '#/lib/hooks/useHandleRef'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {NavigationProp} from '#/lib/routes/types'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {emitSoftReset} from '#/state/events'
|
||||
import {useLightboxControls} from '#/state/lightbox'
|
||||
import {useSetDrawerOpen} from '#/state/shell'
|
||||
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
|
||||
import {TextLink} from '#/view/com/util/Link'
|
||||
import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {UserAvatar, UserAvatarType} from '#/view/com/util/UserAvatar'
|
||||
import {StarterPack} from '#/components/icons/StarterPack'
|
||||
import {TextLink} from '../util/Link'
|
||||
import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {UserAvatar, UserAvatarType} from '../util/UserAvatar'
|
||||
import {CenteredView} from '../util/Views'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
export function ProfileSubpageHeader({
|
||||
isLoading,
|
||||
@@ -48,7 +43,6 @@ export function ProfileSubpageHeader({
|
||||
| undefined
|
||||
avatarType: UserAvatarType | 'starter-pack'
|
||||
}>) {
|
||||
const setDrawerOpen = useSetDrawerOpen()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {_} = useLingui()
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
@@ -57,18 +51,6 @@ export function ProfileSubpageHeader({
|
||||
const canGoBack = navigation.canGoBack()
|
||||
const aviRef = useHandleRef()
|
||||
|
||||
const onPressBack = React.useCallback(() => {
|
||||
if (navigation.canGoBack()) {
|
||||
navigation.goBack()
|
||||
} else {
|
||||
navigation.navigate('Home')
|
||||
}
|
||||
}, [navigation])
|
||||
|
||||
const onPressMenu = React.useCallback(() => {
|
||||
setDrawerOpen(true)
|
||||
}, [setDrawerOpen])
|
||||
|
||||
const _openLightbox = React.useCallback(
|
||||
(uri: string, thumbRect: MeasuredDimensions | null) => {
|
||||
openLightbox({
|
||||
@@ -106,42 +88,17 @@ export function ProfileSubpageHeader({
|
||||
}, [_openLightbox, avatar, aviRef])
|
||||
|
||||
return (
|
||||
<CenteredView style={pal.view}>
|
||||
{isMobile && (
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
paddingTop: isNative ? 0 : 8,
|
||||
paddingBottom: 8,
|
||||
paddingHorizontal: isMobile ? 12 : 14,
|
||||
},
|
||||
pal.border,
|
||||
]}>
|
||||
<Pressable
|
||||
testID="headerDrawerBtn"
|
||||
onPress={canGoBack ? onPressBack : onPressMenu}
|
||||
hitSlop={BACK_HITSLOP}
|
||||
style={canGoBack ? styles.backBtn : styles.backBtnWide}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={canGoBack ? 'Back' : 'Menu'}
|
||||
accessibilityHint="">
|
||||
{canGoBack ? (
|
||||
<FontAwesomeIcon
|
||||
size={18}
|
||||
icon="angle-left"
|
||||
style={[styles.backIcon, pal.text]}
|
||||
/>
|
||||
) : (
|
||||
<Menu size="lg" style={[{marginTop: 4}, pal.textLight]} />
|
||||
)}
|
||||
</Pressable>
|
||||
<View style={{flex: 1}} />
|
||||
{children}
|
||||
</View>
|
||||
)}
|
||||
<>
|
||||
<Layout.Header.Outer>
|
||||
{canGoBack ? (
|
||||
<Layout.Header.BackButton />
|
||||
) : (
|
||||
<Layout.Header.MenuButton />
|
||||
)}
|
||||
<Layout.Header.Content />
|
||||
{children}
|
||||
</Layout.Header.Outer>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
@@ -206,31 +163,7 @@ export function ProfileSubpageHeader({
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
{!isMobile && (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
{children}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</CenteredView>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
backBtn: {
|
||||
width: 20,
|
||||
height: 30,
|
||||
},
|
||||
backBtnWide: {
|
||||
width: 20,
|
||||
height: 30,
|
||||
marginRight: 4,
|
||||
},
|
||||
backIcon: {
|
||||
marginTop: 6,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -4,10 +4,9 @@ import {ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/hook
|
||||
|
||||
import {batchedUpdates} from '#/lib/batchedUpdates'
|
||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {useScrollHandlers} from '#/lib/ScrollContext'
|
||||
import {addStyle} from '#/lib/styles'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
export type ListMethods = any // TODO: Better types.
|
||||
export type ListProps<ItemT> = Omit<
|
||||
@@ -24,6 +23,9 @@ export type ListProps<ItemT> = Omit<
|
||||
desktopFixedHeight?: number | boolean
|
||||
// Web only prop to contain the scroll to the container rather than the window
|
||||
disableFullWindowScroll?: boolean
|
||||
/**
|
||||
* @deprecated Should be using Layout components
|
||||
*/
|
||||
sideBorders?: boolean
|
||||
}
|
||||
export type ListRef = React.MutableRefObject<any | null> // TODO: Better types.
|
||||
@@ -56,20 +58,11 @@ function ListImpl<ItemT>(
|
||||
renderItem,
|
||||
extraData,
|
||||
style,
|
||||
sideBorders = true,
|
||||
...props
|
||||
}: ListProps<ItemT>,
|
||||
ref: React.Ref<ListMethods>,
|
||||
) {
|
||||
const contextScrollHandlers = useScrollHandlers()
|
||||
const pal = usePalette('default')
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
if (!isMobile) {
|
||||
contentContainerStyle = addStyle(
|
||||
contentContainerStyle,
|
||||
styles.containerScroll,
|
||||
)
|
||||
}
|
||||
|
||||
const isEmpty = !data || data.length === 0
|
||||
|
||||
@@ -326,53 +319,53 @@ function ListImpl<ItemT>(
|
||||
styles.parentTreeVisibilityDetector
|
||||
}
|
||||
/>
|
||||
<View
|
||||
ref={containerRef}
|
||||
style={[
|
||||
!isMobile && sideBorders && styles.sideBorders,
|
||||
contentContainerStyle,
|
||||
desktopFixedHeight ? styles.minHeightViewport : null,
|
||||
pal.border,
|
||||
]}>
|
||||
<Visibility
|
||||
root={disableFullWindowScroll ? nativeRef : null}
|
||||
onVisibleChange={handleAboveTheFoldVisibleChange}
|
||||
style={[styles.aboveTheFoldDetector, {height: headerOffset}]}
|
||||
/>
|
||||
{onStartReached && !isEmpty && (
|
||||
<EdgeVisibility
|
||||
<Layout.Center>
|
||||
<View
|
||||
ref={containerRef}
|
||||
style={[
|
||||
contentContainerStyle,
|
||||
desktopFixedHeight ? styles.minHeightViewport : null,
|
||||
]}>
|
||||
<Visibility
|
||||
root={disableFullWindowScroll ? nativeRef : null}
|
||||
onVisibleChange={onHeadVisibilityChange}
|
||||
topMargin={(onStartReachedThreshold ?? 0) * 100 + '%'}
|
||||
containerRef={containerRef}
|
||||
onVisibleChange={handleAboveTheFoldVisibleChange}
|
||||
style={[styles.aboveTheFoldDetector, {height: headerOffset}]}
|
||||
/>
|
||||
)}
|
||||
{headerComponent}
|
||||
{isEmpty
|
||||
? emptyComponent
|
||||
: (data as Array<ItemT>)?.map((item, index) => {
|
||||
const key = keyExtractor!(item, index)
|
||||
return (
|
||||
<Row<ItemT>
|
||||
key={key}
|
||||
item={item}
|
||||
index={index}
|
||||
renderItem={renderItem}
|
||||
extraData={extraData}
|
||||
onItemSeen={onItemSeen}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{onEndReached && !isEmpty && (
|
||||
<EdgeVisibility
|
||||
root={disableFullWindowScroll ? nativeRef : null}
|
||||
onVisibleChange={onTailVisibilityChange}
|
||||
bottomMargin={(onEndReachedThreshold ?? 0) * 100 + '%'}
|
||||
containerRef={containerRef}
|
||||
/>
|
||||
)}
|
||||
{footerComponent}
|
||||
</View>
|
||||
{onStartReached && !isEmpty && (
|
||||
<EdgeVisibility
|
||||
root={disableFullWindowScroll ? nativeRef : null}
|
||||
onVisibleChange={onHeadVisibilityChange}
|
||||
topMargin={(onStartReachedThreshold ?? 0) * 100 + '%'}
|
||||
containerRef={containerRef}
|
||||
/>
|
||||
)}
|
||||
{headerComponent}
|
||||
{isEmpty
|
||||
? emptyComponent
|
||||
: (data as Array<ItemT>)?.map((item, index) => {
|
||||
const key = keyExtractor!(item, index)
|
||||
return (
|
||||
<Row<ItemT>
|
||||
key={key}
|
||||
item={item}
|
||||
index={index}
|
||||
renderItem={renderItem}
|
||||
extraData={extraData}
|
||||
onItemSeen={onItemSeen}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{onEndReached && !isEmpty && (
|
||||
<EdgeVisibility
|
||||
root={disableFullWindowScroll ? nativeRef : null}
|
||||
onVisibleChange={onTailVisibilityChange}
|
||||
bottomMargin={(onEndReachedThreshold ?? 0) * 100 + '%'}
|
||||
containerRef={containerRef}
|
||||
/>
|
||||
)}
|
||||
{footerComponent}
|
||||
</View>
|
||||
</Layout.Center>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -558,16 +551,6 @@ export const List = memo(React.forwardRef(ListImpl)) as <ItemT>(
|
||||
// https://stackoverflow.com/questions/7944460/detect-safari-browser
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
sideBorders: {
|
||||
borderLeftWidth: 1,
|
||||
borderRightWidth: 1,
|
||||
},
|
||||
containerScroll: {
|
||||
width: '100%',
|
||||
maxWidth: 600,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
},
|
||||
minHeightViewport: {
|
||||
// @ts-ignore web only
|
||||
minHeight: '100vh',
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import {ActivityIndicator, View} from 'react-native'
|
||||
|
||||
import {s} from '#/lib/styles'
|
||||
import {CenteredView} from './Views'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
/**
|
||||
* @deprecated use Layout compoenents directly
|
||||
*/
|
||||
export function LoadingScreen() {
|
||||
return (
|
||||
<CenteredView>
|
||||
<Layout.Content>
|
||||
<View style={s.p20}>
|
||||
<ActivityIndicator size="large" />
|
||||
</View>
|
||||
</CenteredView>
|
||||
</Layout.Content>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
StyleProp,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
ViewStyle,
|
||||
} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {NavigationProp} from '#/lib/routes/types'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useSetDrawerOpen} from '#/state/shell'
|
||||
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
|
||||
import {CenteredView} from './Views'
|
||||
|
||||
const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20}
|
||||
|
||||
export function SimpleViewHeader({
|
||||
showBackButton = true,
|
||||
style,
|
||||
children,
|
||||
}: React.PropsWithChildren<{
|
||||
showBackButton?: boolean
|
||||
style?: StyleProp<ViewStyle>
|
||||
}>) {
|
||||
const pal = usePalette('default')
|
||||
const setDrawerOpen = useSetDrawerOpen()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const canGoBack = navigation.canGoBack()
|
||||
|
||||
const onPressBack = React.useCallback(() => {
|
||||
if (navigation.canGoBack()) {
|
||||
navigation.goBack()
|
||||
} else {
|
||||
navigation.navigate('Home')
|
||||
}
|
||||
}, [navigation])
|
||||
|
||||
const onPressMenu = React.useCallback(() => {
|
||||
setDrawerOpen(true)
|
||||
}, [setDrawerOpen])
|
||||
|
||||
const Container = isMobile ? View : CenteredView
|
||||
return (
|
||||
<Container
|
||||
style={[
|
||||
styles.header,
|
||||
isMobile && styles.headerMobile,
|
||||
isWeb && styles.headerWeb,
|
||||
pal.view,
|
||||
style,
|
||||
]}>
|
||||
{showBackButton ? (
|
||||
<TouchableOpacity
|
||||
testID="viewHeaderDrawerBtn"
|
||||
onPress={canGoBack ? onPressBack : onPressMenu}
|
||||
hitSlop={BACK_HITSLOP}
|
||||
style={canGoBack ? styles.backBtn : styles.backBtnWide}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={canGoBack ? 'Back' : 'Menu'}
|
||||
accessibilityHint="">
|
||||
{canGoBack ? (
|
||||
<FontAwesomeIcon
|
||||
size={18}
|
||||
icon="angle-left"
|
||||
style={[styles.backIcon, pal.text]}
|
||||
/>
|
||||
) : (
|
||||
<Menu size="lg" style={[{marginTop: 4}, pal.textLight]} />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
{children}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 18,
|
||||
paddingVertical: 12,
|
||||
width: '100%',
|
||||
},
|
||||
headerMobile: {
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 10,
|
||||
},
|
||||
headerWeb: {
|
||||
// @ts-ignore web-only
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 1,
|
||||
},
|
||||
backBtn: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
},
|
||||
backBtnWide: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
paddingLeft: 4,
|
||||
marginRight: 4,
|
||||
},
|
||||
backIcon: {
|
||||
marginTop: 6,
|
||||
},
|
||||
})
|
||||
@@ -1,271 +1,27 @@
|
||||
import React from 'react'
|
||||
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||
import Animated from 'react-native-reanimated'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {useMinimalShellHeaderTransform} from '#/lib/hooks/useMinimalShellTransform'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {NavigationProp} from '#/lib/routes/types'
|
||||
import {useSetDrawerOpen} from '#/state/shell'
|
||||
import {useTheme} from '#/alf'
|
||||
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
|
||||
import {Text} from './text/Text'
|
||||
import {CenteredView} from './Views'
|
||||
|
||||
const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20}
|
||||
import {Header} from '#/components/Layout'
|
||||
|
||||
/**
|
||||
* Legacy ViewHeader component. Use Layout.Header going forward.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export function ViewHeader({
|
||||
title,
|
||||
subtitle,
|
||||
canGoBack,
|
||||
showBackButton = true,
|
||||
hideOnScroll,
|
||||
showOnDesktop,
|
||||
showBorder,
|
||||
renderButton,
|
||||
}: {
|
||||
title: string
|
||||
subtitle?: string
|
||||
canGoBack?: boolean
|
||||
showBackButton?: boolean
|
||||
hideOnScroll?: boolean
|
||||
showOnDesktop?: boolean
|
||||
showBorder?: boolean
|
||||
renderButton?: () => JSX.Element
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const setDrawerOpen = useSetDrawerOpen()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {isDesktop, isTablet} = useWebMediaQueries()
|
||||
const t = useTheme()
|
||||
|
||||
const onPressBack = React.useCallback(() => {
|
||||
if (navigation.canGoBack()) {
|
||||
navigation.goBack()
|
||||
} else {
|
||||
navigation.navigate('Home')
|
||||
}
|
||||
}, [navigation])
|
||||
|
||||
const onPressMenu = React.useCallback(() => {
|
||||
setDrawerOpen(true)
|
||||
}, [setDrawerOpen])
|
||||
|
||||
if (isDesktop) {
|
||||
if (showOnDesktop) {
|
||||
return (
|
||||
<DesktopWebHeader
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
renderButton={renderButton}
|
||||
showBorder={showBorder}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return null
|
||||
} else {
|
||||
if (typeof canGoBack === 'undefined') {
|
||||
canGoBack = navigation.canGoBack()
|
||||
}
|
||||
|
||||
return (
|
||||
<Container hideOnScroll={hideOnScroll || false} showBorder={showBorder}>
|
||||
<View style={{flex: 1}}>
|
||||
<View style={{flexDirection: 'row', alignItems: 'center'}}>
|
||||
{showBackButton ? (
|
||||
<TouchableOpacity
|
||||
testID="viewHeaderDrawerBtn"
|
||||
onPress={canGoBack ? onPressBack : onPressMenu}
|
||||
hitSlop={BACK_HITSLOP}
|
||||
style={canGoBack ? styles.backBtn : styles.backBtnWide}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={canGoBack ? _(msg`Back`) : _(msg`Menu`)}
|
||||
accessibilityHint={
|
||||
canGoBack ? '' : _(msg`Access navigation links and settings`)
|
||||
}>
|
||||
{canGoBack ? (
|
||||
<FontAwesomeIcon
|
||||
size={18}
|
||||
icon="angle-left"
|
||||
style={[styles.backIcon, pal.text]}
|
||||
/>
|
||||
) : !isTablet ? (
|
||||
<Menu size="lg" style={[{marginTop: 3}, pal.textLight]} />
|
||||
) : null}
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
<View style={styles.titleContainer} pointerEvents="none">
|
||||
<Text emoji type="title" style={[pal.text, styles.title]}>
|
||||
{title}
|
||||
</Text>
|
||||
</View>
|
||||
{renderButton ? (
|
||||
renderButton()
|
||||
) : showBackButton ? (
|
||||
<View style={canGoBack ? styles.backBtn : styles.backBtnWide} />
|
||||
) : null}
|
||||
</View>
|
||||
{subtitle ? (
|
||||
<View
|
||||
style={[styles.titleContainer, {marginTop: -3}]}
|
||||
pointerEvents="none">
|
||||
<Text
|
||||
style={[
|
||||
pal.text,
|
||||
styles.subtitle,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
{subtitle}
|
||||
</Text>
|
||||
</View>
|
||||
) : undefined}
|
||||
</View>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function DesktopWebHeader({
|
||||
title,
|
||||
subtitle,
|
||||
renderButton,
|
||||
showBorder = true,
|
||||
}: {
|
||||
title: string
|
||||
subtitle?: string
|
||||
renderButton?: () => JSX.Element
|
||||
showBorder?: boolean
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const t = useTheme()
|
||||
return (
|
||||
<CenteredView
|
||||
style={[
|
||||
styles.header,
|
||||
styles.desktopHeader,
|
||||
pal.border,
|
||||
{
|
||||
borderBottomWidth: showBorder ? StyleSheet.hairlineWidth : 0,
|
||||
},
|
||||
{display: 'flex', flexDirection: 'column'},
|
||||
]}>
|
||||
<View>
|
||||
<View style={styles.titleContainer} pointerEvents="none">
|
||||
<Text type="title-lg" style={[pal.text, styles.title]}>
|
||||
{title}
|
||||
</Text>
|
||||
</View>
|
||||
{renderButton?.()}
|
||||
</View>
|
||||
{subtitle ? (
|
||||
<View>
|
||||
<View style={[styles.titleContainer]} pointerEvents="none">
|
||||
<Text
|
||||
style={[
|
||||
pal.text,
|
||||
styles.subtitleDesktop,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
{subtitle}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
) : null}
|
||||
</CenteredView>
|
||||
<Header.Outer>
|
||||
<Header.BackButton />
|
||||
<Header.Content>
|
||||
<Header.TitleText>{title}</Header.TitleText>
|
||||
</Header.Content>
|
||||
<Header.Slot>{renderButton?.() ?? null}</Header.Slot>
|
||||
</Header.Outer>
|
||||
)
|
||||
}
|
||||
|
||||
function Container({
|
||||
children,
|
||||
hideOnScroll,
|
||||
showBorder,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
hideOnScroll: boolean
|
||||
showBorder?: boolean
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const headerMinimalShellTransform = useMinimalShellHeaderTransform()
|
||||
|
||||
if (!hideOnScroll) {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.header,
|
||||
pal.view,
|
||||
pal.border,
|
||||
showBorder && styles.border,
|
||||
]}>
|
||||
{children}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.header,
|
||||
styles.headerFloating,
|
||||
pal.view,
|
||||
pal.border,
|
||||
headerMinimalShellTransform,
|
||||
showBorder && styles.border,
|
||||
]}>
|
||||
{children}
|
||||
</Animated.View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 6,
|
||||
width: '100%',
|
||||
},
|
||||
headerFloating: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
width: '100%',
|
||||
},
|
||||
desktopHeader: {
|
||||
paddingVertical: 12,
|
||||
maxWidth: 600,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
},
|
||||
border: {
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
},
|
||||
titleContainer: {
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
alignItems: 'center',
|
||||
},
|
||||
title: {
|
||||
fontWeight: '600',
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 13,
|
||||
},
|
||||
subtitleDesktop: {
|
||||
fontSize: 15,
|
||||
},
|
||||
backBtn: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
},
|
||||
backBtnWide: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
paddingLeft: 4,
|
||||
marginRight: 4,
|
||||
},
|
||||
backIcon: {
|
||||
marginTop: 6,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -15,9 +15,16 @@ export type FlatList_INTERNAL<ItemT = any> = Omit<
|
||||
FlatListComponent<ItemT, FlatListPropsWithLayout<ItemT>>,
|
||||
'CellRendererComponent'
|
||||
>
|
||||
|
||||
/**
|
||||
* @deprecated use `Layout` components
|
||||
*/
|
||||
export const ScrollView = Animated.ScrollView
|
||||
export type ScrollView = typeof Animated.ScrollView
|
||||
|
||||
/**
|
||||
* @deprecated use `Layout` components
|
||||
*/
|
||||
export const CenteredView = forwardRef<
|
||||
View,
|
||||
React.PropsWithChildren<
|
||||
|
||||
@@ -31,10 +31,12 @@ interface AddedProps {
|
||||
desktopFixedHeight?: boolean | number
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use `Layout` components
|
||||
*/
|
||||
export const CenteredView = React.forwardRef(function CenteredView(
|
||||
{
|
||||
style,
|
||||
sideBorders,
|
||||
topBorder,
|
||||
...props
|
||||
}: React.PropsWithChildren<
|
||||
@@ -47,13 +49,6 @@ export const CenteredView = React.forwardRef(function CenteredView(
|
||||
if (!isMobile) {
|
||||
style = addStyle(style, styles.container)
|
||||
}
|
||||
if (sideBorders && !isMobile) {
|
||||
style = addStyle(style, {
|
||||
borderLeftWidth: StyleSheet.hairlineWidth,
|
||||
borderRightWidth: StyleSheet.hairlineWidth,
|
||||
})
|
||||
style = addStyle(style, pal.border)
|
||||
}
|
||||
if (topBorder) {
|
||||
style = addStyle(style, {
|
||||
borderTopWidth: 1,
|
||||
@@ -75,7 +70,6 @@ export const FlatList_INTERNAL = React.forwardRef(function FlatListImpl<ItemT>(
|
||||
>,
|
||||
ref: React.Ref<FlatList<ItemT>>,
|
||||
) {
|
||||
const pal = usePalette('default')
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
if (!isMobile) {
|
||||
contentContainerStyle = addStyle(
|
||||
@@ -123,11 +117,7 @@ export const FlatList_INTERNAL = React.forwardRef(function FlatListImpl<ItemT>(
|
||||
return (
|
||||
<Animated.FlatList
|
||||
ref={ref}
|
||||
contentContainerStyle={[
|
||||
styles.contentContainer,
|
||||
contentContainerStyle,
|
||||
pal.border,
|
||||
]}
|
||||
contentContainerStyle={[styles.contentContainer, contentContainerStyle]}
|
||||
style={style}
|
||||
contentOffset={contentOffset}
|
||||
{...props}
|
||||
@@ -135,12 +125,13 @@ export const FlatList_INTERNAL = React.forwardRef(function FlatListImpl<ItemT>(
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* @deprecated use `Layout` components
|
||||
*/
|
||||
export const ScrollView = React.forwardRef(function ScrollViewImpl(
|
||||
{contentContainerStyle, ...props}: React.PropsWithChildren<ScrollViewProps>,
|
||||
ref: React.Ref<Animated.ScrollView>,
|
||||
) {
|
||||
const pal = usePalette('default')
|
||||
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
if (!isMobile) {
|
||||
contentContainerStyle = addStyle(
|
||||
@@ -150,11 +141,7 @@ export const ScrollView = React.forwardRef(function ScrollViewImpl(
|
||||
}
|
||||
return (
|
||||
<Animated.ScrollView
|
||||
contentContainerStyle={[
|
||||
styles.contentContainer,
|
||||
contentContainerStyle,
|
||||
pal.border,
|
||||
]}
|
||||
contentContainerStyle={[styles.contentContainer, contentContainerStyle]}
|
||||
// @ts-ignore something is wrong with the reanimated types -prf
|
||||
ref={ref}
|
||||
{...props}
|
||||
@@ -164,8 +151,6 @@ export const ScrollView = React.forwardRef(function ScrollViewImpl(
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
contentContainer: {
|
||||
borderLeftWidth: StyleSheet.hairlineWidth,
|
||||
borderRightWidth: StyleSheet.hairlineWidth,
|
||||
// @ts-ignore web only
|
||||
minHeight: '100vh',
|
||||
},
|
||||
|
||||
@@ -36,7 +36,9 @@ export function ErrorScreen({
|
||||
|
||||
return (
|
||||
<>
|
||||
{showHeader && isMobile && <ViewHeader title="Error" showBorder />}
|
||||
{showHeader && isMobile && (
|
||||
<ViewHeader title={_(msg`Error`)} showBorder />
|
||||
)}
|
||||
<CenteredView testID={testID} style={[styles.outer, pal.view]}>
|
||||
<View style={styles.errorIconContainer}>
|
||||
<View
|
||||
|
||||
+45
-83
@@ -24,14 +24,13 @@ import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {useComposerControls} from '#/state/shell/composer'
|
||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
||||
import {FAB} from '#/view/com/util/fab/FAB'
|
||||
import {TextLink} from '#/view/com/util/Link'
|
||||
import {List, ListMethods} from '#/view/com/util/List'
|
||||
import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
|
||||
import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {ButtonIcon} from '#/components/Button'
|
||||
import {Divider} from '#/components/Divider'
|
||||
import * as FeedCard from '#/components/FeedCard'
|
||||
import {SearchInput} from '#/components/forms/SearchInput'
|
||||
@@ -40,7 +39,9 @@ import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components
|
||||
import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline'
|
||||
import {ListMagnifyingGlass_Stroke2_Corner0_Rounded} from '#/components/icons/ListMagnifyingGlass'
|
||||
import {ListSparkle_Stroke2_Corner0_Rounded} from '#/components/icons/ListSparkle'
|
||||
import {SettingsGear2_Stroke2_Corner0_Rounded as Gear} from '#/components/icons/SettingsGear2'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Link} from '#/components/Link'
|
||||
import * as ListCard from '#/components/ListCard'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Feeds'>
|
||||
@@ -102,7 +103,7 @@ type FlatlistSlice =
|
||||
export function FeedsScreen(_props: Props) {
|
||||
const pal = usePalette('default')
|
||||
const {openComposer} = useComposerControls()
|
||||
const {isMobile, isTabletOrDesktop} = useWebMediaQueries()
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const [query, setQuery] = React.useState('')
|
||||
const [isPTR, setIsPTR] = React.useState(false)
|
||||
const {
|
||||
@@ -374,22 +375,6 @@ export function FeedsScreen(_props: Props) {
|
||||
isUserSearching,
|
||||
])
|
||||
|
||||
const renderHeaderBtn = React.useCallback(() => {
|
||||
return (
|
||||
<View style={styles.headerBtnGroup}>
|
||||
<TextLink
|
||||
testID="editFeedsBtn"
|
||||
type="lg-medium"
|
||||
href="/settings/saved-feeds"
|
||||
accessibilityLabel={_(msg`Edit My Feeds`)}
|
||||
accessibilityHint=""
|
||||
text={_(msg`Edit`)}
|
||||
style={[pal.link, a.pr_xs]}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}, [pal, _])
|
||||
|
||||
const searchBarIndex = items.findIndex(
|
||||
item => item.type === 'popularFeedsHeader',
|
||||
)
|
||||
@@ -430,36 +415,7 @@ export function FeedsScreen(_props: Props) {
|
||||
</View>
|
||||
)
|
||||
} else if (item.type === 'savedFeedsHeader') {
|
||||
return (
|
||||
<>
|
||||
{!isMobile && (
|
||||
<View
|
||||
style={[
|
||||
pal.view,
|
||||
styles.header,
|
||||
pal.border,
|
||||
{
|
||||
borderBottomWidth: 1,
|
||||
},
|
||||
]}>
|
||||
<Text type="title-lg" style={[pal.text, s.bold]}>
|
||||
<Trans>Feeds</Trans>
|
||||
</Text>
|
||||
<View style={styles.headerBtnGroup}>
|
||||
<TextLink
|
||||
type="lg"
|
||||
href="/settings/saved-feeds"
|
||||
accessibilityLabel={_(msg`Edit My Feeds`)}
|
||||
accessibilityHint=""
|
||||
text={_(msg`Edit`)}
|
||||
style={[pal.link]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
<FeedsSavedHeader />
|
||||
</>
|
||||
)
|
||||
return <FeedsSavedHeader />
|
||||
} else if (item.type === 'savedFeedNoResults') {
|
||||
return (
|
||||
<View
|
||||
@@ -530,13 +486,8 @@ export function FeedsScreen(_props: Props) {
|
||||
return null
|
||||
},
|
||||
[
|
||||
isMobile,
|
||||
pal.view,
|
||||
pal.border,
|
||||
pal.text,
|
||||
pal.textLight,
|
||||
pal.link,
|
||||
_,
|
||||
query,
|
||||
onChangeQuery,
|
||||
onPressCancelSearch,
|
||||
@@ -547,31 +498,45 @@ export function FeedsScreen(_props: Props) {
|
||||
|
||||
return (
|
||||
<Layout.Screen testID="FeedsScreen">
|
||||
{isMobile && (
|
||||
<ViewHeader
|
||||
title={_(msg`Feeds`)}
|
||||
renderButton={renderHeaderBtn}
|
||||
showBorder
|
||||
/>
|
||||
)}
|
||||
<Layout.Center>
|
||||
<Layout.Header.Outer>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content>
|
||||
<Layout.Header.TitleText>
|
||||
<Trans>Feeds</Trans>
|
||||
</Layout.Header.TitleText>
|
||||
</Layout.Header.Content>
|
||||
<Layout.Header.Slot>
|
||||
<Link
|
||||
testID="editFeedsBtn"
|
||||
to="/settings/saved-feeds"
|
||||
label={_(msg`Edit My Feeds`)}
|
||||
size="small"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
shape="round"
|
||||
style={[a.justify_center, {right: -3}]}>
|
||||
<ButtonIcon icon={Gear} size="lg" />
|
||||
</Link>
|
||||
</Layout.Header.Slot>
|
||||
</Layout.Header.Outer>
|
||||
|
||||
<List
|
||||
ref={listRef}
|
||||
style={[!isTabletOrDesktop && s.flex1, styles.list]}
|
||||
data={items}
|
||||
keyExtractor={item => item.key}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
renderItem={renderItem}
|
||||
refreshing={isPTR}
|
||||
onRefresh={isUserSearching ? undefined : onPullToRefresh}
|
||||
initialNumToRender={10}
|
||||
onEndReached={onEndReached}
|
||||
// @ts-ignore our .web version only -prf
|
||||
desktopFixedHeight
|
||||
scrollIndicatorInsets={{right: 1}}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
keyboardDismissMode="on-drag"
|
||||
/>
|
||||
<List
|
||||
ref={listRef}
|
||||
data={items}
|
||||
keyExtractor={item => item.key}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
renderItem={renderItem}
|
||||
refreshing={isPTR}
|
||||
onRefresh={isUserSearching ? undefined : onPullToRefresh}
|
||||
initialNumToRender={10}
|
||||
onEndReached={onEndReached}
|
||||
desktopFixedHeight
|
||||
keyboardShouldPersistTaps="handled"
|
||||
keyboardDismissMode="on-drag"
|
||||
sideBorders={false}
|
||||
/>
|
||||
</Layout.Center>
|
||||
|
||||
{hasSession && (
|
||||
<FAB
|
||||
@@ -728,7 +693,7 @@ function FeedsSavedHeader() {
|
||||
}>
|
||||
<IconCircle icon={ListSparkle_Stroke2_Corner0_Rounded} size="lg" />
|
||||
<View style={[a.flex_1, a.gap_xs]}>
|
||||
<Text style={[a.flex_1, a.text_2xl, a.font_bold, t.atoms.text]}>
|
||||
<Text style={[a.flex_1, a.text_2xl, a.font_heavy, t.atoms.text]}>
|
||||
<Trans>My Feeds</Trans>
|
||||
</Text>
|
||||
<Text style={[t.atoms.text_contrast_high]}>
|
||||
@@ -754,7 +719,7 @@ function FeedsAboutHeader() {
|
||||
size="lg"
|
||||
/>
|
||||
<View style={[a.flex_1, a.gap_sm]}>
|
||||
<Text style={[a.flex_1, a.text_2xl, a.font_bold, t.atoms.text]}>
|
||||
<Text style={[a.flex_1, a.text_2xl, a.font_heavy, t.atoms.text]}>
|
||||
<Trans>Discover New Feeds</Trans>
|
||||
</Text>
|
||||
<Text style={[t.atoms.text_contrast_high]}>
|
||||
@@ -769,9 +734,6 @@ function FeedsAboutHeader() {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
list: {
|
||||
height: '100%',
|
||||
},
|
||||
contentContainer: {
|
||||
paddingBottom: 100,
|
||||
},
|
||||
|
||||
+26
-46
@@ -1,33 +1,26 @@
|
||||
import React from 'react'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
import {AtUri} from '@atproto/api'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect, useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {useEmail} from '#/lib/hooks/useEmail'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {NavigationProp} from '#/lib/routes/types'
|
||||
import {s} from '#/lib/styles'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {MyLists} from '#/view/com/lists/MyLists'
|
||||
import {Button} from '#/view/com/util/forms/Button'
|
||||
import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
|
||||
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Lists'>
|
||||
export function ListsScreen({}: Props) {
|
||||
const {_} = useLingui()
|
||||
const pal = usePalette('default')
|
||||
const setMinimalShellMode = useSetMinimalShellMode()
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {openModal} = useModalControls()
|
||||
const {needsEmailVerification} = useEmail()
|
||||
@@ -62,43 +55,30 @@ export function ListsScreen({}: Props) {
|
||||
|
||||
return (
|
||||
<Layout.Screen testID="listsScreen">
|
||||
<SimpleViewHeader
|
||||
showBackButton={isMobile}
|
||||
style={[
|
||||
pal.border,
|
||||
isMobile
|
||||
? {borderBottomWidth: StyleSheet.hairlineWidth}
|
||||
: {
|
||||
borderLeftWidth: StyleSheet.hairlineWidth,
|
||||
borderRightWidth: StyleSheet.hairlineWidth,
|
||||
},
|
||||
]}>
|
||||
<View style={{flex: 1}}>
|
||||
<Text type="title-lg" style={[pal.text, {fontWeight: '600'}]}>
|
||||
<Trans>User Lists</Trans>
|
||||
</Text>
|
||||
<Text style={pal.textLight}>
|
||||
<Layout.Header.Outer>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content align="left">
|
||||
<Layout.Header.TitleText>
|
||||
<Trans>Lists</Trans>
|
||||
</Layout.Header.TitleText>
|
||||
<Layout.Header.SubtitleText>
|
||||
<Trans>Public, shareable lists which can drive feeds.</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[{marginLeft: 18}, isMobile && {marginLeft: 12}]}>
|
||||
<Button
|
||||
testID="newUserListBtn"
|
||||
type="default"
|
||||
onPress={onPressNewList}
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
}}>
|
||||
<FontAwesomeIcon icon="plus" color={pal.colors.text} />
|
||||
<Text type="button" style={pal.text}>
|
||||
<Trans context="action">New</Trans>
|
||||
</Text>
|
||||
</Button>
|
||||
</View>
|
||||
</SimpleViewHeader>
|
||||
<MyLists filter="curate" style={s.flexGrow1} />
|
||||
</Layout.Header.SubtitleText>
|
||||
</Layout.Header.Content>
|
||||
<Button
|
||||
label={_(msg`New list`)}
|
||||
testID="newUserListBtn"
|
||||
color="secondary"
|
||||
variant="solid"
|
||||
size="small"
|
||||
onPress={onPressNewList}>
|
||||
<ButtonIcon icon={PlusIcon} />
|
||||
<ButtonText>
|
||||
<Trans context="action">New</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</Layout.Header.Outer>
|
||||
<MyLists filter="curate" style={a.flex_grow} />
|
||||
<VerifyEmailDialog
|
||||
reasonText={_(
|
||||
msg`Before creating a list, you must first verify your email.`,
|
||||
|
||||
@@ -23,7 +23,6 @@ import {ProfileCard} from '#/view/com/profile/ProfileCard'
|
||||
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
@@ -97,14 +96,7 @@ export function ModerationBlockedAccounts({}: Props) {
|
||||
)
|
||||
return (
|
||||
<Layout.Screen testID="blockedAccountsScreen">
|
||||
<CenteredView
|
||||
style={[
|
||||
styles.container,
|
||||
isTabletOrDesktop && styles.containerDesktop,
|
||||
pal.view,
|
||||
pal.border,
|
||||
]}
|
||||
testID="blockedAccountsScreen">
|
||||
<Layout.Center>
|
||||
<ViewHeader title={_(msg`Blocked Accounts`)} showOnDesktop />
|
||||
<Text
|
||||
type="sm"
|
||||
@@ -112,6 +104,9 @@ export function ModerationBlockedAccounts({}: Props) {
|
||||
styles.description,
|
||||
pal.text,
|
||||
isTabletOrDesktop && styles.descriptionDesktop,
|
||||
{
|
||||
marginTop: 20,
|
||||
},
|
||||
]}>
|
||||
<Trans>
|
||||
Blocked accounts cannot reply in your threads, mention you, or
|
||||
@@ -120,7 +115,7 @@ export function ModerationBlockedAccounts({}: Props) {
|
||||
</Trans>
|
||||
</Text>
|
||||
{isEmpty ? (
|
||||
<View style={[pal.border, !isTabletOrDesktop && styles.flex1]}>
|
||||
<View style={[pal.border]}>
|
||||
{isError ? (
|
||||
<ErrorScreen
|
||||
title="Oops!"
|
||||
@@ -166,21 +161,12 @@ export function ModerationBlockedAccounts({}: Props) {
|
||||
desktopFixedHeight
|
||||
/>
|
||||
)}
|
||||
</CenteredView>
|
||||
</Layout.Center>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingBottom: 100,
|
||||
},
|
||||
containerDesktop: {
|
||||
borderLeftWidth: 1,
|
||||
borderRightWidth: 1,
|
||||
paddingBottom: 0,
|
||||
},
|
||||
title: {
|
||||
textAlign: 'center',
|
||||
marginTop: 12,
|
||||
|
||||
@@ -1,33 +1,26 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {AtUri} from '@atproto/api'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect, useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {useEmail} from '#/lib/hooks/useEmail'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {NavigationProp} from '#/lib/routes/types'
|
||||
import {s} from '#/lib/styles'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {MyLists} from '#/view/com/lists/MyLists'
|
||||
import {Button} from '#/view/com/util/forms/Button'
|
||||
import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
|
||||
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ModerationModlists'>
|
||||
export function ModerationModlistsScreen({}: Props) {
|
||||
const {_} = useLingui()
|
||||
const pal = usePalette('default')
|
||||
const setMinimalShellMode = useSetMinimalShellMode()
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {openModal} = useModalControls()
|
||||
const {needsEmailVerification} = useEmail()
|
||||
@@ -62,39 +55,32 @@ export function ModerationModlistsScreen({}: Props) {
|
||||
|
||||
return (
|
||||
<Layout.Screen testID="moderationModlistsScreen">
|
||||
<SimpleViewHeader
|
||||
showBackButton={isMobile}
|
||||
style={
|
||||
!isMobile && [pal.border, {borderLeftWidth: 1, borderRightWidth: 1}]
|
||||
}>
|
||||
<View style={{flex: 1}}>
|
||||
<Text type="title-lg" style={[pal.text, {fontWeight: '600'}]}>
|
||||
<Layout.Header.Outer>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content align="left">
|
||||
<Layout.Header.TitleText>
|
||||
<Trans>Moderation Lists</Trans>
|
||||
</Text>
|
||||
<Text style={pal.textLight}>
|
||||
</Layout.Header.TitleText>
|
||||
<Layout.Header.SubtitleText>
|
||||
<Trans>
|
||||
Public, shareable lists of users to mute or block in bulk.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[{marginLeft: 18}, isMobile && {marginLeft: 12}]}>
|
||||
<Button
|
||||
testID="newModListBtn"
|
||||
type="default"
|
||||
onPress={onPressNewList}
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
}}>
|
||||
<FontAwesomeIcon icon="plus" color={pal.colors.text} />
|
||||
<Text type="button" style={pal.text}>
|
||||
<Trans>New</Trans>
|
||||
</Text>
|
||||
</Button>
|
||||
</View>
|
||||
</SimpleViewHeader>
|
||||
<MyLists filter="mod" style={s.flexGrow1} />
|
||||
</Layout.Header.SubtitleText>
|
||||
</Layout.Header.Content>
|
||||
<Button
|
||||
label={_(msg`New list`)}
|
||||
testID="newModListBtn"
|
||||
color="secondary"
|
||||
variant="solid"
|
||||
size="small"
|
||||
onPress={onPressNewList}>
|
||||
<ButtonIcon icon={PlusIcon} />
|
||||
<ButtonText>
|
||||
<Trans context="action">New</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</Layout.Header.Outer>
|
||||
<MyLists filter="mod" style={a.flex_grow} />
|
||||
<VerifyEmailDialog
|
||||
reasonText={_(
|
||||
msg`Before creating a list, you must first verify your email.`,
|
||||
|
||||
@@ -23,7 +23,6 @@ import {ProfileCard} from '#/view/com/profile/ProfileCard'
|
||||
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
@@ -97,21 +96,17 @@ export function ModerationMutedAccounts({}: Props) {
|
||||
)
|
||||
return (
|
||||
<Layout.Screen testID="mutedAccountsScreen">
|
||||
<CenteredView
|
||||
style={[
|
||||
styles.container,
|
||||
isTabletOrDesktop && styles.containerDesktop,
|
||||
pal.view,
|
||||
pal.border,
|
||||
]}
|
||||
testID="mutedAccountsScreen">
|
||||
<ViewHeader title={_(msg`Muted Accounts`)} showOnDesktop />
|
||||
<ViewHeader title={_(msg`Muted Accounts`)} showOnDesktop />
|
||||
<Layout.Center>
|
||||
<Text
|
||||
type="sm"
|
||||
style={[
|
||||
styles.description,
|
||||
pal.text,
|
||||
isTabletOrDesktop && styles.descriptionDesktop,
|
||||
{
|
||||
marginTop: 20,
|
||||
},
|
||||
]}>
|
||||
<Trans>
|
||||
Muted accounts have their posts removed from your feed and from your
|
||||
@@ -119,7 +114,7 @@ export function ModerationMutedAccounts({}: Props) {
|
||||
</Trans>
|
||||
</Text>
|
||||
{isEmpty ? (
|
||||
<View style={[pal.border, !isTabletOrDesktop && styles.flex1]}>
|
||||
<View style={[pal.border]}>
|
||||
{isError ? (
|
||||
<ErrorScreen
|
||||
title="Oops!"
|
||||
@@ -165,21 +160,12 @@ export function ModerationMutedAccounts({}: Props) {
|
||||
desktopFixedHeight
|
||||
/>
|
||||
)}
|
||||
</CenteredView>
|
||||
</Layout.Center>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingBottom: 100,
|
||||
},
|
||||
containerDesktop: {
|
||||
borderLeftWidth: 1,
|
||||
borderRightWidth: 1,
|
||||
paddingBottom: 0,
|
||||
},
|
||||
title: {
|
||||
textAlign: 'center',
|
||||
marginTop: 12,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useCallback} from 'react'
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
@@ -6,7 +6,6 @@ import {useFocusEffect, useIsFocused} from '@react-navigation/native'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {ComposeIcon2} from '#/lib/icons'
|
||||
import {
|
||||
NativeStackScreenProps,
|
||||
@@ -14,7 +13,7 @@ import {
|
||||
} from '#/lib/routes/types'
|
||||
import {s} from '#/lib/styles'
|
||||
import {logger} from '#/logger'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {isNative, isWeb} from '#/platform/detection'
|
||||
import {emitSoftReset, listenSoftReset} from '#/state/events'
|
||||
import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed'
|
||||
import {
|
||||
@@ -29,28 +28,25 @@ import {FAB} from '#/view/com/util/fab/FAB'
|
||||
import {ListMethods} from '#/view/com/util/List'
|
||||
import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
|
||||
import {MainScrollProvider} from '#/view/com/util/MainScrollProvider'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button} from '#/components/Button'
|
||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon} from '#/components/Button'
|
||||
import {SettingsGear2_Stroke2_Corner0_Rounded as SettingsIcon} from '#/components/icons/SettingsGear2'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Link} from '#/components/Link'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
NotificationsTabNavigatorParams,
|
||||
'Notifications'
|
||||
>
|
||||
export function NotificationsScreen({route: {params}}: Props) {
|
||||
const t = useTheme()
|
||||
const {gtTablet} = useBreakpoints()
|
||||
const {_} = useLingui()
|
||||
const setMinimalShellMode = useSetMinimalShellMode()
|
||||
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
|
||||
const [isLoadingLatest, setIsLoadingLatest] = React.useState(false)
|
||||
const scrollElRef = React.useRef<ListMethods>(null)
|
||||
const t = useTheme()
|
||||
const {isDesktop} = useWebMediaQueries()
|
||||
const queryClient = useQueryClient()
|
||||
const unreadNotifs = useUnreadNotifications()
|
||||
const unreadApi = useUnreadNotificationsApi()
|
||||
@@ -110,121 +106,77 @@ export function NotificationsScreen({route: {params}}: Props) {
|
||||
return listenSoftReset(onPressLoadLatest)
|
||||
}, [onPressLoadLatest, isScreenFocused])
|
||||
|
||||
const renderButton = useCallback(() => {
|
||||
return (
|
||||
<Link
|
||||
to="/notifications/settings"
|
||||
label={_(msg`Notification settings`)}
|
||||
size="small"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
shape="square"
|
||||
style={[a.justify_center]}>
|
||||
<SettingsIcon size="md" style={t.atoms.text_contrast_medium} />
|
||||
</Link>
|
||||
)
|
||||
}, [_, t])
|
||||
|
||||
const ListHeaderComponent = React.useCallback(() => {
|
||||
if (isDesktop) {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
t.atoms.bg,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_between,
|
||||
a.gap_lg,
|
||||
a.px_lg,
|
||||
a.pr_md,
|
||||
a.py_sm,
|
||||
]}>
|
||||
return (
|
||||
<Layout.Screen testID="notificationsScreen">
|
||||
<Layout.Header.Outer>
|
||||
<Layout.Header.MenuButton />
|
||||
<Layout.Header.Content>
|
||||
<Button
|
||||
label={_(msg`Notifications`)}
|
||||
accessibilityHint={_(msg`Refresh notifications`)}
|
||||
onPress={emitSoftReset}>
|
||||
{({hovered, pressed}) => (
|
||||
<Text
|
||||
style={[
|
||||
a.text_2xl,
|
||||
a.font_bold,
|
||||
(hovered || pressed) && a.underline,
|
||||
]}>
|
||||
onPress={emitSoftReset}
|
||||
style={[a.justify_start]}>
|
||||
{({hovered}) => (
|
||||
<Layout.Header.TitleText
|
||||
style={[a.w_full, hovered && a.underline]}>
|
||||
<Trans>Notifications</Trans>
|
||||
{hasNew && (
|
||||
{isWeb && gtTablet && hasNew && (
|
||||
<View
|
||||
style={{
|
||||
left: 4,
|
||||
top: -8,
|
||||
backgroundColor: t.palette.primary_500,
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
}}
|
||||
style={[
|
||||
a.rounded_full,
|
||||
{
|
||||
width: 8,
|
||||
height: 8,
|
||||
bottom: 3,
|
||||
left: 6,
|
||||
backgroundColor: t.palette.primary_500,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Text>
|
||||
</Layout.Header.TitleText>
|
||||
)}
|
||||
</Button>
|
||||
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
|
||||
{isLoadingLatest ? <Loader size="md" /> : <></>}
|
||||
{renderButton()}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
return <></>
|
||||
}, [isDesktop, t, hasNew, renderButton, _, isLoadingLatest])
|
||||
</Layout.Header.Content>
|
||||
<Layout.Header.Slot>
|
||||
<Link
|
||||
to="/notifications/settings"
|
||||
label={_(msg`Notification settings`)}
|
||||
size="small"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
shape="round"
|
||||
style={[a.justify_center]}>
|
||||
<ButtonIcon
|
||||
icon={isLoadingLatest ? Loader : SettingsIcon}
|
||||
size="lg"
|
||||
/>
|
||||
</Link>
|
||||
</Layout.Header.Slot>
|
||||
</Layout.Header.Outer>
|
||||
|
||||
const renderHeaderSpinner = React.useCallback(() => {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
{width: 30, height: 20},
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_end,
|
||||
a.gap_md,
|
||||
]}>
|
||||
{isLoadingLatest ? <Loader width={20} /> : <></>}
|
||||
{renderButton()}
|
||||
</View>
|
||||
)
|
||||
}, [renderButton, isLoadingLatest])
|
||||
|
||||
return (
|
||||
<Layout.Screen testID="notificationsScreen">
|
||||
<CenteredView style={[a.flex_1, {paddingTop: 2}]} sideBorders={true}>
|
||||
<ViewHeader
|
||||
title={_(msg`Notifications`)}
|
||||
canGoBack={false}
|
||||
showBorder={true}
|
||||
renderButton={renderHeaderSpinner}
|
||||
<MainScrollProvider>
|
||||
<Feed
|
||||
onScrolledDownChange={setIsScrolledDown}
|
||||
scrollElRef={scrollElRef}
|
||||
overridePriorityNotifications={params?.show === 'all'}
|
||||
/>
|
||||
<MainScrollProvider>
|
||||
<Feed
|
||||
onScrolledDownChange={setIsScrolledDown}
|
||||
scrollElRef={scrollElRef}
|
||||
ListHeaderComponent={ListHeaderComponent}
|
||||
overridePriorityNotifications={params?.show === 'all'}
|
||||
/>
|
||||
</MainScrollProvider>
|
||||
{(isScrolledDown || hasNew) && (
|
||||
<LoadLatestBtn
|
||||
onPress={onPressLoadLatest}
|
||||
label={_(msg`Load new notifications`)}
|
||||
showIndicator={hasNew}
|
||||
/>
|
||||
)}
|
||||
<FAB
|
||||
testID="composeFAB"
|
||||
onPress={() => openComposer({})}
|
||||
icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`New post`)}
|
||||
accessibilityHint=""
|
||||
</MainScrollProvider>
|
||||
{(isScrolledDown || hasNew) && (
|
||||
<LoadLatestBtn
|
||||
onPress={onPressLoadLatest}
|
||||
label={_(msg`Load new notifications`)}
|
||||
showIndicator={hasNew}
|
||||
/>
|
||||
</CenteredView>
|
||||
)}
|
||||
<FAB
|
||||
testID="composeFAB"
|
||||
onPress={() => openComposer({})}
|
||||
icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`New post`)}
|
||||
accessibilityHint=""
|
||||
/>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {makeRecordUri} from '#/lib/strings/url-helpers'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {PostThread as PostThreadComponent} from '#/view/com/post-thread/PostThread'
|
||||
import {atoms as a} from '#/alf'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'>
|
||||
@@ -24,9 +22,7 @@ export function PostThreadScreen({route}: Props) {
|
||||
|
||||
return (
|
||||
<Layout.Screen testID="postThreadScreen">
|
||||
<View style={a.flex_1}>
|
||||
<PostThreadComponent uri={uri} />
|
||||
</View>
|
||||
<PostThreadComponent uri={uri} />
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -40,11 +40,9 @@ import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader'
|
||||
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
||||
import {FAB} from '#/view/com/util/fab/FAB'
|
||||
import {ListRef} from '#/view/com/util/List'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header'
|
||||
import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed'
|
||||
import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels'
|
||||
import {web} from '#/alf'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ScreenHider} from '#/components/moderation/ScreenHider'
|
||||
import {ProfileStarterPacks} from '#/components/StarterPack/ProfileStarterPacks'
|
||||
@@ -116,9 +114,9 @@ function ProfileScreenInner({route}: Props) {
|
||||
// Most pushes will happen here, since we will have only placeholder data
|
||||
if (isLoadingDid || isLoadingProfile || starterPacksQuery.isLoading) {
|
||||
return (
|
||||
<CenteredView sideBorders style={web({height: '100vh'})}>
|
||||
<Layout.Content>
|
||||
<ProfileHeaderLoading />
|
||||
</CenteredView>
|
||||
</Layout.Content>
|
||||
)
|
||||
}
|
||||
if (resolveError || profileError) {
|
||||
|
||||
@@ -49,7 +49,6 @@ import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
|
||||
import {LoadingScreen} from '#/view/com/util/LoadingScreen'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button as NewButton, ButtonText} from '#/components/Button'
|
||||
import {useRichText} from '#/components/hooks/useRichText'
|
||||
@@ -98,7 +97,7 @@ export function ProfileFeedScreen(props: Props) {
|
||||
if (error) {
|
||||
return (
|
||||
<Layout.Screen testID="profileFeedScreenError">
|
||||
<CenteredView>
|
||||
<Layout.Content>
|
||||
<View style={[pal.view, pal.border, styles.notFoundContainer]}>
|
||||
<Text type="title-lg" style={[pal.text, s.mb10]}>
|
||||
<Trans>Could not load feed</Trans>
|
||||
@@ -120,7 +119,7 @@ export function ProfileFeedScreen(props: Props) {
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</CenteredView>
|
||||
</Layout.Content>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
@@ -394,7 +393,7 @@ export function ProfileFeedScreenInner({
|
||||
])
|
||||
|
||||
return (
|
||||
<View style={s.hContentRegion}>
|
||||
<>
|
||||
<ReportDialog
|
||||
control={reportDialogControl}
|
||||
params={{
|
||||
@@ -434,7 +433,7 @@ export function ProfileFeedScreenInner({
|
||||
accessibilityHint=""
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import {ProfileFollowers as ProfileFollowersComponent} from '#/view/com/profile/
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ListHeaderDesktop} from '#/components/Lists'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollowers'>
|
||||
export const ProfileFollowersScreen = ({route}: Props) => {
|
||||
@@ -27,7 +26,6 @@ export const ProfileFollowersScreen = ({route}: Props) => {
|
||||
return (
|
||||
<Layout.Screen testID="profileFollowersScreen">
|
||||
<CenteredView sideBorders={true}>
|
||||
<ListHeaderDesktop title={_(msg`Followers`)} />
|
||||
<ViewHeader title={_(msg`Followers`)} showBorder={!isWeb} />
|
||||
<ProfileFollowersComponent name={name} />
|
||||
</CenteredView>
|
||||
|
||||
@@ -10,7 +10,6 @@ import {ProfileFollows as ProfileFollowsComponent} from '#/view/com/profile/Prof
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ListHeaderDesktop} from '#/components/Lists'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollows'>
|
||||
export const ProfileFollowsScreen = ({route}: Props) => {
|
||||
@@ -27,7 +26,6 @@ export const ProfileFollowsScreen = ({route}: Props) => {
|
||||
return (
|
||||
<Layout.Screen testID="profileFollowsScreen">
|
||||
<CenteredView sideBorders={true}>
|
||||
<ListHeaderDesktop title={_(msg`Following`)} />
|
||||
<ViewHeader title={_(msg`Following`)} showBorder={!isWeb} />
|
||||
<ProfileFollowsComponent name={name} />
|
||||
</CenteredView>
|
||||
|
||||
@@ -69,7 +69,6 @@ import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
|
||||
import {LoadingScreen} from '#/view/com/util/LoadingScreen'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import {ListHiddenScreen} from '#/screens/List/ListHiddenScreen'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
@@ -107,20 +106,20 @@ function ProfileListScreenInner(props: Props) {
|
||||
|
||||
if (resolveError) {
|
||||
return (
|
||||
<CenteredView>
|
||||
<Layout.Content>
|
||||
<ErrorScreen
|
||||
error={_(
|
||||
msg`We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @${handleOrDid}.`,
|
||||
)}
|
||||
/>
|
||||
</CenteredView>
|
||||
</Layout.Content>
|
||||
)
|
||||
}
|
||||
if (listError) {
|
||||
return (
|
||||
<CenteredView>
|
||||
<Layout.Content>
|
||||
<ErrorScreen error={cleanError(listError)} />
|
||||
</CenteredView>
|
||||
</Layout.Content>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1010,7 +1009,6 @@ function ErrorScreen({error}: {error: string}) {
|
||||
pal.view,
|
||||
pal.border,
|
||||
{
|
||||
marginTop: 10,
|
||||
paddingHorizontal: 18,
|
||||
paddingVertical: 14,
|
||||
borderTopWidth: StyleSheet.hairlineWidth,
|
||||
|
||||
+117
-127
@@ -25,13 +25,12 @@ import {FeedSourceCard} from '#/view/com/feeds/FeedSourceCard'
|
||||
import {TextLink} from '#/view/com/util/Link'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
||||
import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
|
||||
import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline'
|
||||
import {FloppyDisk_Stroke2_Corner0_Rounded as SaveIcon} from '#/components/icons/FloppyDisk'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Loader} from '#/components/Loader'
|
||||
|
||||
@@ -51,7 +50,7 @@ function SavedFeedsInner({
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const {isMobile, isTabletOrDesktop, isDesktop} = useWebMediaQueries()
|
||||
const {isMobile, isDesktop} = useWebMediaQueries()
|
||||
const setMinimalShellMode = useSetMinimalShellMode()
|
||||
const {mutateAsync: overwriteSavedFeeds, isPending: isOverwritePending} =
|
||||
useOverwriteSavedFeedsMutation()
|
||||
@@ -88,136 +87,128 @@ function SavedFeedsInner({
|
||||
}
|
||||
}, [_, overwriteSavedFeeds, currentFeeds, navigation])
|
||||
|
||||
const renderHeaderBtn = React.useCallback(() => {
|
||||
return (
|
||||
<Button
|
||||
size="small"
|
||||
variant={hasUnsavedChanges ? 'solid' : 'solid'}
|
||||
color={hasUnsavedChanges ? 'primary' : 'secondary'}
|
||||
onPress={onSaveChanges}
|
||||
label={_(msg`Save changes`)}
|
||||
disabled={isOverwritePending || !hasUnsavedChanges}
|
||||
style={[isDesktop && a.mt_sm]}
|
||||
testID="saveChangesBtn">
|
||||
<ButtonText style={[isDesktop && a.text_md]}>
|
||||
{isDesktop ? <Trans>Save changes</Trans> : <Trans>Save</Trans>}
|
||||
</ButtonText>
|
||||
{isOverwritePending && <ButtonIcon icon={Loader} />}
|
||||
</Button>
|
||||
)
|
||||
}, [_, isDesktop, onSaveChanges, hasUnsavedChanges, isOverwritePending])
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<CenteredView
|
||||
style={[a.util_screen_outer]}
|
||||
sideBorders={isTabletOrDesktop}>
|
||||
<ViewHeader
|
||||
title={_(msg`Edit My Feeds`)}
|
||||
showOnDesktop
|
||||
showBorder
|
||||
renderButton={renderHeaderBtn}
|
||||
/>
|
||||
<ScrollView style={[a.flex_1]} contentContainerStyle={[a.border_0]}>
|
||||
{noSavedFeedsOfAnyType && (
|
||||
<View style={[pal.border, a.border_b]}>
|
||||
<NoSavedFeedsOfAnyType />
|
||||
<Layout.Header.Outer>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content align="left">
|
||||
<Layout.Header.TitleText>
|
||||
<Trans>Feeds</Trans>
|
||||
</Layout.Header.TitleText>
|
||||
</Layout.Header.Content>
|
||||
<Button
|
||||
testID="saveChangesBtn"
|
||||
size="small"
|
||||
variant={hasUnsavedChanges ? 'solid' : 'solid'}
|
||||
color={hasUnsavedChanges ? 'primary' : 'secondary'}
|
||||
onPress={onSaveChanges}
|
||||
label={_(msg`Save changes`)}
|
||||
disabled={isOverwritePending || !hasUnsavedChanges}>
|
||||
<ButtonIcon icon={isOverwritePending ? Loader : SaveIcon} />
|
||||
<ButtonText>
|
||||
{isDesktop ? <Trans>Save changes</Trans> : <Trans>Save</Trans>}
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</Layout.Header.Outer>
|
||||
|
||||
<Layout.Content>
|
||||
{noSavedFeedsOfAnyType && (
|
||||
<View style={[pal.border, a.border_b]}>
|
||||
<NoSavedFeedsOfAnyType />
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={[pal.text, pal.border, styles.title]}>
|
||||
<Text type="title" style={pal.text}>
|
||||
<Trans>Pinned Feeds</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{preferences ? (
|
||||
!pinnedFeeds.length ? (
|
||||
<View
|
||||
style={[
|
||||
pal.border,
|
||||
isMobile && s.flex1,
|
||||
pal.viewLight,
|
||||
styles.empty,
|
||||
]}>
|
||||
<Text type="lg" style={[pal.text]}>
|
||||
<Trans>You don't have any pinned feeds.</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={[pal.text, pal.border, styles.title]}>
|
||||
<Text type="title" style={pal.text}>
|
||||
<Trans>Pinned Feeds</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{preferences ? (
|
||||
!pinnedFeeds.length ? (
|
||||
<View
|
||||
style={[
|
||||
pal.border,
|
||||
isMobile && s.flex1,
|
||||
pal.viewLight,
|
||||
styles.empty,
|
||||
]}>
|
||||
<Text type="lg" style={[pal.text]}>
|
||||
<Trans>You don't have any pinned feeds.</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
pinnedFeeds.map(f => (
|
||||
<ListItem
|
||||
key={f.id}
|
||||
feed={f}
|
||||
isPinned
|
||||
currentFeeds={currentFeeds}
|
||||
setCurrentFeeds={setCurrentFeeds}
|
||||
preferences={preferences}
|
||||
/>
|
||||
))
|
||||
)
|
||||
) : (
|
||||
<ActivityIndicator style={{marginTop: 20}} />
|
||||
)}
|
||||
pinnedFeeds.map(f => (
|
||||
<ListItem
|
||||
key={f.id}
|
||||
feed={f}
|
||||
isPinned
|
||||
currentFeeds={currentFeeds}
|
||||
setCurrentFeeds={setCurrentFeeds}
|
||||
preferences={preferences}
|
||||
/>
|
||||
))
|
||||
)
|
||||
) : (
|
||||
<ActivityIndicator style={{marginTop: 20}} />
|
||||
)}
|
||||
|
||||
{noFollowingFeed && (
|
||||
<View style={[pal.border, a.border_b]}>
|
||||
<NoFollowingFeed />
|
||||
{noFollowingFeed && (
|
||||
<View style={[pal.border, a.border_b]}>
|
||||
<NoFollowingFeed />
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={[pal.text, pal.border, styles.title]}>
|
||||
<Text type="title" style={pal.text}>
|
||||
<Trans>Saved Feeds</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
{preferences ? (
|
||||
!unpinnedFeeds.length ? (
|
||||
<View
|
||||
style={[
|
||||
pal.border,
|
||||
isMobile && s.flex1,
|
||||
pal.viewLight,
|
||||
styles.empty,
|
||||
]}>
|
||||
<Text type="lg" style={[pal.text]}>
|
||||
<Trans>You don't have any saved feeds.</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={[pal.text, pal.border, styles.title]}>
|
||||
<Text type="title" style={pal.text}>
|
||||
<Trans>Saved Feeds</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
{preferences ? (
|
||||
!unpinnedFeeds.length ? (
|
||||
<View
|
||||
style={[
|
||||
pal.border,
|
||||
isMobile && s.flex1,
|
||||
pal.viewLight,
|
||||
styles.empty,
|
||||
]}>
|
||||
<Text type="lg" style={[pal.text]}>
|
||||
<Trans>You don't have any saved feeds.</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
unpinnedFeeds.map(f => (
|
||||
<ListItem
|
||||
key={f.id}
|
||||
feed={f}
|
||||
isPinned={false}
|
||||
currentFeeds={currentFeeds}
|
||||
setCurrentFeeds={setCurrentFeeds}
|
||||
preferences={preferences}
|
||||
/>
|
||||
))
|
||||
)
|
||||
) : (
|
||||
<ActivityIndicator style={{marginTop: 20}} />
|
||||
)}
|
||||
unpinnedFeeds.map(f => (
|
||||
<ListItem
|
||||
key={f.id}
|
||||
feed={f}
|
||||
isPinned={false}
|
||||
currentFeeds={currentFeeds}
|
||||
setCurrentFeeds={setCurrentFeeds}
|
||||
preferences={preferences}
|
||||
/>
|
||||
))
|
||||
)
|
||||
) : (
|
||||
<ActivityIndicator style={{marginTop: 20}} />
|
||||
)}
|
||||
|
||||
<View style={styles.footerText}>
|
||||
<Text type="sm" style={pal.textLight}>
|
||||
<Trans>
|
||||
Feeds are custom algorithms that users build with a little
|
||||
coding expertise.{' '}
|
||||
<TextLink
|
||||
type="sm"
|
||||
style={pal.link}
|
||||
href="https://github.com/bluesky-social/feed-generator"
|
||||
text={_(msg`See this guide`)}
|
||||
/>{' '}
|
||||
for more information.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{height: 100}} />
|
||||
</ScrollView>
|
||||
</CenteredView>
|
||||
<View style={styles.footerText}>
|
||||
<Text type="sm" style={pal.textLight}>
|
||||
<Trans>
|
||||
Feeds are custom algorithms that users build with a little coding
|
||||
expertise.{' '}
|
||||
<TextLink
|
||||
type="sm"
|
||||
style={pal.link}
|
||||
href="https://github.com/bluesky-social/feed-generator"
|
||||
text={_(msg`See this guide`)}
|
||||
/>{' '}
|
||||
for more information.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
</Layout.Content>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
@@ -456,7 +447,6 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
footerText: {
|
||||
paddingHorizontal: 26,
|
||||
paddingTop: 22,
|
||||
paddingBottom: 100,
|
||||
paddingVertical: 22,
|
||||
},
|
||||
})
|
||||
|
||||
+105
-131
@@ -55,7 +55,6 @@ import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
|
||||
import {Link} from '#/view/com/util/Link'
|
||||
import {List} from '#/view/com/util/List'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
||||
import {Explore} from '#/view/screens/Search/Explore'
|
||||
import {SearchLinkCard, SearchProfileCard} from '#/view/shell/desktop/Search'
|
||||
import {makeSearchQuery, parseSearchQuery} from '#/screens/Search/utils'
|
||||
@@ -68,63 +67,46 @@ import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
function Loader() {
|
||||
const pal = usePalette('default')
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
return (
|
||||
<CenteredView
|
||||
style={[
|
||||
// @ts-ignore web only -prf
|
||||
{
|
||||
padding: 18,
|
||||
height: isWeb ? '100vh' : undefined,
|
||||
},
|
||||
pal.border,
|
||||
]}
|
||||
sideBorders={!isMobile}>
|
||||
<ActivityIndicator />
|
||||
</CenteredView>
|
||||
<Layout.Content>
|
||||
<View style={[a.py_xl]}>
|
||||
<ActivityIndicator />
|
||||
</View>
|
||||
</Layout.Content>
|
||||
)
|
||||
}
|
||||
|
||||
function EmptyState({message, error}: {message: string; error?: string}) {
|
||||
const pal = usePalette('default')
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
|
||||
return (
|
||||
<CenteredView
|
||||
sideBorders={!isMobile}
|
||||
style={[
|
||||
pal.border,
|
||||
// @ts-ignore web only -prf
|
||||
{
|
||||
padding: 18,
|
||||
height: isWeb ? '100vh' : undefined,
|
||||
},
|
||||
]}>
|
||||
<View style={[pal.viewLight, {padding: 18, borderRadius: 8}]}>
|
||||
<Text style={[pal.text]}>{message}</Text>
|
||||
<Layout.Content>
|
||||
<View style={[a.p_xl]}>
|
||||
<View style={[pal.viewLight, {padding: 18, borderRadius: 8}]}>
|
||||
<Text style={[pal.text]}>{message}</Text>
|
||||
|
||||
{error && (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
marginVertical: 12,
|
||||
height: 1,
|
||||
width: '100%',
|
||||
backgroundColor: pal.text.color,
|
||||
opacity: 0.2,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{error && (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
marginVertical: 12,
|
||||
height: 1,
|
||||
width: '100%',
|
||||
backgroundColor: pal.text.color,
|
||||
opacity: 0.2,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Text style={[pal.textLight]}>
|
||||
<Trans>Error:</Trans> {error}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
<Text style={[pal.textLight]}>
|
||||
<Trans>Error:</Trans> {error}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</CenteredView>
|
||||
</Layout.Content>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -224,7 +206,7 @@ let SearchScreenPostResults = ({
|
||||
if (item.type === 'post') {
|
||||
return <Post post={item.post} />
|
||||
} else {
|
||||
return <Loader />
|
||||
return null
|
||||
}
|
||||
}}
|
||||
keyExtractor={item => item.key}
|
||||
@@ -550,19 +532,13 @@ let SearchScreenInner = ({
|
||||
<Pager
|
||||
onPageSelected={onPageSelected}
|
||||
renderTabBar={props => (
|
||||
<CenteredView
|
||||
sideBorders
|
||||
<Layout.Center
|
||||
style={[
|
||||
pal.border,
|
||||
pal.view,
|
||||
web({
|
||||
position: isWeb ? 'sticky' : '',
|
||||
zIndex: 1,
|
||||
}),
|
||||
web([a.sticky, a.z_10]),
|
||||
{top: isWeb ? headerHeight : undefined},
|
||||
]}>
|
||||
<TabBar items={sections.map(section => section.title)} {...props} />
|
||||
</CenteredView>
|
||||
</Layout.Center>
|
||||
)}
|
||||
initialPage={0}>
|
||||
{sections.map((section, i) => (
|
||||
@@ -572,7 +548,7 @@ let SearchScreenInner = ({
|
||||
) : hasSession ? (
|
||||
<Explore />
|
||||
) : (
|
||||
<CenteredView sideBorders style={pal.border}>
|
||||
<Layout.Center>
|
||||
<View
|
||||
// @ts-ignore web only -esb
|
||||
style={{
|
||||
@@ -614,7 +590,7 @@ let SearchScreenInner = ({
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</CenteredView>
|
||||
</Layout.Center>
|
||||
)
|
||||
}
|
||||
SearchScreenInner = React.memo(SearchScreenInner)
|
||||
@@ -650,7 +626,7 @@ export function SearchScreen(
|
||||
* Arbitrary sizing, so guess and check, used for sticky header alignment and
|
||||
* sizing.
|
||||
*/
|
||||
const headerHeight = 64 + (showFilters ? 40 : 0)
|
||||
const headerHeight = 60 + (showFilters ? 40 : 0)
|
||||
|
||||
useFocusEffect(
|
||||
useNonReactiveCallback(() => {
|
||||
@@ -861,73 +837,79 @@ export function SearchScreen(
|
||||
|
||||
return (
|
||||
<Layout.Screen testID="searchScreen">
|
||||
<CenteredView
|
||||
<View
|
||||
style={[
|
||||
a.p_md,
|
||||
a.pb_sm,
|
||||
a.gap_sm,
|
||||
t.atoms.bg,
|
||||
web({
|
||||
height: headerHeight,
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 1,
|
||||
}),
|
||||
]}
|
||||
sideBorders={gtMobile}>
|
||||
<View style={[a.flex_row, a.gap_sm]}>
|
||||
{!gtMobile && (
|
||||
<Button
|
||||
testID="viewHeaderBackOrMenuBtn"
|
||||
onPress={onPressMenu}
|
||||
hitSlop={HITSLOP_10}
|
||||
label={_(msg`Menu`)}
|
||||
accessibilityHint={_(msg`Access navigation links and settings`)}
|
||||
size="large"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
shape="square">
|
||||
<ButtonIcon icon={Menu} size="lg" />
|
||||
</Button>
|
||||
)}
|
||||
<View style={[a.flex_1]}>
|
||||
<SearchInput
|
||||
ref={textInput}
|
||||
value={searchText}
|
||||
onFocus={onSearchInputFocus}
|
||||
onChangeText={onChangeText}
|
||||
onClearText={onPressClearQuery}
|
||||
onSubmitEditing={onSubmit}
|
||||
/>
|
||||
</View>
|
||||
{showAutocomplete && (
|
||||
<Button
|
||||
label={_(msg`Cancel search`)}
|
||||
size="large"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
style={[a.px_sm]}
|
||||
onPress={onPressCancelSearch}
|
||||
hitSlop={HITSLOP_10}>
|
||||
<ButtonText>
|
||||
<Trans>Cancel</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{showFilters && (
|
||||
<View
|
||||
style={[a.flex_row, a.align_center, a.justify_between, a.gap_sm]}>
|
||||
<View style={[{width: 140}]}>
|
||||
<SearchLanguageDropdown
|
||||
value={params.lang}
|
||||
onChange={params.setLang}
|
||||
/>
|
||||
]}>
|
||||
<Layout.Center>
|
||||
<View style={[a.p_md, a.pb_sm, a.gap_sm, t.atoms.bg]}>
|
||||
<View style={[a.flex_row, a.gap_sm]}>
|
||||
{!gtMobile && (
|
||||
<Button
|
||||
testID="viewHeaderBackOrMenuBtn"
|
||||
onPress={onPressMenu}
|
||||
hitSlop={HITSLOP_10}
|
||||
label={_(msg`Menu`)}
|
||||
accessibilityHint={_(
|
||||
msg`Access navigation links and settings`,
|
||||
)}
|
||||
size="large"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
shape="square">
|
||||
<ButtonIcon icon={Menu} size="lg" />
|
||||
</Button>
|
||||
)}
|
||||
<View style={[a.flex_1]}>
|
||||
<SearchInput
|
||||
ref={textInput}
|
||||
value={searchText}
|
||||
onFocus={onSearchInputFocus}
|
||||
onChangeText={onChangeText}
|
||||
onClearText={onPressClearQuery}
|
||||
onSubmitEditing={onSubmit}
|
||||
/>
|
||||
</View>
|
||||
{showAutocomplete && (
|
||||
<Button
|
||||
label={_(msg`Cancel search`)}
|
||||
size="large"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
style={[a.px_sm]}
|
||||
onPress={onPressCancelSearch}
|
||||
hitSlop={HITSLOP_10}>
|
||||
<ButtonText>
|
||||
<Trans>Cancel</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{showFilters && (
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_between,
|
||||
a.gap_sm,
|
||||
]}>
|
||||
<View style={[{width: 140}]}>
|
||||
<SearchLanguageDropdown
|
||||
value={params.lang}
|
||||
onChange={params.setLang}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</CenteredView>
|
||||
</Layout.Center>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
@@ -992,10 +974,7 @@ let AutocompleteResults = ({
|
||||
!moderationOpts ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<ScrollView
|
||||
style={{height: '100%'}}
|
||||
// @ts-ignore web only -prf
|
||||
dataSet={{stableGutters: '1'}}
|
||||
<Layout.Content
|
||||
keyboardShouldPersistTaps="handled"
|
||||
keyboardDismissMode="on-drag">
|
||||
<SearchLinkCard
|
||||
@@ -1020,7 +999,7 @@ let AutocompleteResults = ({
|
||||
/>
|
||||
))}
|
||||
<View style={{height: 200}} />
|
||||
</ScrollView>
|
||||
</Layout.Content>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
@@ -1042,17 +1021,12 @@ function SearchHistory({
|
||||
onRemoveItemClick: (item: string) => void
|
||||
onRemoveProfileClick: (profile: AppBskyActorDefs.ProfileViewBasic) => void
|
||||
}) {
|
||||
const {isTabletOrDesktop, isMobile} = useWebMediaQueries()
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
|
||||
return (
|
||||
<CenteredView
|
||||
sideBorders={isTabletOrDesktop}
|
||||
// @ts-ignore web only -prf
|
||||
style={{
|
||||
height: isWeb ? '100vh' : undefined,
|
||||
}}>
|
||||
<Layout.Content>
|
||||
<View style={styles.searchHistoryContainer}>
|
||||
{(searchHistory.length > 0 || selectedProfiles.length > 0) && (
|
||||
<Text style={[pal.text, styles.searchHistoryTitle]}>
|
||||
@@ -1152,7 +1126,7 @@ function SearchHistory({
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</CenteredView>
|
||||
</Layout.Content>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import {StyleSheet, View} from 'react-native'
|
||||
import {DismissableLayer} from '@radix-ui/react-dismissable-layer'
|
||||
import {useFocusGuards} from '@radix-ui/react-focus-guards'
|
||||
import {FocusScope} from '@radix-ui/react-focus-scope'
|
||||
import {RemoveScrollBar} from 'react-remove-scroll-bar'
|
||||
|
||||
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
|
||||
import {useModals} from '#/state/modals'
|
||||
import {ComposerOpts, useComposerState} from '#/state/shell/composer'
|
||||
import {
|
||||
@@ -20,8 +20,6 @@ export function Composer({}: {winHeight: number}) {
|
||||
const state = useComposerState()
|
||||
const isActive = !!state
|
||||
|
||||
useWebBodyScrollLock(isActive)
|
||||
|
||||
// rendering
|
||||
// =
|
||||
|
||||
@@ -29,7 +27,12 @@ export function Composer({}: {winHeight: number}) {
|
||||
return <View />
|
||||
}
|
||||
|
||||
return <Inner state={state} />
|
||||
return (
|
||||
<>
|
||||
<RemoveScrollBar />
|
||||
<Inner state={state} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function Inner({state}: {state: ComposerOpts}) {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import React from 'react'
|
||||
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||
import {
|
||||
FontAwesomeIcon,
|
||||
FontAwesomeIconStyle,
|
||||
} from '@fortawesome/react-native-fontawesome'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {
|
||||
@@ -14,9 +11,9 @@ import {
|
||||
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {getCurrentRoute, isStateAtTabRoot, isTab} from '#/lib/routes/helpers'
|
||||
import {getCurrentRoute, isTab} from '#/lib/routes/helpers'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
|
||||
import {CommonNavigatorParams} from '#/lib/routes/types'
|
||||
import {isInvalidHandle} from '#/lib/strings/handles'
|
||||
import {emitSoftReset} from '#/state/events'
|
||||
import {useFetchHandle} from '#/state/queries/handle'
|
||||
@@ -101,47 +98,6 @@ function ProfileCard() {
|
||||
)
|
||||
}
|
||||
|
||||
const HIDDEN_BACK_BNT_ROUTES = ['StarterPackWizard', 'StarterPackEdit']
|
||||
|
||||
function BackBtn() {
|
||||
const {isTablet} = useWebMediaQueries()
|
||||
const pal = usePalette('default')
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const {_} = useLingui()
|
||||
const shouldShow = useNavigationState(
|
||||
state =>
|
||||
!isStateAtTabRoot(state) &&
|
||||
!HIDDEN_BACK_BNT_ROUTES.includes(getCurrentRoute(state).name),
|
||||
)
|
||||
|
||||
const onPressBack = React.useCallback(() => {
|
||||
if (navigation.canGoBack()) {
|
||||
navigation.goBack()
|
||||
} else {
|
||||
navigation.navigate('Home')
|
||||
}
|
||||
}, [navigation])
|
||||
|
||||
if (!shouldShow || isTablet) {
|
||||
return <></>
|
||||
}
|
||||
return (
|
||||
<TouchableOpacity
|
||||
testID="viewHeaderBackOrMenuBtn"
|
||||
onPress={onPressBack}
|
||||
style={styles.backBtn}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Go back`)}
|
||||
accessibilityHint="">
|
||||
<FontAwesomeIcon
|
||||
size={24}
|
||||
icon="angle-left"
|
||||
style={pal.text as FontAwesomeIconStyle}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
}
|
||||
|
||||
interface NavItemProps {
|
||||
count?: string
|
||||
href: string
|
||||
@@ -220,35 +176,44 @@ function NavItem({count, href, icon, iconFilled, label}: NavItemProps) {
|
||||
]}>
|
||||
{isCurrent ? iconFilled : icon}
|
||||
{typeof count === 'string' && count ? (
|
||||
<Text
|
||||
accessibilityLabel={_(msg`${count} unread items`)}
|
||||
accessibilityHint=""
|
||||
accessible={true}
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
a.text_xs,
|
||||
a.font_bold,
|
||||
a.rounded_full,
|
||||
a.text_center,
|
||||
{
|
||||
top: '-10%',
|
||||
left: count.length === 1 ? '50%' : '40%',
|
||||
backgroundColor: t.palette.primary_500,
|
||||
color: t.palette.white,
|
||||
lineHeight: a.text_sm.fontSize,
|
||||
paddingHorizontal: 4,
|
||||
paddingVertical: 1,
|
||||
minWidth: 16,
|
||||
},
|
||||
isTablet && [
|
||||
{
|
||||
top: '10%',
|
||||
left: count.length === 1 ? '50%' : '40%',
|
||||
},
|
||||
],
|
||||
a.inset_0,
|
||||
{right: -20}, // more breathing room
|
||||
]}>
|
||||
{count}
|
||||
</Text>
|
||||
<Text
|
||||
accessibilityLabel={_(msg`${count} unread items`)}
|
||||
accessibilityHint=""
|
||||
accessible={true}
|
||||
numberOfLines={1}
|
||||
style={[
|
||||
a.absolute,
|
||||
a.text_xs,
|
||||
a.font_bold,
|
||||
a.rounded_full,
|
||||
a.text_center,
|
||||
a.leading_tight,
|
||||
{
|
||||
top: '-10%',
|
||||
left: count.length === 1 ? 12 : 8,
|
||||
backgroundColor: t.palette.primary_500,
|
||||
color: t.palette.white,
|
||||
lineHeight: a.text_sm.fontSize,
|
||||
paddingHorizontal: 4,
|
||||
paddingVertical: 1,
|
||||
minWidth: 16,
|
||||
},
|
||||
isTablet && [
|
||||
{
|
||||
top: '10%',
|
||||
left: count.length === 1 ? 20 : 16,
|
||||
},
|
||||
],
|
||||
]}>
|
||||
{count}
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
{gtTablet && (
|
||||
@@ -366,9 +331,9 @@ export function DesktopLeftNav() {
|
||||
<View
|
||||
role="navigation"
|
||||
style={[
|
||||
a.px_xl,
|
||||
styles.leftNav,
|
||||
isTablet && styles.leftNavTablet,
|
||||
pal.view,
|
||||
pal.border,
|
||||
]}>
|
||||
{hasSession ? (
|
||||
@@ -381,8 +346,6 @@ export function DesktopLeftNav() {
|
||||
|
||||
{hasSession && (
|
||||
<>
|
||||
<BackBtn />
|
||||
|
||||
<NavItem
|
||||
href="/"
|
||||
icon={
|
||||
@@ -525,8 +488,17 @@ const styles = StyleSheet.create({
|
||||
position: 'fixed',
|
||||
top: 10,
|
||||
// @ts-ignore web only
|
||||
left: 'calc(50vw - 300px - 220px - 20px)',
|
||||
width: 220,
|
||||
left: '50%',
|
||||
transform: [
|
||||
{
|
||||
translateX: -300,
|
||||
},
|
||||
{
|
||||
translateX: '-100%',
|
||||
},
|
||||
...a.scrollbar_offset.transform,
|
||||
],
|
||||
width: 240,
|
||||
// @ts-ignore web only
|
||||
maxHeight: 'calc(100vh - 10px)',
|
||||
overflowY: 'auto',
|
||||
@@ -538,7 +510,10 @@ const styles = StyleSheet.create({
|
||||
borderRightWidth: 1,
|
||||
height: '100%',
|
||||
width: 76,
|
||||
paddingLeft: 0,
|
||||
paddingRight: 0,
|
||||
alignItems: 'center',
|
||||
transform: [],
|
||||
},
|
||||
|
||||
profileCard: {
|
||||
|
||||
@@ -28,7 +28,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.rightNav, pal.view]}>
|
||||
<View style={[a.px_xl, styles.rightNav]}>
|
||||
<View style={{paddingVertical: 20}}>
|
||||
{routeName === 'Search' ? (
|
||||
<View style={{marginBottom: 18}}>
|
||||
@@ -122,8 +122,13 @@ const styles = StyleSheet.create({
|
||||
// @ts-ignore web only
|
||||
position: 'fixed',
|
||||
// @ts-ignore web only
|
||||
left: 'calc(50vw + 300px + 20px)',
|
||||
width: 300,
|
||||
left: '50%',
|
||||
transform: [
|
||||
{
|
||||
translateX: 300,
|
||||
},
|
||||
...a.scrollbar_offset.transform,
|
||||
],
|
||||
maxHeight: '100%',
|
||||
overflowY: 'auto',
|
||||
},
|
||||
|
||||
@@ -3,10 +3,10 @@ import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {RemoveScrollBar} from 'react-remove-scroll-bar'
|
||||
|
||||
import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
|
||||
import {useIntentHandler} from '#/lib/hooks/useIntentHandler'
|
||||
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {NavigationProp} from '#/lib/routes/types'
|
||||
import {colors} from '#/lib/styles'
|
||||
@@ -34,7 +34,6 @@ function ShellInner() {
|
||||
const {_} = useLingui()
|
||||
const showDrawer = !isDesktop && isDrawerOpen
|
||||
|
||||
useWebBodyScrollLock(showDrawer)
|
||||
useComposerKeyboardShortcut()
|
||||
useIntentHandler()
|
||||
|
||||
@@ -58,31 +57,34 @@ function ShellInner() {
|
||||
<PortalOutlet />
|
||||
|
||||
{showDrawer && (
|
||||
<TouchableWithoutFeedback
|
||||
onPress={ev => {
|
||||
// Only close if press happens outside of the drawer
|
||||
if (ev.target === ev.currentTarget) {
|
||||
setDrawerOpen(false)
|
||||
}
|
||||
}}
|
||||
accessibilityLabel={_(msg`Close navigation footer`)}
|
||||
accessibilityHint={_(msg`Closes bottom navigation bar`)}>
|
||||
<View
|
||||
style={[
|
||||
styles.drawerMask,
|
||||
{
|
||||
backgroundColor: select(t.name, {
|
||||
light: 'rgba(0, 57, 117, 0.1)',
|
||||
dark: 'rgba(1, 82, 168, 0.1)',
|
||||
dim: 'rgba(10, 13, 16, 0.8)',
|
||||
}),
|
||||
},
|
||||
]}>
|
||||
<View style={styles.drawerContainer}>
|
||||
<DrawerContent />
|
||||
<>
|
||||
<RemoveScrollBar />
|
||||
<TouchableWithoutFeedback
|
||||
onPress={ev => {
|
||||
// Only close if press happens outside of the drawer
|
||||
if (ev.target === ev.currentTarget) {
|
||||
setDrawerOpen(false)
|
||||
}
|
||||
}}
|
||||
accessibilityLabel={_(msg`Close navigation footer`)}
|
||||
accessibilityHint={_(msg`Closes bottom navigation bar`)}>
|
||||
<View
|
||||
style={[
|
||||
styles.drawerMask,
|
||||
{
|
||||
backgroundColor: select(t.name, {
|
||||
light: 'rgba(0, 57, 117, 0.1)',
|
||||
dark: 'rgba(1, 82, 168, 0.1)',
|
||||
dim: 'rgba(10, 13, 16, 0.8)',
|
||||
}),
|
||||
},
|
||||
]}>
|
||||
<View style={styles.drawerContainer}>
|
||||
<DrawerContent />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</TouchableWithoutFeedback>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user