[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:
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M3 4a1 1 0 0 1 1-1h13a1 1 0 0 1 .707.293l3 3A1 1 0 0 1 21 7v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm6 15h6v-5H9v5Zm8 0v-6a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v6H5V5h2v3a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V5.414l2 2V19h-2ZM15 5H9v2h6V5Z" clip-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 361 B |
@@ -40,7 +40,6 @@
|
|||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
scrollbar-gutter: stable both-edges;
|
|
||||||
}
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
html {
|
html {
|
||||||
@@ -76,9 +75,15 @@
|
|||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateX(-50%) translateY(-50%) translateY(-50px);
|
transform: translateX(-50%) translateY(-50%) translateY(-50px);
|
||||||
}
|
}
|
||||||
/* We need this style to prevent web dropdowns from shifting the display when opening */
|
/**
|
||||||
|
* We need these styles to prevent shifting due to scrollbar show/hide on
|
||||||
|
* OSs that have them enabled by default. This also handles cases where the
|
||||||
|
* screen wouldn't otherwise scroll, and therefore hide the scrollbar and
|
||||||
|
* shift the content, by forcing the page to show a scrollbar.
|
||||||
|
*/
|
||||||
body {
|
body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -193,6 +193,7 @@
|
|||||||
"react-native-web": "~0.19.11",
|
"react-native-web": "~0.19.11",
|
||||||
"react-native-web-webview": "^1.0.2",
|
"react-native-web-webview": "^1.0.2",
|
||||||
"react-native-webview": "13.10.2",
|
"react-native-webview": "13.10.2",
|
||||||
|
"react-remove-scroll-bar": "^2.3.6",
|
||||||
"react-responsive": "^9.0.2",
|
"react-responsive": "^9.0.2",
|
||||||
"react-textarea-autosize": "^8.5.3",
|
"react-textarea-autosize": "^8.5.3",
|
||||||
"rn-fetch-blob": "^0.12.0",
|
"rn-fetch-blob": "^0.12.0",
|
||||||
|
|||||||
+21
-1
@@ -1,7 +1,8 @@
|
|||||||
import {Platform, StyleProp, StyleSheet, ViewStyle} from 'react-native'
|
import {Platform, StyleProp, StyleSheet, ViewStyle} from 'react-native'
|
||||||
|
|
||||||
import * as tokens from '#/alf/tokens'
|
import * as tokens from '#/alf/tokens'
|
||||||
import {ios, native, web} from '#/alf/util/platform'
|
import {ios, native, platform, web} from '#/alf/util/platform'
|
||||||
|
import * as Layout from '#/components/Layout'
|
||||||
|
|
||||||
export const atoms = {
|
export const atoms = {
|
||||||
debug: {
|
debug: {
|
||||||
@@ -21,6 +22,9 @@ export const atoms = {
|
|||||||
relative: {
|
relative: {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
},
|
},
|
||||||
|
sticky: web({
|
||||||
|
position: 'sticky',
|
||||||
|
}),
|
||||||
inset_0: {
|
inset_0: {
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
@@ -941,4 +945,20 @@ export const atoms = {
|
|||||||
transitionTimingFunction: 'cubic-bezier(0.17, 0.73, 0.14, 1)',
|
transitionTimingFunction: 'cubic-bezier(0.17, 0.73, 0.14, 1)',
|
||||||
transitionDuration: '100ms',
|
transitionDuration: '100ms',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Layout.SCROLLBAR_OFFSET}
|
||||||
|
*/
|
||||||
|
scrollbar_offset: platform({
|
||||||
|
web: {
|
||||||
|
transform: [
|
||||||
|
{
|
||||||
|
translateX: Layout.SCROLLBAR_OFFSET,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
native: {
|
||||||
|
transform: [],
|
||||||
|
},
|
||||||
|
}) as {transform: Exclude<ViewStyle['transform'], string | undefined>},
|
||||||
} as const
|
} as const
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export * from '#/alf/types'
|
|||||||
export * from '#/alf/util/flatten'
|
export * from '#/alf/util/flatten'
|
||||||
export * from '#/alf/util/platform'
|
export * from '#/alf/util/platform'
|
||||||
export * from '#/alf/util/themeSelector'
|
export * from '#/alf/util/themeSelector'
|
||||||
|
export * from '#/alf/util/useGutterStyles'
|
||||||
|
|
||||||
export type Alf = {
|
export type Alf = {
|
||||||
themeName: ThemeName
|
themeName: ThemeName
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import {atoms as a, useBreakpoints, ViewStyleProp} from '#/alf'
|
||||||
|
|
||||||
|
export function useGutterStyles({
|
||||||
|
top,
|
||||||
|
bottom,
|
||||||
|
}: {
|
||||||
|
top?: boolean
|
||||||
|
bottom?: boolean
|
||||||
|
} = {}) {
|
||||||
|
const {gtMobile} = useBreakpoints()
|
||||||
|
return React.useMemo<ViewStyleProp['style']>(() => {
|
||||||
|
return [
|
||||||
|
a.px_lg,
|
||||||
|
top && a.pt_md,
|
||||||
|
bottom && a.pb_md,
|
||||||
|
gtMobile && [a.px_xl, top && a.pt_lg, bottom && a.pb_lg],
|
||||||
|
]
|
||||||
|
}, [gtMobile, top, bottom])
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import {useLingui} from '@lingui/react'
|
|||||||
import {DismissableLayer} from '@radix-ui/react-dismissable-layer'
|
import {DismissableLayer} from '@radix-ui/react-dismissable-layer'
|
||||||
import {useFocusGuards} from '@radix-ui/react-focus-guards'
|
import {useFocusGuards} from '@radix-ui/react-focus-guards'
|
||||||
import {FocusScope} from '@radix-ui/react-focus-scope'
|
import {FocusScope} from '@radix-ui/react-focus-scope'
|
||||||
|
import {RemoveScrollBar} from 'react-remove-scroll-bar'
|
||||||
|
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {useDialogStateControlContext} from '#/state/dialogs'
|
import {useDialogStateControlContext} from '#/state/dialogs'
|
||||||
@@ -103,6 +104,7 @@ export function Outer({
|
|||||||
{isOpen && (
|
{isOpen && (
|
||||||
<Portal>
|
<Portal>
|
||||||
<Context.Provider value={context}>
|
<Context.Provider value={context}>
|
||||||
|
<RemoveScrollBar />
|
||||||
<TouchableWithoutFeedback
|
<TouchableWithoutFeedback
|
||||||
accessibilityHint={undefined}
|
accessibilityHint={undefined}
|
||||||
accessibilityLabel={_(msg`Close active dialog`)}
|
accessibilityLabel={_(msg`Close active dialog`)}
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
import React, {useContext, useMemo} from 'react'
|
|
||||||
import {View, ViewStyle} from 'react-native'
|
|
||||||
import {StyleProp} from 'react-native'
|
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
|
||||||
|
|
||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
|
||||||
import {ScrollView} from '#/view/com/util/Views'
|
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
|
||||||
import {atoms as a} from '#/alf'
|
|
||||||
|
|
||||||
// Every screen should have a Layout component wrapping it.
|
|
||||||
// This component provides a default padding for the top of the screen.
|
|
||||||
// This allows certain screens to avoid the top padding if they want to.
|
|
||||||
|
|
||||||
const LayoutContext = React.createContext({
|
|
||||||
withinScreen: false,
|
|
||||||
topPaddingDisabled: false,
|
|
||||||
withinScrollView: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Every screen should have a Layout.Screen component wrapping it.
|
|
||||||
* This component provides a default padding for the top of the screen
|
|
||||||
* and height/minHeight
|
|
||||||
*/
|
|
||||||
let Screen = ({
|
|
||||||
disableTopPadding = false,
|
|
||||||
style,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof View> & {
|
|
||||||
disableTopPadding?: boolean
|
|
||||||
style?: StyleProp<ViewStyle>
|
|
||||||
}): React.ReactNode => {
|
|
||||||
const {top} = useSafeAreaInsets()
|
|
||||||
const context = useMemo(
|
|
||||||
() => ({
|
|
||||||
withinScreen: true,
|
|
||||||
topPaddingDisabled: disableTopPadding,
|
|
||||||
withinScrollView: false,
|
|
||||||
}),
|
|
||||||
[disableTopPadding],
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
<LayoutContext.Provider value={context}>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
{paddingTop: disableTopPadding ? 0 : top},
|
|
||||||
a.util_screen_outer,
|
|
||||||
style,
|
|
||||||
]}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</LayoutContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Screen = React.memo(Screen)
|
|
||||||
export {Screen}
|
|
||||||
|
|
||||||
let Header = (
|
|
||||||
props: React.ComponentProps<typeof ViewHeader>,
|
|
||||||
): React.ReactNode => {
|
|
||||||
const {withinScrollView} = useContext(LayoutContext)
|
|
||||||
if (!withinScrollView) {
|
|
||||||
return (
|
|
||||||
<CenteredView topBorder={false} sideBorders>
|
|
||||||
<ViewHeader showOnDesktop showBorder {...props} />
|
|
||||||
</CenteredView>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return <ViewHeader showOnDesktop showBorder {...props} />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Header = React.memo(Header)
|
|
||||||
export {Header}
|
|
||||||
|
|
||||||
let Content = ({
|
|
||||||
style,
|
|
||||||
contentContainerStyle,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof ScrollView> & {
|
|
||||||
style?: StyleProp<ViewStyle>
|
|
||||||
contentContainerStyle?: StyleProp<ViewStyle>
|
|
||||||
}): React.ReactNode => {
|
|
||||||
const context = useContext(LayoutContext)
|
|
||||||
const newContext = useMemo(
|
|
||||||
() => ({...context, withinScrollView: true}),
|
|
||||||
[context],
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
<LayoutContext.Provider value={newContext}>
|
|
||||||
<ScrollView
|
|
||||||
style={[a.flex_1, style]}
|
|
||||||
contentContainerStyle={[{paddingBottom: 100}, contentContainerStyle]}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</LayoutContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Content = React.memo(Content)
|
|
||||||
export {Content}
|
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import {createContext, useCallback, useContext} from 'react'
|
||||||
|
import {GestureResponderEvent, View} from 'react-native'
|
||||||
|
import {msg} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {useNavigation} from '@react-navigation/native'
|
||||||
|
|
||||||
|
import {HITSLOP_30} from '#/lib/constants'
|
||||||
|
import {NavigationProp} from '#/lib/routes/types'
|
||||||
|
import {isIOS} from '#/platform/detection'
|
||||||
|
import {useSetDrawerOpen} from '#/state/shell'
|
||||||
|
import {
|
||||||
|
atoms as a,
|
||||||
|
platform,
|
||||||
|
TextStyleProp,
|
||||||
|
useBreakpoints,
|
||||||
|
useGutterStyles,
|
||||||
|
useTheme,
|
||||||
|
} from '#/alf'
|
||||||
|
import {Button, ButtonIcon, ButtonProps} from '#/components/Button'
|
||||||
|
import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeft} from '#/components/icons/Arrow'
|
||||||
|
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
|
||||||
|
import {
|
||||||
|
BUTTON_VISUAL_ALIGNMENT_OFFSET,
|
||||||
|
HEADER_SLOT_SIZE,
|
||||||
|
} from '#/components/Layout/const'
|
||||||
|
import {ScrollbarOffsetContext} from '#/components/Layout/context'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
|
export function Outer({
|
||||||
|
children,
|
||||||
|
noBottomBorder,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
noBottomBorder?: boolean
|
||||||
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const gutter = useGutterStyles()
|
||||||
|
const {gtMobile} = useBreakpoints()
|
||||||
|
const {isWithinOffsetView} = useContext(ScrollbarOffsetContext)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.w_full,
|
||||||
|
!noBottomBorder && a.border_b,
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.gap_sm,
|
||||||
|
gutter,
|
||||||
|
platform({
|
||||||
|
native: [a.pb_sm, a.pt_xs],
|
||||||
|
web: [a.py_sm],
|
||||||
|
}),
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
gtMobile && [a.mx_auto, {maxWidth: 600}],
|
||||||
|
!isWithinOffsetView && a.scrollbar_offset,
|
||||||
|
]}>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const AlignmentContext = createContext<'platform' | 'left'>('platform')
|
||||||
|
|
||||||
|
export function Content({
|
||||||
|
children,
|
||||||
|
align = 'platform',
|
||||||
|
}: {
|
||||||
|
children?: React.ReactNode
|
||||||
|
align?: 'platform' | 'left'
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_1,
|
||||||
|
a.justify_center,
|
||||||
|
isIOS && align === 'platform' && a.align_center,
|
||||||
|
{minHeight: HEADER_SLOT_SIZE},
|
||||||
|
]}>
|
||||||
|
<AlignmentContext.Provider value={align}>
|
||||||
|
{children}
|
||||||
|
</AlignmentContext.Provider>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Slot({children}: {children?: React.ReactNode}) {
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.z_50,
|
||||||
|
{
|
||||||
|
width: HEADER_SLOT_SIZE,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BackButton({onPress, style, ...props}: Partial<ButtonProps>) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const navigation = useNavigation<NavigationProp>()
|
||||||
|
|
||||||
|
const onPressBack = useCallback(
|
||||||
|
(evt: GestureResponderEvent) => {
|
||||||
|
onPress?.(evt)
|
||||||
|
if (evt.defaultPrevented) return
|
||||||
|
if (navigation.canGoBack()) {
|
||||||
|
navigation.goBack()
|
||||||
|
} else {
|
||||||
|
navigation.navigate('Home')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onPress, navigation],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Slot>
|
||||||
|
<Button
|
||||||
|
label={_(msg`Go back`)}
|
||||||
|
size="small"
|
||||||
|
variant="ghost"
|
||||||
|
color="secondary"
|
||||||
|
shape="square"
|
||||||
|
onPress={onPressBack}
|
||||||
|
hitSlop={HITSLOP_30}
|
||||||
|
style={[{marginLeft: -BUTTON_VISUAL_ALIGNMENT_OFFSET}, style]}
|
||||||
|
{...props}>
|
||||||
|
<ButtonIcon icon={ArrowLeft} size="lg" />
|
||||||
|
</Button>
|
||||||
|
</Slot>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MenuButton() {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const setDrawerOpen = useSetDrawerOpen()
|
||||||
|
const {gtMobile} = useBreakpoints()
|
||||||
|
|
||||||
|
const onPress = useCallback(() => {
|
||||||
|
setDrawerOpen(true)
|
||||||
|
}, [setDrawerOpen])
|
||||||
|
|
||||||
|
return gtMobile ? null : (
|
||||||
|
<Slot>
|
||||||
|
<Button
|
||||||
|
label={_(msg`Open drawer menu`)}
|
||||||
|
size="small"
|
||||||
|
variant="ghost"
|
||||||
|
color="secondary"
|
||||||
|
shape="square"
|
||||||
|
onPress={onPress}
|
||||||
|
hitSlop={HITSLOP_30}
|
||||||
|
style={[{marginLeft: -BUTTON_VISUAL_ALIGNMENT_OFFSET}]}>
|
||||||
|
<ButtonIcon icon={Menu} size="lg" />
|
||||||
|
</Button>
|
||||||
|
</Slot>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TitleText({
|
||||||
|
children,
|
||||||
|
style,
|
||||||
|
}: {children: React.ReactNode} & TextStyleProp) {
|
||||||
|
const {gtMobile} = useBreakpoints()
|
||||||
|
const align = useContext(AlignmentContext)
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_lg,
|
||||||
|
a.font_heavy,
|
||||||
|
a.leading_tight,
|
||||||
|
isIOS && align === 'platform' && a.text_center,
|
||||||
|
gtMobile && a.text_xl,
|
||||||
|
style,
|
||||||
|
]}
|
||||||
|
numberOfLines={2}>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SubtitleText({children}: {children: React.ReactNode}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const align = useContext(AlignmentContext)
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_sm,
|
||||||
|
a.leading_snug,
|
||||||
|
isIOS && align === 'platform' && a.text_center,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
]}
|
||||||
|
numberOfLines={2}>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
# Layout
|
||||||
|
|
||||||
|
This directory contains our core layout components. Use these when creating new
|
||||||
|
screens, or when supplementing other components with functionality like
|
||||||
|
centering.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
If we aren't talking about the `shell` components, layouts on individual screens
|
||||||
|
look like more or less like this:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Outer>
|
||||||
|
<Header>...</Header>
|
||||||
|
<Content>...</Content>
|
||||||
|
</Outer>
|
||||||
|
```
|
||||||
|
|
||||||
|
I'll map these words to real components.
|
||||||
|
|
||||||
|
### `Layout.Screen`
|
||||||
|
|
||||||
|
Provides the "Outer" functionality for a screen, like taking up the full height
|
||||||
|
of the screen. **All screens should be wrapped with this component,** probably
|
||||||
|
as the outermost component.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> On web, `Layout.Screen` also provides the side borders on our central content
|
||||||
|
> column. These borders are fixed position, 1px outside our center column width
|
||||||
|
> of 600px.
|
||||||
|
>
|
||||||
|
> What this effectively means is that _nothing inside the center content column
|
||||||
|
> needs (or should) define left/right borders._ That is now handled in one
|
||||||
|
> place: within `Layout.Screen`.
|
||||||
|
|
||||||
|
### `Layout.Header.*`
|
||||||
|
|
||||||
|
The `Layout.Header` component actually contains multiple sub-components. Use
|
||||||
|
this to compose different versions of the header. The most basic version looks
|
||||||
|
like this:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Layout.Header.Outer>
|
||||||
|
<Layout.Header.BackButton /> {/* or <Layout.Header.MenuButton /> */}
|
||||||
|
|
||||||
|
<Layout.Header.Content>
|
||||||
|
<Layout.Header.TitleText>Account</Layout.Header.TitleText>
|
||||||
|
|
||||||
|
{/* Optional subtitle */}
|
||||||
|
<Layout.Header.SubtitleText>Settings for @esb.lol</Layout.Header.SubtitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
|
||||||
|
<Layout.Header.Slot />
|
||||||
|
</Layout.Header.Outer>
|
||||||
|
```
|
||||||
|
|
||||||
|
Note the additional `Slot` component. This is here to keep the header balanced
|
||||||
|
and provide correct spacing on all platforms. The `Slot` is 34px wide, which
|
||||||
|
matches the `BackButton` and `MenuButton`.
|
||||||
|
|
||||||
|
> If anyone has better ideas, I'm all ears, but this was simple and the small
|
||||||
|
> amount of boilerplate is only incurred when creating a new screen, which is
|
||||||
|
> infrequent.
|
||||||
|
|
||||||
|
It can also function as a "slot" for a button positioned on the right side. See
|
||||||
|
the `Hashtag` screen for an example, abbreviated below:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Layout.Header.Slot>
|
||||||
|
<Button size='small' shape='round'>...</Button>
|
||||||
|
</Layout.Header.Slot>
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need additional customization, simply use the components that are helpful
|
||||||
|
and create new ones as needed. A good example is the `SavedFeeds` screen, which
|
||||||
|
looks roughly like this:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Layout.Header.Outer>
|
||||||
|
<Layout.Header.BackButton />
|
||||||
|
|
||||||
|
{/* Override to align content to the left, making room for the button */}
|
||||||
|
<Layout.Header.Content align='left'>
|
||||||
|
<Layout.Header.TitleText>Edit My Feeds</Layout.Header.TitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
|
||||||
|
{/* Custom button, wider than 34px */}
|
||||||
|
<Button size='small'>...</Button>
|
||||||
|
</Layout.Header.Outer>
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> The `Header` should be _outside_ the `Content` component in order to be
|
||||||
|
> fixed on scroll on native. Placing it inside will make it scroll with the rest
|
||||||
|
> of the page.
|
||||||
|
|
||||||
|
### `Layout.Content`
|
||||||
|
|
||||||
|
This provides the "Content" functionality for a screen. This component is
|
||||||
|
actually an `Animated.ScrollView`, and accepts props for that component. It
|
||||||
|
provides a little default styling as well. On web, it also _centers the content
|
||||||
|
inside our center content column of 600px_.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> What about flatlists or pagers? Those components are not colocated here (yet).
|
||||||
|
> But those components serve the same purpose of "Content".
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
The most basic layout available to us looks like this:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Layout.Screen>
|
||||||
|
<Layout.Header.Outer>
|
||||||
|
<Layout.Header.BackButton /> {/* or <Layout.Header.MenuButton /> */}
|
||||||
|
|
||||||
|
<Layout.Header.Content>
|
||||||
|
<Layout.Header.TitleText>Account</Layout.Header.TitleText>
|
||||||
|
|
||||||
|
{/* Optional subtitle */}
|
||||||
|
<Layout.Header.SubtitleText>Settings for @esb.lol</Layout.Header.SubtitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
|
||||||
|
<Layout.Header.Slot />
|
||||||
|
</Layout.Header.Outer>
|
||||||
|
|
||||||
|
<Layout.Content>
|
||||||
|
...
|
||||||
|
</Layout.Content>
|
||||||
|
</Layout.Screen>
|
||||||
|
```
|
||||||
|
|
||||||
|
**For `List` views,** you'd sub in `List` for `Layout.Content` and it will
|
||||||
|
function the same. See `Feeds` screen for an example.
|
||||||
|
|
||||||
|
**For `Pager` views,** including `PagerWithHeader`, do the same. See `Hashtag`
|
||||||
|
screen for an example.
|
||||||
|
|
||||||
|
## Utilities
|
||||||
|
|
||||||
|
### `Layout.Center`
|
||||||
|
|
||||||
|
This component behaves like our old `CenteredView` component.
|
||||||
|
|
||||||
|
### `Layout.SCROLLBAR_OFFSET` and `Layout.SCROLLBAR_OFFSET_POSITIVE`
|
||||||
|
|
||||||
|
Provide a pre-configured CSS vars for use when aligning fixed position elements.
|
||||||
|
More on this below.
|
||||||
|
|
||||||
|
## Scrollbar gutter handling
|
||||||
|
|
||||||
|
Operating systems allow users to configure if their browser _always_ shows
|
||||||
|
scrollbars not. Some OSs also don't allow configuration.
|
||||||
|
|
||||||
|
The presence of scrollbars affects layout, particularly fixed position elements.
|
||||||
|
Browsers support `scrollbar-gutter`, but each behaves differently. Our approach
|
||||||
|
is to use the default `scrollbar-gutter: auto`. Basically, we start from a clean
|
||||||
|
slate.
|
||||||
|
|
||||||
|
This handling becomes particularly thorny when we need to lock scroll, like when
|
||||||
|
opening a dialog or dropdown. Radix uses the library `react-remove-scroll`
|
||||||
|
internally, which in turn depends on
|
||||||
|
[`react-remove-scroll-bar`](https://github.com/theKashey/react-remove-scroll-bar).
|
||||||
|
We've opted to rely on this transient dependency. This library adds some utility
|
||||||
|
classes and CSS vars to the page when scroll is locked.
|
||||||
|
|
||||||
|
**It is this CSS variable that we use in `SCROLLBAR_OFFSET` values.** This
|
||||||
|
ensures that elements do not shift relative to the screen when opening a
|
||||||
|
dropdown or dialog.
|
||||||
|
|
||||||
|
These styles are applied where needed and we should have very little need of
|
||||||
|
adjusting them often.
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
export const SCROLLBAR_OFFSET =
|
||||||
|
'calc(-1 * var(--removed-body-scroll-bar-size, 0px) / 2)' as any
|
||||||
|
export const SCROLLBAR_OFFSET_POSITIVE =
|
||||||
|
'calc(var(--removed-body-scroll-bar-size, 0px) / 2)' as any
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Useful for visually aligning icons within header buttons with the elements
|
||||||
|
* below them on the screen. Apply positively or negatively depending on side
|
||||||
|
* of the screen you're on.
|
||||||
|
*/
|
||||||
|
export const BUTTON_VISUAL_ALIGNMENT_OFFSET = 3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Corresponds to the width of a small square or round button
|
||||||
|
*/
|
||||||
|
export const HEADER_SLOT_SIZE = 34
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export const ScrollbarOffsetContext = React.createContext({
|
||||||
|
isWithinOffsetView: false,
|
||||||
|
})
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
import React, {useContext, useMemo} from 'react'
|
||||||
|
import {StyleSheet, View, ViewProps, ViewStyle} from 'react-native'
|
||||||
|
import {StyleProp} from 'react-native'
|
||||||
|
import {
|
||||||
|
KeyboardAwareScrollView,
|
||||||
|
KeyboardAwareScrollViewProps,
|
||||||
|
} from 'react-native-keyboard-controller'
|
||||||
|
import Animated, {
|
||||||
|
AnimatedScrollViewProps,
|
||||||
|
useAnimatedProps,
|
||||||
|
} from 'react-native-reanimated'
|
||||||
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
|
|
||||||
|
import {isWeb} from '#/platform/detection'
|
||||||
|
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||||
|
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
|
||||||
|
import {ScrollbarOffsetContext} from '#/components/Layout/context'
|
||||||
|
|
||||||
|
export * from '#/components/Layout/const'
|
||||||
|
export * as Header from '#/components/Layout/Header'
|
||||||
|
|
||||||
|
export type ScreenProps = React.ComponentProps<typeof View> & {
|
||||||
|
style?: StyleProp<ViewStyle>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outermost component of every screen
|
||||||
|
*/
|
||||||
|
export const Screen = React.memo(function Screen({
|
||||||
|
style,
|
||||||
|
...props
|
||||||
|
}: ScreenProps) {
|
||||||
|
const {top} = useSafeAreaInsets()
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isWeb && <WebCenterBorders />}
|
||||||
|
<View
|
||||||
|
style={[a.util_screen_outer, {paddingTop: top}, style]}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export type ContentProps = AnimatedScrollViewProps & {
|
||||||
|
style?: StyleProp<ViewStyle>
|
||||||
|
contentContainerStyle?: StyleProp<ViewStyle>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default scroll view for simple pages
|
||||||
|
*/
|
||||||
|
export const Content = React.memo(function Content({
|
||||||
|
children,
|
||||||
|
style,
|
||||||
|
contentContainerStyle,
|
||||||
|
...props
|
||||||
|
}: ContentProps) {
|
||||||
|
const {footerHeight} = useShellLayout()
|
||||||
|
const animatedProps = useAnimatedProps(() => {
|
||||||
|
return {
|
||||||
|
scrollIndicatorInsets: {
|
||||||
|
bottom: footerHeight.get(),
|
||||||
|
top: 0,
|
||||||
|
right: 1,
|
||||||
|
},
|
||||||
|
} satisfies AnimatedScrollViewProps
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Animated.ScrollView
|
||||||
|
id="content"
|
||||||
|
automaticallyAdjustsScrollIndicatorInsets={false}
|
||||||
|
// sets the scroll inset to the height of the footer
|
||||||
|
animatedProps={animatedProps}
|
||||||
|
style={[scrollViewStyles.common, style]}
|
||||||
|
contentContainerStyle={[
|
||||||
|
scrollViewStyles.contentContainer,
|
||||||
|
contentContainerStyle,
|
||||||
|
]}
|
||||||
|
{...props}>
|
||||||
|
{isWeb ? (
|
||||||
|
// @ts-ignore web only -esb
|
||||||
|
<Center>{children}</Center>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</Animated.ScrollView>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const scrollViewStyles = StyleSheet.create({
|
||||||
|
common: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
contentContainer: {
|
||||||
|
paddingBottom: 100,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export type KeyboardAwareContentProps = KeyboardAwareScrollViewProps & {
|
||||||
|
children: React.ReactNode
|
||||||
|
contentContainerStyle?: StyleProp<ViewStyle>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default scroll view for simple pages.
|
||||||
|
*
|
||||||
|
* BE SURE TO TEST THIS WHEN USING, it's untested as of writing this comment.
|
||||||
|
*/
|
||||||
|
export const KeyboardAwareContent = React.memo(function LayoutScrollView({
|
||||||
|
children,
|
||||||
|
style,
|
||||||
|
contentContainerStyle,
|
||||||
|
...props
|
||||||
|
}: KeyboardAwareContentProps) {
|
||||||
|
return (
|
||||||
|
<KeyboardAwareScrollView
|
||||||
|
style={[scrollViewStyles.common, style]}
|
||||||
|
contentContainerStyle={[
|
||||||
|
scrollViewStyles.contentContainer,
|
||||||
|
contentContainerStyle,
|
||||||
|
]}
|
||||||
|
keyboardShouldPersistTaps="handled"
|
||||||
|
{...props}>
|
||||||
|
{isWeb ? <Center>{children}</Center> : children}
|
||||||
|
</KeyboardAwareScrollView>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility component to center content within the screen
|
||||||
|
*/
|
||||||
|
export const Center = React.memo(function LayoutContent({
|
||||||
|
children,
|
||||||
|
style,
|
||||||
|
...props
|
||||||
|
}: ViewProps) {
|
||||||
|
const {isWithinOffsetView} = useContext(ScrollbarOffsetContext)
|
||||||
|
const {gtMobile} = useBreakpoints()
|
||||||
|
const ctx = useMemo(() => ({isWithinOffsetView: true}), [])
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.w_full,
|
||||||
|
a.mx_auto,
|
||||||
|
gtMobile && {
|
||||||
|
maxWidth: 600,
|
||||||
|
},
|
||||||
|
style,
|
||||||
|
!isWithinOffsetView && a.scrollbar_offset,
|
||||||
|
]}
|
||||||
|
{...props}>
|
||||||
|
<ScrollbarOffsetContext.Provider value={ctx}>
|
||||||
|
{children}
|
||||||
|
</ScrollbarOffsetContext.Provider>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only used within `Layout.Screen`, not for reuse
|
||||||
|
*/
|
||||||
|
const WebCenterBorders = React.memo(function LayoutContent() {
|
||||||
|
const t = useTheme()
|
||||||
|
const {gtMobile} = useBreakpoints()
|
||||||
|
return gtMobile ? (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.fixed,
|
||||||
|
a.inset_0,
|
||||||
|
a.border_l,
|
||||||
|
a.border_r,
|
||||||
|
t.atoms.border_contrast_low,
|
||||||
|
web({
|
||||||
|
width: 602,
|
||||||
|
left: '50%',
|
||||||
|
transform: [
|
||||||
|
{
|
||||||
|
translateX: '-50%',
|
||||||
|
},
|
||||||
|
...a.scrollbar_offset.transform,
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
})
|
||||||
@@ -12,8 +12,14 @@ import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
|
|||||||
import {List} from '#/view/com/util/List'
|
import {List} from '#/view/com/util/List'
|
||||||
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||||
|
|
||||||
function renderItem({item}: {item: GetLikes.Like}) {
|
function renderItem({item, index}: {item: GetLikes.Like; index: number}) {
|
||||||
return <ProfileCardWithFollowBtn key={item.actor.did} profile={item.actor} />
|
return (
|
||||||
|
<ProfileCardWithFollowBtn
|
||||||
|
key={item.actor.did}
|
||||||
|
profile={item.actor}
|
||||||
|
noBorder={index === 0}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function keyExtractor(item: GetLikes.Like) {
|
function keyExtractor(item: GetLikes.Like) {
|
||||||
@@ -81,6 +87,8 @@ export function LikedByList({uri}: {uri: string}) {
|
|||||||
)}
|
)}
|
||||||
errorMessage={cleanError(resolveError || error)}
|
errorMessage={cleanError(resolveError || error)}
|
||||||
onRetry={isError ? refetch : undefined}
|
onRetry={isError ? refetch : undefined}
|
||||||
|
topBorder={false}
|
||||||
|
sideBorders={false}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -103,6 +111,7 @@ export function LikedByList({uri}: {uri: string}) {
|
|||||||
onEndReachedThreshold={3}
|
onEndReachedThreshold={3}
|
||||||
initialNumToRender={initialNumToRender}
|
initialNumToRender={initialNumToRender}
|
||||||
windowSize={11}
|
windowSize={11}
|
||||||
|
sideBorders={false}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,38 +109,6 @@ function ListFooterMaybeError({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ListHeaderDesktop({
|
|
||||||
title,
|
|
||||||
subtitle,
|
|
||||||
}: {
|
|
||||||
title: string
|
|
||||||
subtitle?: string
|
|
||||||
}) {
|
|
||||||
const {gtTablet} = useBreakpoints()
|
|
||||||
const t = useTheme()
|
|
||||||
|
|
||||||
if (!gtTablet) return null
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.w_full,
|
|
||||||
a.py_sm,
|
|
||||||
a.px_xl,
|
|
||||||
a.gap_xs,
|
|
||||||
a.justify_center,
|
|
||||||
{minHeight: 50},
|
|
||||||
]}>
|
|
||||||
<Text style={[a.text_2xl, a.font_bold]}>{title}</Text>
|
|
||||||
{subtitle ? (
|
|
||||||
<Text style={[a.text_md, t.atoms.text_contrast_medium]}>
|
|
||||||
{subtitle}
|
|
||||||
</Text>
|
|
||||||
) : undefined}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let ListMaybePlaceholder = ({
|
let ListMaybePlaceholder = ({
|
||||||
isLoading,
|
isLoading,
|
||||||
noEmpty,
|
noEmpty,
|
||||||
@@ -154,7 +122,7 @@ let ListMaybePlaceholder = ({
|
|||||||
onGoBack,
|
onGoBack,
|
||||||
hideBackButton,
|
hideBackButton,
|
||||||
sideBorders,
|
sideBorders,
|
||||||
topBorder = true,
|
topBorder = false,
|
||||||
}: {
|
}: {
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
noEmpty?: boolean
|
noEmpty?: boolean
|
||||||
|
|||||||
@@ -65,25 +65,23 @@ export let MessagesListHeader = ({
|
|||||||
a.pr_lg,
|
a.pr_lg,
|
||||||
a.py_sm,
|
a.py_sm,
|
||||||
]}>
|
]}>
|
||||||
{!gtTablet && (
|
<TouchableOpacity
|
||||||
<TouchableOpacity
|
testID="conversationHeaderBackBtn"
|
||||||
testID="conversationHeaderBackBtn"
|
onPress={onPressBack}
|
||||||
onPress={onPressBack}
|
hitSlop={BACK_HITSLOP}
|
||||||
hitSlop={BACK_HITSLOP}
|
style={{width: 30, height: 30, marginTop: isWeb ? 6 : 4}}
|
||||||
style={{width: 30, height: 30, marginTop: isWeb ? 6 : 4}}
|
accessibilityRole="button"
|
||||||
accessibilityRole="button"
|
accessibilityLabel={_(msg`Back`)}
|
||||||
accessibilityLabel={_(msg`Back`)}
|
accessibilityHint="">
|
||||||
accessibilityHint="">
|
<FontAwesomeIcon
|
||||||
<FontAwesomeIcon
|
size={18}
|
||||||
size={18}
|
icon="angle-left"
|
||||||
icon="angle-left"
|
style={{
|
||||||
style={{
|
marginTop: 6,
|
||||||
marginTop: 6,
|
}}
|
||||||
}}
|
color={t.atoms.text.color}
|
||||||
color={t.atoms.text.color}
|
/>
|
||||||
/>
|
</TouchableOpacity>
|
||||||
</TouchableOpacity>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{profile && moderation && blockInfo ? (
|
{profile && moderation && blockInfo ? (
|
||||||
<HeaderReady
|
<HeaderReady
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export function DateField({
|
|||||||
open
|
open
|
||||||
timeZoneOffsetInMinutes={0}
|
timeZoneOffsetInMinutes={0}
|
||||||
theme={t.scheme}
|
theme={t.scheme}
|
||||||
|
// @ts-ignore TODO
|
||||||
buttonColor={t.name === 'light' ? '#000000' : '#ffffff'}
|
buttonColor={t.name === 'light' ? '#000000' : '#ffffff'}
|
||||||
date={new Date(value)}
|
date={new Date(value)}
|
||||||
onConfirm={onChangeInternal}
|
onConfirm={onChangeInternal}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import {createSinglePathSVG} from './TEMPLATE'
|
||||||
|
|
||||||
|
export const FloppyDisk_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||||
|
path: 'M3 4a1 1 0 0 1 1-1h13a1 1 0 0 1 .707.293l3 3A1 1 0 0 1 21 7v13a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm6 15h6v-5H9v5Zm8 0v-6a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v6H5V5h2v3a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V5.414l2 2V19h-2ZM15 5H9v2h6V5Z',
|
||||||
|
})
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import {useEffect} from 'react'
|
|
||||||
|
|
||||||
import {isWeb} from '#/platform/detection'
|
|
||||||
|
|
||||||
let refCount = 0
|
|
||||||
|
|
||||||
function incrementRefCount() {
|
|
||||||
if (refCount === 0) {
|
|
||||||
document.body.style.overflow = 'hidden'
|
|
||||||
document.documentElement.style.scrollbarGutter = 'auto'
|
|
||||||
}
|
|
||||||
refCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
function decrementRefCount() {
|
|
||||||
refCount--
|
|
||||||
if (refCount === 0) {
|
|
||||||
document.body.style.overflow = ''
|
|
||||||
document.documentElement.style.scrollbarGutter = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useWebBodyScrollLock(isLockActive: boolean) {
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isWeb || !isLockActive) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
incrementRefCount()
|
|
||||||
return () => decrementRefCount()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -17,13 +17,13 @@ import {
|
|||||||
} from '#/state/session'
|
} from '#/state/session'
|
||||||
import {useSetMinimalShellMode} from '#/state/shell'
|
import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||||
import {ScrollView} from '#/view/com/util/Views'
|
|
||||||
import {Logo} from '#/view/icons/Logo'
|
import {Logo} from '#/view/icons/Logo'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {AccountList} from '#/components/AccountList'
|
import {AccountList} from '#/components/AccountList'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {Divider} from '#/components/Divider'
|
import {Divider} from '#/components/Divider'
|
||||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
||||||
|
import * as Layout from '#/components/Layout'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
@@ -104,24 +104,17 @@ export function Deactivated() {
|
|||||||
}, [_, agent, setPending, setError, queryClient])
|
}, [_, agent, setPending, setError, queryClient])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[a.util_screen_outer, a.flex_1, t.atoms.bg]}>
|
<View style={[a.util_screen_outer, a.flex_1]}>
|
||||||
<ScrollView
|
<Layout.Content
|
||||||
style={[
|
contentContainerStyle={[
|
||||||
a.h_full,
|
|
||||||
a.w_full,
|
|
||||||
a.px_2xl,
|
a.px_2xl,
|
||||||
{
|
{
|
||||||
paddingTop: isWeb ? 64 : insets.top + 16,
|
paddingTop: isWeb ? 64 : insets.top + 16,
|
||||||
paddingBottom: isWeb ? 64 : insets.bottom,
|
paddingBottom: isWeb ? 64 : insets.bottom,
|
||||||
},
|
},
|
||||||
]}
|
|
||||||
contentContainerStyle={[
|
|
||||||
a.w_full,
|
|
||||||
a.flex_row,
|
|
||||||
a.justify_center,
|
|
||||||
{borderWidth: 0},
|
|
||||||
]}>
|
]}>
|
||||||
<View style={[a.w_full, {maxWidth: COL_WIDTH}]}>
|
<View
|
||||||
|
style={[a.w_full, {marginHorizontal: 'auto', maxWidth: COL_WIDTH}]}>
|
||||||
<View style={[a.w_full, a.justify_center, a.align_center, a.pb_5xl]}>
|
<View style={[a.w_full, a.justify_center, a.align_center, a.pb_5xl]}>
|
||||||
<Logo width={40} />
|
<Logo width={40} />
|
||||||
</View>
|
</View>
|
||||||
@@ -218,7 +211,7 @@ export function Deactivated() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</Layout.Content>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-41
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {ListRenderItemInfo, Pressable, View} from 'react-native'
|
import {ListRenderItemInfo, View} from 'react-native'
|
||||||
import {PostView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
|
import {PostView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
@@ -13,16 +13,15 @@ import {shareUrl} from '#/lib/sharing'
|
|||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
import {enforceLen} from '#/lib/strings/helpers'
|
import {enforceLen} from '#/lib/strings/helpers'
|
||||||
import {isNative, isWeb} from '#/platform/detection'
|
|
||||||
import {useSearchPostsQuery} from '#/state/queries/search-posts'
|
import {useSearchPostsQuery} from '#/state/queries/search-posts'
|
||||||
import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
|
import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
|
||||||
import {Pager} from '#/view/com/pager/Pager'
|
import {Pager} from '#/view/com/pager/Pager'
|
||||||
import {TabBar} from '#/view/com/pager/TabBar'
|
import {TabBar} from '#/view/com/pager/TabBar'
|
||||||
import {Post} from '#/view/com/post/Post'
|
import {Post} from '#/view/com/post/Post'
|
||||||
import {List} from '#/view/com/util/List'
|
import {List} from '#/view/com/util/List'
|
||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
import {atoms as a, web} from '#/alf'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
import {Button, ButtonIcon} from '#/components/Button'
|
||||||
import {ArrowOutOfBox_Stroke2_Corner0_Rounded} from '#/components/icons/ArrowOutOfBox'
|
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||||
|
|
||||||
@@ -110,46 +109,36 @@ export default function HashtagScreen({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen>
|
<Layout.Screen>
|
||||||
<CenteredView sideBorders={true}>
|
<Layout.Header.Outer>
|
||||||
<ViewHeader
|
<Layout.Header.BackButton />
|
||||||
showOnDesktop
|
<Layout.Header.Content>
|
||||||
title={headerTitle}
|
<Layout.Header.TitleText>{headerTitle}</Layout.Header.TitleText>
|
||||||
subtitle={author ? _(msg`From @${sanitizedAuthor}`) : undefined}
|
{author && (
|
||||||
canGoBack
|
<Layout.Header.SubtitleText>
|
||||||
renderButton={
|
{_(msg`From @${sanitizedAuthor}`)}
|
||||||
isNative
|
</Layout.Header.SubtitleText>
|
||||||
? () => (
|
)}
|
||||||
<Pressable
|
</Layout.Header.Content>
|
||||||
accessibilityRole="button"
|
<Layout.Header.Slot>
|
||||||
onPress={onShare}
|
<Button
|
||||||
hitSlop={HITSLOP_10}>
|
label={_(msg`Share`)}
|
||||||
<ArrowOutOfBox_Stroke2_Corner0_Rounded
|
size="small"
|
||||||
size="lg"
|
variant="ghost"
|
||||||
onPress={onShare}
|
color="primary"
|
||||||
/>
|
shape="round"
|
||||||
</Pressable>
|
onPress={onShare}
|
||||||
)
|
hitSlop={HITSLOP_10}
|
||||||
: undefined
|
style={[{right: -3}]}>
|
||||||
}
|
<ButtonIcon icon={Share} size="md" />
|
||||||
/>
|
</Button>
|
||||||
</CenteredView>
|
</Layout.Header.Slot>
|
||||||
|
</Layout.Header.Outer>
|
||||||
<Pager
|
<Pager
|
||||||
onPageSelected={onPageSelected}
|
onPageSelected={onPageSelected}
|
||||||
renderTabBar={props => (
|
renderTabBar={props => (
|
||||||
<CenteredView
|
<Layout.Center style={web([a.sticky, a.z_10, {top: 0}])}>
|
||||||
sideBorders={true}
|
|
||||||
// @ts-ignore web only
|
|
||||||
style={
|
|
||||||
isWeb
|
|
||||||
? {
|
|
||||||
position: isWeb ? 'sticky' : '',
|
|
||||||
top: 0,
|
|
||||||
zIndex: 1,
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}>
|
|
||||||
<TabBar items={sections.map(section => section.title)} {...props} />
|
<TabBar items={sections.map(section => section.title)} {...props} />
|
||||||
</CenteredView>
|
</Layout.Center>
|
||||||
)}
|
)}
|
||||||
initialPage={0}>
|
initialPage={0}>
|
||||||
{sections.map((section, i) => (
|
{sections.map((section, i) => (
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ import {MESSAGE_SCREEN_POLL_INTERVAL} from '#/state/messages/convo/const'
|
|||||||
import {useMessagesEventBus} from '#/state/messages/events'
|
import {useMessagesEventBus} from '#/state/messages/events'
|
||||||
import {useListConvosQuery} from '#/state/queries/messages/list-converations'
|
import {useListConvosQuery} from '#/state/queries/messages/list-converations'
|
||||||
import {List} from '#/view/com/util/List'
|
import {List} from '#/view/com/util/List'
|
||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
|
||||||
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
|
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {DialogControlProps, useDialogControl} from '#/components/Dialog'
|
import {DialogControlProps, useDialogControl} from '#/components/Dialog'
|
||||||
@@ -49,7 +47,6 @@ export function MessagesScreen({navigation, route}: Props) {
|
|||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const newChatControl = useDialogControl()
|
const newChatControl = useDialogControl()
|
||||||
const {gtMobile} = useBreakpoints()
|
|
||||||
const pushToConversation = route.params?.pushToConversation
|
const pushToConversation = route.params?.pushToConversation
|
||||||
|
|
||||||
// Whenever we have `pushToConversation` set, it means we pressed a notification for a chat without being on
|
// Whenever we have `pushToConversation` set, it means we pressed a notification for a chat without being on
|
||||||
@@ -81,21 +78,6 @@ export function MessagesScreen({navigation, route}: Props) {
|
|||||||
}, [messagesBus, isActive]),
|
}, [messagesBus, isActive]),
|
||||||
)
|
)
|
||||||
|
|
||||||
const renderButton = useCallback(() => {
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
to="/messages/settings"
|
|
||||||
label={_(msg`Chat settings`)}
|
|
||||||
size="small"
|
|
||||||
variant="ghost"
|
|
||||||
color="secondary"
|
|
||||||
shape="square"
|
|
||||||
style={[a.justify_center]}>
|
|
||||||
<SettingsSlider size="md" style={[t.atoms.text_contrast_medium]} />
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}, [_, t])
|
|
||||||
|
|
||||||
const initialNumToRender = useInitialNumToRender({minItemHeight: 80})
|
const initialNumToRender = useInitialNumToRender({minItemHeight: 80})
|
||||||
const [isPTRing, setIsPTRing] = useState(false)
|
const [isPTRing, setIsPTRing] = useState(false)
|
||||||
|
|
||||||
@@ -144,28 +126,11 @@ export function MessagesScreen({navigation, route}: Props) {
|
|||||||
[navigation],
|
[navigation],
|
||||||
)
|
)
|
||||||
|
|
||||||
const onNavigateToSettings = useCallback(() => {
|
|
||||||
navigation.navigate('MessagesSettings')
|
|
||||||
}, [navigation])
|
|
||||||
|
|
||||||
if (conversations.length < 1) {
|
if (conversations.length < 1) {
|
||||||
return (
|
return (
|
||||||
<Layout.Screen>
|
<Layout.Screen>
|
||||||
<CenteredView sideBorders={gtMobile} style={[a.h_full_vh]}>
|
<Header newChatControl={newChatControl} />
|
||||||
{gtMobile ? (
|
<Layout.Center>
|
||||||
<DesktopHeader
|
|
||||||
newChatControl={newChatControl}
|
|
||||||
onNavigateToSettings={onNavigateToSettings}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<ViewHeader
|
|
||||||
title={_(msg`Messages`)}
|
|
||||||
renderButton={renderButton}
|
|
||||||
showBorder
|
|
||||||
canGoBack={false}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<View style={[a.align_center, a.pt_3xl, web({paddingTop: '10vh'})]}>
|
<View style={[a.align_center, a.pt_3xl, web({paddingTop: '10vh'})]}>
|
||||||
<Loader size="xl" />
|
<Loader size="xl" />
|
||||||
@@ -227,7 +192,7 @@ export function MessagesScreen({navigation, route}: Props) {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</CenteredView>
|
</Layout.Center>
|
||||||
|
|
||||||
{!isLoading && !isError && (
|
{!isLoading && !isError && (
|
||||||
<NewChat onNewChat={onNewChat} control={newChatControl} />
|
<NewChat onNewChat={onNewChat} control={newChatControl} />
|
||||||
@@ -238,14 +203,7 @@ export function MessagesScreen({navigation, route}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen testID="messagesScreen">
|
<Layout.Screen testID="messagesScreen">
|
||||||
{!gtMobile && (
|
<Header newChatControl={newChatControl} />
|
||||||
<ViewHeader
|
|
||||||
title={_(msg`Messages`)}
|
|
||||||
renderButton={renderButton}
|
|
||||||
showBorder
|
|
||||||
canGoBack={false}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<NewChat onNewChat={onNewChat} control={newChatControl} />
|
<NewChat onNewChat={onNewChat} control={newChatControl} />
|
||||||
<List
|
<List
|
||||||
data={conversations}
|
data={conversations}
|
||||||
@@ -254,12 +212,6 @@ export function MessagesScreen({navigation, route}: Props) {
|
|||||||
refreshing={isPTRing}
|
refreshing={isPTRing}
|
||||||
onRefresh={onRefresh}
|
onRefresh={onRefresh}
|
||||||
onEndReached={onEndReached}
|
onEndReached={onEndReached}
|
||||||
ListHeaderComponent={
|
|
||||||
<DesktopHeader
|
|
||||||
newChatControl={newChatControl}
|
|
||||||
onNavigateToSettings={onNavigateToSettings}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
ListFooterComponent={
|
ListFooterComponent={
|
||||||
<ListFooter
|
<ListFooter
|
||||||
isFetchingNextPage={isFetchingNextPage}
|
isFetchingNextPage={isFetchingNextPage}
|
||||||
@@ -276,67 +228,65 @@ export function MessagesScreen({navigation, route}: Props) {
|
|||||||
windowSize={11}
|
windowSize={11}
|
||||||
// @ts-ignore our .web version only -sfn
|
// @ts-ignore our .web version only -sfn
|
||||||
desktopFixedHeight
|
desktopFixedHeight
|
||||||
|
sideBorders={false}
|
||||||
/>
|
/>
|
||||||
</Layout.Screen>
|
</Layout.Screen>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function DesktopHeader({
|
function Header({newChatControl}: {newChatControl: DialogControlProps}) {
|
||||||
newChatControl,
|
|
||||||
onNavigateToSettings,
|
|
||||||
}: {
|
|
||||||
newChatControl: DialogControlProps
|
|
||||||
onNavigateToSettings: () => void
|
|
||||||
}) {
|
|
||||||
const t = useTheme()
|
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {gtMobile, gtTablet} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
|
|
||||||
if (!gtMobile) {
|
const settingsLink = (
|
||||||
return null
|
<Link
|
||||||
}
|
to="/messages/settings"
|
||||||
|
label={_(msg`Chat settings`)}
|
||||||
|
size="small"
|
||||||
|
variant="ghost"
|
||||||
|
color="secondary"
|
||||||
|
shape="square"
|
||||||
|
style={[a.justify_center]}>
|
||||||
|
<ButtonIcon icon={SettingsSlider} size="md" />
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<Layout.Header.Outer>
|
||||||
style={[
|
{gtMobile ? (
|
||||||
t.atoms.bg,
|
<>
|
||||||
a.flex_row,
|
<Layout.Header.Content>
|
||||||
a.align_center,
|
<Layout.Header.TitleText>
|
||||||
a.justify_between,
|
<Trans>Messages</Trans>
|
||||||
a.gap_lg,
|
</Layout.Header.TitleText>
|
||||||
a.px_lg,
|
</Layout.Header.Content>
|
||||||
a.pr_md,
|
|
||||||
a.py_sm,
|
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
|
||||||
a.border_b,
|
{settingsLink}
|
||||||
t.atoms.border_contrast_low,
|
<Button
|
||||||
]}>
|
label={_(msg`New chat`)}
|
||||||
<Text style={[a.text_2xl, a.font_bold]}>
|
color="primary"
|
||||||
<Trans>Messages</Trans>
|
size="small"
|
||||||
</Text>
|
variant="solid"
|
||||||
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
|
onPress={newChatControl.open}>
|
||||||
<Button
|
<ButtonIcon icon={Plus} position="left" />
|
||||||
label={_(msg`Message settings`)}
|
<ButtonText>
|
||||||
color="secondary"
|
<Trans>New chat</Trans>
|
||||||
size="small"
|
</ButtonText>
|
||||||
variant="ghost"
|
</Button>
|
||||||
shape="square"
|
</View>
|
||||||
onPress={onNavigateToSettings}>
|
</>
|
||||||
<SettingsSlider size="md" style={[t.atoms.text_contrast_medium]} />
|
) : (
|
||||||
</Button>
|
<>
|
||||||
{gtTablet && (
|
<Layout.Header.MenuButton />
|
||||||
<Button
|
<Layout.Header.Content>
|
||||||
label={_(msg`New chat`)}
|
<Layout.Header.TitleText>
|
||||||
color="primary"
|
<Trans>Messages</Trans>
|
||||||
size="small"
|
</Layout.Header.TitleText>
|
||||||
variant="solid"
|
</Layout.Header.Content>
|
||||||
onPress={newChatControl.open}>
|
<Layout.Header.Slot>{settingsLink}</Layout.Header.Slot>
|
||||||
<ButtonIcon icon={Plus} position="left" />
|
</>
|
||||||
<ButtonText>
|
)}
|
||||||
<Trans>New chat</Trans>
|
</Layout.Header.Outer>
|
||||||
</ButtonText>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {useCurrentConvoId} from '#/state/messages/current-convo-id'
|
|||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {useProfileQuery} from '#/state/queries/profile'
|
import {useProfileQuery} from '#/state/queries/profile'
|
||||||
import {useSetMinimalShellMode} from '#/state/shell'
|
import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
|
||||||
import {MessagesList} from '#/screens/Messages/components/MessagesList'
|
import {MessagesList} from '#/screens/Messages/components/MessagesList'
|
||||||
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
|
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
@@ -97,7 +96,7 @@ function Inner() {
|
|||||||
|
|
||||||
if (convoState.status === ConvoStatus.Error) {
|
if (convoState.status === ConvoStatus.Error) {
|
||||||
return (
|
return (
|
||||||
<CenteredView style={[a.flex_1]} sideBorders>
|
<Layout.Center style={[a.flex_1]}>
|
||||||
<MessagesListHeader />
|
<MessagesListHeader />
|
||||||
<Error
|
<Error
|
||||||
title={_(msg`Something went wrong`)}
|
title={_(msg`Something went wrong`)}
|
||||||
@@ -105,12 +104,12 @@ function Inner() {
|
|||||||
onRetry={() => convoState.error.retry()}
|
onRetry={() => convoState.error.retry()}
|
||||||
sideBorders={false}
|
sideBorders={false}
|
||||||
/>
|
/>
|
||||||
</CenteredView>
|
</Layout.Center>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CenteredView style={[a.flex_1]} sideBorders>
|
<Layout.Center style={[a.flex_1]}>
|
||||||
{!readyToShow && <MessagesListHeader />}
|
{!readyToShow && <MessagesListHeader />}
|
||||||
<View style={[a.flex_1]}>
|
<View style={[a.flex_1]}>
|
||||||
{moderationOpts && recipient ? (
|
{moderationOpts && recipient ? (
|
||||||
@@ -140,7 +139,7 @@ function Inner() {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</CenteredView>
|
</Layout.Center>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import {useUpdateActorDeclaration} from '#/state/queries/messages/actor-declarat
|
|||||||
import {useProfileQuery} from '#/state/queries/profile'
|
import {useProfileQuery} from '#/state/queries/profile'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
|
||||||
import {ScrollView} from '#/view/com/util/Views'
|
|
||||||
import {atoms as a} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
import {Admonition} from '#/components/Admonition'
|
import {Admonition} from '#/components/Admonition'
|
||||||
import {Divider} from '#/components/Divider'
|
import {Divider} from '#/components/Divider'
|
||||||
@@ -57,8 +55,16 @@ export function MessagesSettingsScreen({}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen testID="messagesSettingsScreen">
|
<Layout.Screen testID="messagesSettingsScreen">
|
||||||
<ScrollView stickyHeaderIndices={[0]}>
|
<Layout.Header.Outer>
|
||||||
<ViewHeader title={_(msg`Chat Settings`)} showOnDesktop showBorder />
|
<Layout.Header.BackButton />
|
||||||
|
<Layout.Header.Content>
|
||||||
|
<Layout.Header.TitleText>
|
||||||
|
<Trans>Chat Settings</Trans>
|
||||||
|
</Layout.Header.TitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
<Layout.Header.Slot />
|
||||||
|
</Layout.Header.Outer>
|
||||||
|
<Layout.Content>
|
||||||
<View style={[a.p_lg, a.gap_md]}>
|
<View style={[a.p_lg, a.gap_md]}>
|
||||||
<Text style={[a.text_lg, a.font_bold]}>
|
<Text style={[a.text_lg, a.font_bold]}>
|
||||||
<Trans>Allow new messages from</Trans>
|
<Trans>Allow new messages from</Trans>
|
||||||
@@ -142,7 +148,7 @@ export function MessagesSettingsScreen({}: Props) {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</Layout.Content>
|
||||||
</Layout.Screen>
|
</Layout.Screen>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import React from 'react'
|
import {Fragment, useCallback} from 'react'
|
||||||
import {Linking, View} from 'react-native'
|
import {Linking, View} from 'react-native'
|
||||||
import {useSafeAreaFrame} from 'react-native-safe-area-context'
|
|
||||||
import {LABELS} from '@atproto/api'
|
import {LABELS} from '@atproto/api'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
@@ -19,8 +18,6 @@ import {
|
|||||||
import {isNonConfigurableModerationAuthority} from '#/state/session/additional-moderation-authorities'
|
import {isNonConfigurableModerationAuthority} from '#/state/session/additional-moderation-authorities'
|
||||||
import {useSetMinimalShellMode} from '#/state/shell'
|
import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
|
||||||
import {ScrollView} from '#/view/com/util/Views'
|
|
||||||
import {atoms as a, useBreakpoints, useTheme, ViewStyleProp} from '#/alf'
|
import {atoms as a, useBreakpoints, useTheme, ViewStyleProp} from '#/alf'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
@@ -37,6 +34,7 @@ import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Perso
|
|||||||
import * as LabelingService from '#/components/LabelingServiceCard'
|
import * as LabelingService from '#/components/LabelingServiceCard'
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
import {InlineLinkText, Link} from '#/components/Link'
|
import {InlineLinkText, Link} from '#/components/Link'
|
||||||
|
import {ListMaybePlaceholder} from '#/components/Lists'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {GlobalLabelPreference} from '#/components/moderation/LabelPreference'
|
import {GlobalLabelPreference} from '#/components/moderation/LabelPreference'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
@@ -75,35 +73,22 @@ function ErrorState({error}: {error: string}) {
|
|||||||
export function ModerationScreen(
|
export function ModerationScreen(
|
||||||
_props: NativeStackScreenProps<CommonNavigatorParams, 'Moderation'>,
|
_props: NativeStackScreenProps<CommonNavigatorParams, 'Moderation'>,
|
||||||
) {
|
) {
|
||||||
const t = useTheme()
|
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {
|
const {
|
||||||
isLoading: isPreferencesLoading,
|
isLoading: isPreferencesLoading,
|
||||||
error: preferencesError,
|
error: preferencesError,
|
||||||
data: preferences,
|
data: preferences,
|
||||||
} = usePreferencesQuery()
|
} = usePreferencesQuery()
|
||||||
const {gtMobile} = useBreakpoints()
|
|
||||||
const {height} = useSafeAreaFrame()
|
|
||||||
|
|
||||||
const isLoading = isPreferencesLoading
|
const isLoading = isPreferencesLoading
|
||||||
const error = preferencesError
|
const error = preferencesError
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen testID="moderationScreen">
|
<Layout.Screen testID="moderationScreen">
|
||||||
<CenteredView
|
<ViewHeader title={_(msg`Moderation`)} showOnDesktop />
|
||||||
testID="moderationScreen"
|
<Layout.Content>
|
||||||
style={[
|
|
||||||
t.atoms.border_contrast_low,
|
|
||||||
t.atoms.bg,
|
|
||||||
{minHeight: height},
|
|
||||||
...(gtMobile ? [a.border_l, a.border_r] : []),
|
|
||||||
]}>
|
|
||||||
<ViewHeader title={_(msg`Moderation`)} showOnDesktop />
|
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<View style={[a.w_full, a.align_center, a.pt_2xl]}>
|
<ListMaybePlaceholder isLoading={true} sideBorders={false} />
|
||||||
<Loader size="xl" fill={t.atoms.text.color} />
|
|
||||||
</View>
|
|
||||||
) : error || !preferences ? (
|
) : error || !preferences ? (
|
||||||
<ErrorState
|
<ErrorState
|
||||||
error={
|
error={
|
||||||
@@ -114,7 +99,7 @@ export function ModerationScreen(
|
|||||||
) : (
|
) : (
|
||||||
<ModerationScreenInner preferences={preferences} />
|
<ModerationScreenInner preferences={preferences} />
|
||||||
)}
|
)}
|
||||||
</CenteredView>
|
</Layout.Content>
|
||||||
</Layout.Screen>
|
</Layout.Screen>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -169,7 +154,7 @@ export function ModerationScreenInner({
|
|||||||
} = useMyLabelersQuery()
|
} = useMyLabelersQuery()
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
React.useCallback(() => {
|
useCallback(() => {
|
||||||
setMinimalShellMode(false)
|
setMinimalShellMode(false)
|
||||||
}, [setMinimalShellMode]),
|
}, [setMinimalShellMode]),
|
||||||
)
|
)
|
||||||
@@ -183,7 +168,7 @@ export function ModerationScreenInner({
|
|||||||
const ageNotSet = !preferences.userAge
|
const ageNotSet = !preferences.userAge
|
||||||
const isUnderage = (preferences.userAge || 0) < 18
|
const isUnderage = (preferences.userAge || 0) < 18
|
||||||
|
|
||||||
const onToggleAdultContentEnabled = React.useCallback(
|
const onToggleAdultContentEnabled = useCallback(
|
||||||
async (selected: boolean) => {
|
async (selected: boolean) => {
|
||||||
try {
|
try {
|
||||||
await setAdultContentPref({
|
await setAdultContentPref({
|
||||||
@@ -201,13 +186,7 @@ export function ModerationScreenInner({
|
|||||||
const disabledOnIOS = isIOS && !adultContentEnabled
|
const disabledOnIOS = isIOS && !adultContentEnabled
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<View style={[a.pt_2xl, a.px_lg, gtMobile && a.px_2xl]}>
|
||||||
contentContainerStyle={[
|
|
||||||
a.border_0,
|
|
||||||
a.pt_2xl,
|
|
||||||
a.px_lg,
|
|
||||||
gtMobile && a.px_2xl,
|
|
||||||
]}>
|
|
||||||
<Text
|
<Text
|
||||||
style={[a.text_md, a.font_bold, a.pb_md, t.atoms.text_contrast_high]}>
|
style={[a.text_md, a.font_bold, a.pb_md, t.atoms.text_contrast_high]}>
|
||||||
<Trans>Moderation tools</Trans>
|
<Trans>Moderation tools</Trans>
|
||||||
@@ -420,7 +399,7 @@ export function ModerationScreenInner({
|
|||||||
<View style={[a.rounded_sm, t.atoms.bg_contrast_25]}>
|
<View style={[a.rounded_sm, t.atoms.bg_contrast_25]}>
|
||||||
{labelers.map((labeler, i) => {
|
{labelers.map((labeler, i) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={labeler.creator.did}>
|
<Fragment key={labeler.creator.did}>
|
||||||
{i !== 0 && <Divider />}
|
{i !== 0 && <Divider />}
|
||||||
<LabelingService.Link labeler={labeler}>
|
<LabelingService.Link labeler={labeler}>
|
||||||
{state => (
|
{state => (
|
||||||
@@ -457,12 +436,12 @@ export function ModerationScreenInner({
|
|||||||
</LabelingService.Outer>
|
</LabelingService.Outer>
|
||||||
)}
|
)}
|
||||||
</LabelingService.Link>
|
</LabelingService.Link>
|
||||||
</React.Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<View style={{height: 200}} />
|
<View style={{height: 150}} />
|
||||||
</ScrollView>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {View} from 'react-native'
|
import {ScrollView, View} from 'react-native'
|
||||||
import Animated from 'react-native-reanimated'
|
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {isWeb} from '#/platform/detection'
|
import {isWeb} from '#/platform/detection'
|
||||||
import {useOnboardingDispatch} from '#/state/shell'
|
import {useOnboardingDispatch} from '#/state/shell'
|
||||||
import {ScrollView} from '#/view/com/util/Views'
|
|
||||||
import {Context} from '#/screens/Onboarding/state'
|
import {Context} from '#/screens/Onboarding/state'
|
||||||
import {
|
import {
|
||||||
atoms as a,
|
atoms as a,
|
||||||
@@ -36,7 +34,7 @@ export function Layout({children}: React.PropsWithChildren<{}>) {
|
|||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
const onboardDispatch = useOnboardingDispatch()
|
const onboardDispatch = useOnboardingDispatch()
|
||||||
const {state, dispatch} = React.useContext(Context)
|
const {state, dispatch} = React.useContext(Context)
|
||||||
const scrollview = React.useRef<Animated.ScrollView>(null)
|
const scrollview = React.useRef<ScrollView>(null)
|
||||||
const prevActiveStep = React.useRef<string>(state.activeStep)
|
const prevActiveStep = React.useRef<string>(state.activeStep)
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|||||||
@@ -5,13 +5,10 @@ import {useFocusEffect} from '@react-navigation/native'
|
|||||||
|
|
||||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||||
import {makeRecordUri} from '#/lib/strings/url-helpers'
|
import {makeRecordUri} from '#/lib/strings/url-helpers'
|
||||||
import {isWeb} from '#/platform/detection'
|
|
||||||
import {useSetMinimalShellMode} from '#/state/shell'
|
import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
import {PostLikedBy as PostLikedByComponent} from '#/view/com/post-thread/PostLikedBy'
|
import {PostLikedBy as PostLikedByComponent} from '#/view/com/post-thread/PostLikedBy'
|
||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
import {ListHeaderDesktop} from '#/components/Lists'
|
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostLikedBy'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostLikedBy'>
|
||||||
export const PostLikedByScreen = ({route}: Props) => {
|
export const PostLikedByScreen = ({route}: Props) => {
|
||||||
@@ -28,11 +25,8 @@ export const PostLikedByScreen = ({route}: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen>
|
<Layout.Screen>
|
||||||
<CenteredView sideBorders={true}>
|
<ViewHeader title={_(msg`Liked By`)} />
|
||||||
<ListHeaderDesktop title={_(msg`Liked By`)} />
|
<PostLikedByComponent uri={uri} />
|
||||||
<ViewHeader title={_(msg`Liked By`)} showBorder={!isWeb} />
|
|
||||||
<PostLikedByComponent uri={uri} />
|
|
||||||
</CenteredView>
|
|
||||||
</Layout.Screen>
|
</Layout.Screen>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {PostQuotes as PostQuotesComponent} from '#/view/com/post-thread/PostQuot
|
|||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
import {CenteredView} from '#/view/com/util/Views'
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
import {ListHeaderDesktop} from '#/components/Lists'
|
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostQuotes'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostQuotes'>
|
||||||
export const PostQuotesScreen = ({route}: Props) => {
|
export const PostQuotesScreen = ({route}: Props) => {
|
||||||
@@ -29,7 +28,6 @@ export const PostQuotesScreen = ({route}: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Layout.Screen>
|
<Layout.Screen>
|
||||||
<CenteredView sideBorders={true}>
|
<CenteredView sideBorders={true}>
|
||||||
<ListHeaderDesktop title={_(msg`Quotes`)} />
|
|
||||||
<ViewHeader title={_(msg`Quotes`)} showBorder={!isWeb} />
|
<ViewHeader title={_(msg`Quotes`)} showBorder={!isWeb} />
|
||||||
<PostQuotesComponent uri={uri} />
|
<PostQuotesComponent uri={uri} />
|
||||||
</CenteredView>
|
</CenteredView>
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {PostRepostedBy as PostRepostedByComponent} from '#/view/com/post-thread/
|
|||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
import {CenteredView} from '#/view/com/util/Views'
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
import {ListHeaderDesktop} from '#/components/Lists'
|
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostRepostedBy'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostRepostedBy'>
|
||||||
export const PostRepostedByScreen = ({route}: Props) => {
|
export const PostRepostedByScreen = ({route}: Props) => {
|
||||||
@@ -29,7 +28,6 @@ export const PostRepostedByScreen = ({route}: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Layout.Screen>
|
<Layout.Screen>
|
||||||
<CenteredView sideBorders={true}>
|
<CenteredView sideBorders={true}>
|
||||||
<ListHeaderDesktop title={_(msg`Reposted By`)} />
|
|
||||||
<ViewHeader title={_(msg`Reposted By`)} showBorder={!isWeb} />
|
<ViewHeader title={_(msg`Reposted By`)} showBorder={!isWeb} />
|
||||||
<PostRepostedByComponent uri={uri} />
|
<PostRepostedByComponent uri={uri} />
|
||||||
</CenteredView>
|
</CenteredView>
|
||||||
|
|||||||
@@ -15,14 +15,22 @@ import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
|
|||||||
import {List} from '#/view/com/util/List'
|
import {List} from '#/view/com/util/List'
|
||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
import {
|
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||||
ListFooter,
|
|
||||||
ListHeaderDesktop,
|
|
||||||
ListMaybePlaceholder,
|
|
||||||
} from '#/components/Lists'
|
|
||||||
|
|
||||||
function renderItem({item}: {item: AppBskyActorDefs.ProfileViewBasic}) {
|
function renderItem({
|
||||||
return <ProfileCardWithFollowBtn key={item.did} profile={item} />
|
item,
|
||||||
|
index,
|
||||||
|
}: {
|
||||||
|
item: AppBskyActorDefs.ProfileViewBasic
|
||||||
|
index: number
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<ProfileCardWithFollowBtn
|
||||||
|
key={item.did}
|
||||||
|
profile={item}
|
||||||
|
noBorder={index === 0}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic) {
|
function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic) {
|
||||||
@@ -93,6 +101,7 @@ export const ProfileKnownFollowersScreen = ({route}: Props) => {
|
|||||||
if (followers.length < 1) {
|
if (followers.length < 1) {
|
||||||
return (
|
return (
|
||||||
<Layout.Screen>
|
<Layout.Screen>
|
||||||
|
<ViewHeader title={_(msg`Followers you know`)} />
|
||||||
<ListMaybePlaceholder
|
<ListMaybePlaceholder
|
||||||
isLoading={isDidLoading || isFollowersLoading}
|
isLoading={isDidLoading || isFollowersLoading}
|
||||||
isError={isError}
|
isError={isError}
|
||||||
@@ -100,6 +109,8 @@ export const ProfileKnownFollowersScreen = ({route}: Props) => {
|
|||||||
emptyMessage={_(msg`You don't follow any users who follow @${name}.`)}
|
emptyMessage={_(msg`You don't follow any users who follow @${name}.`)}
|
||||||
errorMessage={cleanError(resolveError || error)}
|
errorMessage={cleanError(resolveError || error)}
|
||||||
onRetry={isError ? refetch : undefined}
|
onRetry={isError ? refetch : undefined}
|
||||||
|
topBorder={false}
|
||||||
|
sideBorders={false}
|
||||||
/>
|
/>
|
||||||
</Layout.Screen>
|
</Layout.Screen>
|
||||||
)
|
)
|
||||||
@@ -116,9 +127,6 @@ export const ProfileKnownFollowersScreen = ({route}: Props) => {
|
|||||||
onRefresh={onRefresh}
|
onRefresh={onRefresh}
|
||||||
onEndReached={onEndReached}
|
onEndReached={onEndReached}
|
||||||
onEndReachedThreshold={4}
|
onEndReachedThreshold={4}
|
||||||
ListHeaderComponent={
|
|
||||||
<ListHeaderDesktop title={_(msg`Followers you know`)} />
|
|
||||||
}
|
|
||||||
ListFooterComponent={
|
ListFooterComponent={
|
||||||
<ListFooter
|
<ListFooter
|
||||||
isFetchingNextPage={isFetchingNextPage}
|
isFetchingNextPage={isFetchingNextPage}
|
||||||
@@ -130,6 +138,7 @@ export const ProfileKnownFollowersScreen = ({route}: Props) => {
|
|||||||
desktopFixedHeight
|
desktopFixedHeight
|
||||||
initialNumToRender={initialNumToRender}
|
initialNumToRender={initialNumToRender}
|
||||||
windowSize={11}
|
windowSize={11}
|
||||||
|
sideBorders={false}
|
||||||
/>
|
/>
|
||||||
</Layout.Screen>
|
</Layout.Screen>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,10 +15,11 @@ import {isLabelerSubscribed, lookupLabelValueDefinition} from '#/lib/moderation'
|
|||||||
import {useScrollHandlers} from '#/lib/ScrollContext'
|
import {useScrollHandlers} from '#/lib/ScrollContext'
|
||||||
import {isNative} from '#/platform/detection'
|
import {isNative} from '#/platform/detection'
|
||||||
import {ListRef} from '#/view/com/util/List'
|
import {ListRef} from '#/view/com/util/List'
|
||||||
import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
import {ScrollView} from '#/view/com/util/Views'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Divider} from '#/components/Divider'
|
import {Divider} from '#/components/Divider'
|
||||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
|
||||||
|
import * as Layout from '#/components/Layout'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {LabelerLabelPreference} from '#/components/moderation/LabelPreference'
|
import {LabelerLabelPreference} from '#/components/moderation/LabelPreference'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
@@ -75,7 +76,7 @@ export const ProfileLabelsSection = React.forwardRef<
|
|||||||
}, [isFocused, scrollElRef, setScrollViewTag])
|
}, [isFocused, scrollElRef, setScrollViewTag])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CenteredView style={{flex: 1, minHeight}} sideBorders>
|
<Layout.Center style={{flex: 1, minHeight}}>
|
||||||
{isLabelerLoading ? (
|
{isLabelerLoading ? (
|
||||||
<View style={[a.w_full, a.align_center]}>
|
<View style={[a.w_full, a.align_center]}>
|
||||||
<Loader size="xl" />
|
<Loader size="xl" />
|
||||||
@@ -95,7 +96,7 @@ export const ProfileLabelsSection = React.forwardRef<
|
|||||||
headerHeight={headerHeight}
|
headerHeight={headerHeight}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</CenteredView>
|
</Layout.Center>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,15 @@ export function AboutSettingsScreen({}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen>
|
<Layout.Screen>
|
||||||
<Layout.Header title={_(msg`About`)} />
|
<Layout.Header.Outer>
|
||||||
|
<Layout.Header.BackButton />
|
||||||
|
<Layout.Header.Content>
|
||||||
|
<Layout.Header.TitleText>
|
||||||
|
<Trans>About</Trans>
|
||||||
|
</Layout.Header.TitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
<Layout.Header.Slot />
|
||||||
|
</Layout.Header.Outer>
|
||||||
<Layout.Content>
|
<Layout.Content>
|
||||||
<SettingsList.Container>
|
<SettingsList.Container>
|
||||||
<SettingsList.LinkItem
|
<SettingsList.LinkItem
|
||||||
|
|||||||
@@ -39,7 +39,15 @@ export function AccessibilitySettingsScreen({}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen>
|
<Layout.Screen>
|
||||||
<Layout.Header title={_(msg`Accessibility`)} />
|
<Layout.Header.Outer>
|
||||||
|
<Layout.Header.BackButton />
|
||||||
|
<Layout.Header.Content>
|
||||||
|
<Layout.Header.TitleText>
|
||||||
|
<Trans>Accessibility</Trans>
|
||||||
|
</Layout.Header.TitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
<Layout.Header.Slot />
|
||||||
|
</Layout.Header.Outer>
|
||||||
<Layout.Content>
|
<Layout.Content>
|
||||||
<SettingsList.Container>
|
<SettingsList.Container>
|
||||||
<SettingsList.Group contentContainerStyle={[a.gap_sm]}>
|
<SettingsList.Group contentContainerStyle={[a.gap_sm]}>
|
||||||
|
|||||||
@@ -38,7 +38,15 @@ export function AccountSettingsScreen({}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen>
|
<Layout.Screen>
|
||||||
<Layout.Header title={_(msg`Account`)} />
|
<Layout.Header.Outer>
|
||||||
|
<Layout.Header.BackButton />
|
||||||
|
<Layout.Header.Content>
|
||||||
|
<Layout.Header.TitleText>
|
||||||
|
<Trans>Account</Trans>
|
||||||
|
</Layout.Header.TitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
<Layout.Header.Slot />
|
||||||
|
</Layout.Header.Outer>
|
||||||
<Layout.Content>
|
<Layout.Content>
|
||||||
<SettingsList.Container>
|
<SettingsList.Container>
|
||||||
<SettingsList.Item>
|
<SettingsList.Item>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Alert, View} from 'react-native'
|
import {Alert, View} from 'react-native'
|
||||||
import {Image} from 'expo-image'
|
import {Image} from 'expo-image'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import * as AppIcon from '@mozzius/expo-dynamic-app-icon'
|
import * as AppIcon from '@mozzius/expo-dynamic-app-icon'
|
||||||
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||||
@@ -20,7 +20,15 @@ export function AppIconSettingsScreen({}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen>
|
<Layout.Screen>
|
||||||
<Layout.Header title={_('App Icon')} />
|
<Layout.Header.Outer>
|
||||||
|
<Layout.Header.BackButton />
|
||||||
|
<Layout.Header.Content>
|
||||||
|
<Layout.Header.TitleText>
|
||||||
|
<Trans>App Icon</Trans>
|
||||||
|
</Layout.Header.TitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
<Layout.Header.Slot />
|
||||||
|
</Layout.Header.Outer>
|
||||||
<Layout.Content
|
<Layout.Content
|
||||||
contentContainerStyle={[a.py_2xl, a.px_xl, {paddingBottom: 100}]}>
|
contentContainerStyle={[a.py_2xl, a.px_xl, {paddingBottom: 100}]}>
|
||||||
<Text style={[a.text_lg, a.font_heavy]}>Defaults</Text>
|
<Text style={[a.text_lg, a.font_heavy]}>Defaults</Text>
|
||||||
|
|||||||
@@ -44,7 +44,15 @@ export function AppPasswordsScreen({}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen testID="AppPasswordsScreen">
|
<Layout.Screen testID="AppPasswordsScreen">
|
||||||
<Layout.Header title={_(msg`App Passwords`)} />
|
<Layout.Header.Outer>
|
||||||
|
<Layout.Header.BackButton />
|
||||||
|
<Layout.Header.Content>
|
||||||
|
<Layout.Header.TitleText>
|
||||||
|
<Trans>App Passwords</Trans>
|
||||||
|
</Layout.Header.TitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
<Layout.Header.Slot />
|
||||||
|
</Layout.Header.Outer>
|
||||||
<Layout.Content>
|
<Layout.Content>
|
||||||
{error ? (
|
{error ? (
|
||||||
<ErrorScreen
|
<ErrorScreen
|
||||||
|
|||||||
@@ -79,7 +79,15 @@ export function AppearanceSettingsScreen({}: Props) {
|
|||||||
return (
|
return (
|
||||||
<LayoutAnimationConfig skipExiting skipEntering>
|
<LayoutAnimationConfig skipExiting skipEntering>
|
||||||
<Layout.Screen testID="preferencesThreadsScreen">
|
<Layout.Screen testID="preferencesThreadsScreen">
|
||||||
<Layout.Header title={_(msg`Appearance`)} />
|
<Layout.Header.Outer>
|
||||||
|
<Layout.Header.BackButton />
|
||||||
|
<Layout.Header.Content>
|
||||||
|
<Layout.Header.TitleText>
|
||||||
|
<Trans>Appearance</Trans>
|
||||||
|
</Layout.Header.TitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
<Layout.Header.Slot />
|
||||||
|
</Layout.Header.Outer>
|
||||||
<Layout.Content>
|
<Layout.Content>
|
||||||
<SettingsList.Container>
|
<SettingsList.Container>
|
||||||
<AppearanceToggleButtonGroup
|
<AppearanceToggleButtonGroup
|
||||||
|
|||||||
@@ -32,7 +32,15 @@ export function ContentAndMediaSettingsScreen({}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen>
|
<Layout.Screen>
|
||||||
<Layout.Header title={_(msg`Content and Media`)} />
|
<Layout.Header.Outer>
|
||||||
|
<Layout.Header.BackButton />
|
||||||
|
<Layout.Header.Content>
|
||||||
|
<Layout.Header.TitleText>
|
||||||
|
<Trans>Content & Media</Trans>
|
||||||
|
</Layout.Header.TitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
<Layout.Header.Slot />
|
||||||
|
</Layout.Header.Outer>
|
||||||
<Layout.Content>
|
<Layout.Content>
|
||||||
<SettingsList.Container>
|
<SettingsList.Container>
|
||||||
<SettingsList.LinkItem
|
<SettingsList.LinkItem
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {Fragment} from 'react'
|
import {Fragment} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
|
|
||||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||||
import {
|
import {
|
||||||
@@ -23,10 +22,17 @@ type Props = NativeStackScreenProps<
|
|||||||
'PreferencesExternalEmbeds'
|
'PreferencesExternalEmbeds'
|
||||||
>
|
>
|
||||||
export function ExternalMediaPreferencesScreen({}: Props) {
|
export function ExternalMediaPreferencesScreen({}: Props) {
|
||||||
const {_} = useLingui()
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen testID="externalMediaPreferencesScreen">
|
<Layout.Screen testID="externalMediaPreferencesScreen">
|
||||||
<Layout.Header title={_(msg`External Media Preferences`)} />
|
<Layout.Header.Outer>
|
||||||
|
<Layout.Header.BackButton />
|
||||||
|
<Layout.Header.Content>
|
||||||
|
<Layout.Header.TitleText>
|
||||||
|
<Trans>External Media Preferences</Trans>
|
||||||
|
</Layout.Header.TitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
<Layout.Header.Slot />
|
||||||
|
</Layout.Header.Outer>
|
||||||
<Layout.Content>
|
<Layout.Content>
|
||||||
<SettingsList.Container>
|
<SettingsList.Container>
|
||||||
<SettingsList.Item>
|
<SettingsList.Item>
|
||||||
|
|||||||
@@ -46,7 +46,15 @@ export function FollowingFeedPreferencesScreen({}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen testID="followingFeedPreferencesScreen">
|
<Layout.Screen testID="followingFeedPreferencesScreen">
|
||||||
<Layout.Header title={_(msg`Following Feed Preferences`)} />
|
<Layout.Header.Outer>
|
||||||
|
<Layout.Header.BackButton />
|
||||||
|
<Layout.Header.Content>
|
||||||
|
<Layout.Header.TitleText>
|
||||||
|
<Trans>Following Feed Preferences</Trans>
|
||||||
|
</Layout.Header.TitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
<Layout.Header.Slot />
|
||||||
|
</Layout.Header.Outer>
|
||||||
<Layout.Content>
|
<Layout.Content>
|
||||||
<SettingsList.Container>
|
<SettingsList.Container>
|
||||||
<SettingsList.Item>
|
<SettingsList.Item>
|
||||||
|
|||||||
@@ -64,7 +64,15 @@ export function LanguageSettingsScreen({}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen testID="PreferencesLanguagesScreen">
|
<Layout.Screen testID="PreferencesLanguagesScreen">
|
||||||
<Layout.Header title={_(msg`Languages`)} />
|
<Layout.Header.Outer>
|
||||||
|
<Layout.Header.BackButton />
|
||||||
|
<Layout.Header.Content>
|
||||||
|
<Layout.Header.TitleText>
|
||||||
|
<Trans>Languages</Trans>
|
||||||
|
</Layout.Header.TitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
<Layout.Header.Slot />
|
||||||
|
</Layout.Header.Outer>
|
||||||
<Layout.Content>
|
<Layout.Content>
|
||||||
<SettingsList.Container>
|
<SettingsList.Container>
|
||||||
<SettingsList.Group iconInset={false}>
|
<SettingsList.Group iconInset={false}>
|
||||||
|
|||||||
@@ -33,7 +33,15 @@ export function NotificationSettingsScreen({}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen>
|
<Layout.Screen>
|
||||||
<Layout.Header title={_(msg`Notification Settings`)} />
|
<Layout.Header.Outer>
|
||||||
|
<Layout.Header.BackButton />
|
||||||
|
<Layout.Header.Content>
|
||||||
|
<Layout.Header.TitleText>
|
||||||
|
<Trans>Notification Settings</Trans>
|
||||||
|
</Layout.Header.TitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
<Layout.Header.Slot />
|
||||||
|
</Layout.Header.Outer>
|
||||||
<Layout.Content>
|
<Layout.Content>
|
||||||
{isQueryError ? (
|
{isQueryError ? (
|
||||||
<Error
|
<Error
|
||||||
|
|||||||
@@ -29,7 +29,15 @@ export function PrivacyAndSecuritySettingsScreen({}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen>
|
<Layout.Screen>
|
||||||
<Layout.Header title={_(msg`Privacy and Security`)} />
|
<Layout.Header.Outer>
|
||||||
|
<Layout.Header.BackButton />
|
||||||
|
<Layout.Header.Content>
|
||||||
|
<Layout.Header.TitleText>
|
||||||
|
<Trans>Privacy and Security</Trans>
|
||||||
|
</Layout.Header.TitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
<Layout.Header.Slot />
|
||||||
|
</Layout.Header.Outer>
|
||||||
<Layout.Content>
|
<Layout.Content>
|
||||||
<SettingsList.Container>
|
<SettingsList.Container>
|
||||||
<SettingsList.Item>
|
<SettingsList.Item>
|
||||||
|
|||||||
@@ -73,7 +73,15 @@ export function SettingsScreen({}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen>
|
<Layout.Screen>
|
||||||
<Layout.Header title={_(msg`Settings`)} />
|
<Layout.Header.Outer>
|
||||||
|
<Layout.Header.BackButton />
|
||||||
|
<Layout.Header.Content>
|
||||||
|
<Layout.Header.TitleText>
|
||||||
|
<Trans>Settings</Trans>
|
||||||
|
</Layout.Header.TitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
<Layout.Header.Slot />
|
||||||
|
</Layout.Header.Outer>
|
||||||
<Layout.Content>
|
<Layout.Content>
|
||||||
<SettingsList.Container>
|
<SettingsList.Container>
|
||||||
<View
|
<View
|
||||||
|
|||||||
@@ -38,7 +38,15 @@ export function ThreadPreferencesScreen({}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen testID="threadPreferencesScreen">
|
<Layout.Screen testID="threadPreferencesScreen">
|
||||||
<Layout.Header title={_(msg`Thread Preferences`)} />
|
<Layout.Header.Outer>
|
||||||
|
<Layout.Header.BackButton />
|
||||||
|
<Layout.Header.Content>
|
||||||
|
<Layout.Header.TitleText>
|
||||||
|
<Trans>Thread Preferences</Trans>
|
||||||
|
</Layout.Header.TitleText>
|
||||||
|
</Layout.Header.Content>
|
||||||
|
<Layout.Header.Slot />
|
||||||
|
</Layout.Header.Outer>
|
||||||
<Layout.Content>
|
<Layout.Content>
|
||||||
<SettingsList.Container>
|
<SettingsList.Container>
|
||||||
<SettingsList.Group>
|
<SettingsList.Group>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Modal, View} from 'react-native'
|
import {Modal, ScrollView, View} from 'react-native'
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {StatusBar} from 'expo-status-bar'
|
import {StatusBar} from 'expo-status-bar'
|
||||||
import {msg, plural, Trans} from '@lingui/macro'
|
import {msg, plural, Trans} from '@lingui/macro'
|
||||||
@@ -9,7 +9,6 @@ import {logger} from '#/logger'
|
|||||||
import {isIOS, isWeb} from '#/platform/detection'
|
import {isIOS, isWeb} from '#/platform/detection'
|
||||||
import {isSignupQueued, useAgent, useSessionApi} from '#/state/session'
|
import {isSignupQueued, useAgent, useSessionApi} from '#/state/session'
|
||||||
import {useOnboardingDispatch} from '#/state/shell'
|
import {useOnboardingDispatch} from '#/state/shell'
|
||||||
import {ScrollView} from '#/view/com/util/Views'
|
|
||||||
import {Logo} from '#/view/icons/Logo'
|
import {Logo} from '#/view/icons/Logo'
|
||||||
import {atoms as a, native, useBreakpoints, useTheme, web} from '#/alf'
|
import {atoms as a, native, useBreakpoints, useTheme, web} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Keyboard, TouchableOpacity, View} from 'react-native'
|
import {Keyboard, View} from 'react-native'
|
||||||
import {KeyboardAwareScrollView} from 'react-native-keyboard-controller'
|
import {KeyboardAwareScrollView} from 'react-native-keyboard-controller'
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {Image} from 'expo-image'
|
import {Image} from 'expo-image'
|
||||||
@@ -10,13 +10,12 @@ import {
|
|||||||
ModerationOpts,
|
ModerationOpts,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
|
import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {msg, Plural, Trans} from '@lingui/macro'
|
import {msg, Plural, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useFocusEffect, useNavigation} from '@react-navigation/native'
|
import {useFocusEffect, useNavigation} from '@react-navigation/native'
|
||||||
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||||
|
|
||||||
import {HITSLOP_10, STARTER_PACK_MAX_SIZE} from '#/lib/constants'
|
import {STARTER_PACK_MAX_SIZE} from '#/lib/constants'
|
||||||
import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController'
|
import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController'
|
||||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||||
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
|
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
|
||||||
@@ -29,7 +28,7 @@ import {
|
|||||||
parseStarterPackUri,
|
parseStarterPackUri,
|
||||||
} from '#/lib/strings/starter-pack'
|
} from '#/lib/strings/starter-pack'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {isAndroid, isNative, isWeb} from '#/platform/detection'
|
import {isNative} from '#/platform/detection'
|
||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {useAllListMembersQuery} from '#/state/queries/list-members'
|
import {useAllListMembersQuery} from '#/state/queries/list-members'
|
||||||
import {useProfileQuery} from '#/state/queries/profile'
|
import {useProfileQuery} from '#/state/queries/profile'
|
||||||
@@ -147,7 +146,6 @@ function WizardInner({
|
|||||||
}) {
|
}) {
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const [state, dispatch] = useWizardState()
|
const [state, dispatch] = useWizardState()
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
@@ -283,45 +281,24 @@ function WizardInner({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CenteredView style={[a.flex_1]} sideBorders>
|
<CenteredView style={[a.flex_1]} sideBorders>
|
||||||
<View
|
<Layout.Header.Outer>
|
||||||
style={[
|
<Layout.Header.BackButton
|
||||||
a.flex_row,
|
label={_(msg`Back`)}
|
||||||
a.pb_sm,
|
accessibilityHint={_(msg`Go back to the previous step`)}
|
||||||
a.px_md,
|
onPress={evt => {
|
||||||
a.border_b,
|
if (state.currentStep !== 'Details') {
|
||||||
t.atoms.border_contrast_medium,
|
evt.preventDefault()
|
||||||
a.gap_sm,
|
dispatch({type: 'Back'})
|
||||||
a.justify_between,
|
}
|
||||||
a.align_center,
|
}}
|
||||||
isAndroid && a.pt_sm,
|
/>
|
||||||
isWeb && [a.py_md],
|
<Layout.Header.Content>
|
||||||
]}>
|
<Layout.Header.TitleText>
|
||||||
<View style={[{width: 65}]}>
|
{currUiStrings.header}
|
||||||
<TouchableOpacity
|
</Layout.Header.TitleText>
|
||||||
testID="viewHeaderDrawerBtn"
|
</Layout.Header.Content>
|
||||||
hitSlop={HITSLOP_10}
|
<Layout.Header.Slot />
|
||||||
accessibilityRole="button"
|
</Layout.Header.Outer>
|
||||||
accessibilityLabel={_(msg`Back`)}
|
|
||||||
accessibilityHint={_(msg`Go back to the previous step`)}
|
|
||||||
onPress={() => {
|
|
||||||
if (state.currentStep === 'Details') {
|
|
||||||
navigation.pop()
|
|
||||||
} else {
|
|
||||||
dispatch({type: 'Back'})
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
size={18}
|
|
||||||
icon="angle-left"
|
|
||||||
color={t.atoms.text.color}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
<Text style={[a.flex_1, a.font_bold, a.text_lg, a.text_center]}>
|
|
||||||
{currUiStrings.header}
|
|
||||||
</Text>
|
|
||||||
<View style={[{width: 65}]} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Container>
|
<Container>
|
||||||
{state.currentStep === 'Details' ? (
|
{state.currentStep === 'Details' ? (
|
||||||
@@ -463,17 +440,17 @@ function Footer({
|
|||||||
<Trans>
|
<Trans>
|
||||||
<Text style={[a.font_bold, textStyles]}>You</Text> and
|
<Text style={[a.font_bold, textStyles]}>You</Text> and
|
||||||
<Text> </Text>
|
<Text> </Text>
|
||||||
<Text style={[a.font_bold, textStyles]}>
|
<Text style={[a.font_bold, textStyles]} emoji>
|
||||||
{getName(items[1] /* [0] is self, skip it */)}{' '}
|
{getName(items[1] /* [0] is self, skip it */)}{' '}
|
||||||
</Text>
|
</Text>
|
||||||
are included in your starter pack
|
are included in your starter pack
|
||||||
</Trans>
|
</Trans>
|
||||||
) : items.length > 2 ? (
|
) : items.length > 2 ? (
|
||||||
<Trans context="profiles">
|
<Trans context="profiles">
|
||||||
<Text style={[a.font_bold, textStyles]}>
|
<Text style={[a.font_bold, textStyles]} emoji>
|
||||||
{getName(items[1] /* [0] is self, skip it */)},{' '}
|
{getName(items[1] /* [0] is self, skip it */)},{' '}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[a.font_bold, textStyles]}>
|
<Text style={[a.font_bold, textStyles]} emoji>
|
||||||
{getName(items[2])},{' '}
|
{getName(items[2])},{' '}
|
||||||
</Text>
|
</Text>
|
||||||
and{' '}
|
and{' '}
|
||||||
@@ -504,29 +481,29 @@ function Footer({
|
|||||||
{
|
{
|
||||||
items.length === 1 ? (
|
items.length === 1 ? (
|
||||||
<Trans>
|
<Trans>
|
||||||
<Text style={[a.font_bold, textStyles]}>
|
<Text style={[a.font_bold, textStyles]} emoji>
|
||||||
{getName(items[0])}
|
{getName(items[0])}
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
is included in your starter pack
|
is included in your starter pack
|
||||||
</Trans>
|
</Trans>
|
||||||
) : items.length === 2 ? (
|
) : items.length === 2 ? (
|
||||||
<Trans>
|
<Trans>
|
||||||
<Text style={[a.font_bold, textStyles]}>
|
<Text style={[a.font_bold, textStyles]} emoji>
|
||||||
{getName(items[0])}
|
{getName(items[0])}
|
||||||
</Text>{' '}
|
</Text>{' '}
|
||||||
and
|
and
|
||||||
<Text> </Text>
|
<Text> </Text>
|
||||||
<Text style={[a.font_bold, textStyles]}>
|
<Text style={[a.font_bold, textStyles]} emoji>
|
||||||
{getName(items[1])}{' '}
|
{getName(items[1])}{' '}
|
||||||
</Text>
|
</Text>
|
||||||
are included in your starter pack
|
are included in your starter pack
|
||||||
</Trans>
|
</Trans>
|
||||||
) : items.length > 2 ? (
|
) : items.length > 2 ? (
|
||||||
<Trans context="feeds">
|
<Trans context="feeds">
|
||||||
<Text style={[a.font_bold, textStyles]}>
|
<Text style={[a.font_bold, textStyles]} emoji>
|
||||||
{getName(items[0])},{' '}
|
{getName(items[0])},{' '}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[a.font_bold, textStyles]}>
|
<Text style={[a.font_bold, textStyles]} emoji>
|
||||||
{getName(items[1])},{' '}
|
{getName(items[1])},{' '}
|
||||||
</Text>
|
</Text>
|
||||||
and{' '}
|
and{' '}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export function FeedPage({
|
|||||||
}, [scrollToTop, feed, queryClient, setHasNew])
|
}, [scrollToTop, feed, queryClient, setHasNew])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View testID={testID} style={s.h100pct}>
|
<View testID={testID}>
|
||||||
<MainScrollProvider>
|
<MainScrollProvider>
|
||||||
<FeedFeedbackProvider value={feedFeedback}>
|
<FeedFeedbackProvider value={feedFeedback}>
|
||||||
<Feed
|
<Feed
|
||||||
|
|||||||
@@ -1,26 +1,27 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyleSheet, View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import Animated from 'react-native-reanimated'
|
import Animated from 'react-native-reanimated'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {useMinimalShellHeaderTransform} from '#/lib/hooks/useMinimalShellTransform'
|
import {useMinimalShellHeaderTransform} from '#/lib/hooks/useMinimalShellTransform'
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
|
||||||
import {useKawaiiMode} from '#/state/preferences/kawaii'
|
import {useKawaiiMode} from '#/state/preferences/kawaii'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||||
|
import {HomeHeaderLayoutMobile} from '#/view/com/home/HomeHeaderLayoutMobile'
|
||||||
import {Logo} from '#/view/icons/Logo'
|
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 {Hashtag_Stroke2_Corner0_Rounded as FeedsIcon} from '#/components/icons/Hashtag'
|
||||||
|
import * as Layout from '#/components/Layout'
|
||||||
import {Link} from '#/components/Link'
|
import {Link} from '#/components/Link'
|
||||||
import {HomeHeaderLayoutMobile} from './HomeHeaderLayoutMobile'
|
|
||||||
|
|
||||||
export function HomeHeaderLayout(props: {
|
export function HomeHeaderLayout(props: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
tabBarAnchor: JSX.Element | null | undefined
|
tabBarAnchor: JSX.Element | null | undefined
|
||||||
}) {
|
}) {
|
||||||
const {isMobile} = useWebMediaQueries()
|
const {gtMobile} = useBreakpoints()
|
||||||
if (isMobile) {
|
if (!gtMobile) {
|
||||||
return <HomeHeaderLayoutMobile {...props} />
|
return <HomeHeaderLayoutMobile {...props} />
|
||||||
} else {
|
} else {
|
||||||
return <HomeHeaderLayoutDesktopAndTablet {...props} />
|
return <HomeHeaderLayoutDesktopAndTablet {...props} />
|
||||||
@@ -40,98 +41,43 @@ function HomeHeaderLayoutDesktopAndTablet({
|
|||||||
const {hasSession} = useSession()
|
const {hasSession} = useSession()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const kawaii = useKawaiiMode()
|
const kawaii = useKawaiiMode()
|
||||||
|
const gutter = useGutterStyles()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{hasSession && (
|
{hasSession && (
|
||||||
<View
|
<Layout.Center>
|
||||||
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},
|
|
||||||
]}>
|
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[a.flex_row, a.align_center, a.pt_md, gutter, t.atoms.bg]}>
|
||||||
a.absolute,
|
<View style={{width: 34}} />
|
||||||
a.inset_0,
|
<View style={[a.flex_1, a.align_center, a.justify_center]}>
|
||||||
a.pt_lg,
|
<Logo width={kawaii ? 60 : 28} />
|
||||||
a.m_auto,
|
</View>
|
||||||
kawaii && {paddingTop: 4, paddingBottom: 0},
|
<Link
|
||||||
{
|
to="/feeds"
|
||||||
width: kawaii ? 84 : 28,
|
hitSlop={10}
|
||||||
},
|
label={_(msg`View your feeds and explore more`)}
|
||||||
]}>
|
size="small"
|
||||||
<Logo width={kawaii ? 60 : 28} />
|
variant="ghost"
|
||||||
|
color="secondary"
|
||||||
|
shape="square"
|
||||||
|
style={[a.justify_center]}>
|
||||||
|
<ButtonIcon icon={FeedsIcon} size="lg" />
|
||||||
|
</Link>
|
||||||
</View>
|
</View>
|
||||||
|
</Layout.Center>
|
||||||
<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>
|
|
||||||
)}
|
)}
|
||||||
{tabBarAnchor}
|
{tabBarAnchor}
|
||||||
<Animated.View
|
<Layout.Center
|
||||||
onLayout={e => {
|
style={[a.sticky, a.z_10, a.align_center, t.atoms.bg, {top: 0}]}>
|
||||||
headerHeight.set(e.nativeEvent.layout.height)
|
<Animated.View
|
||||||
}}
|
onLayout={e => {
|
||||||
style={[
|
headerHeight.set(e.nativeEvent.layout.height)
|
||||||
t.atoms.bg,
|
}}
|
||||||
t.atoms.border_contrast_low,
|
style={[headerMinimalShellTransform]}>
|
||||||
styles.bar,
|
{children}
|
||||||
styles.tabBar,
|
</Animated.View>
|
||||||
headerMinimalShellTransform,
|
</Layout.Center>
|
||||||
]}>
|
|
||||||
{children}
|
|
||||||
</Animated.View>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 React from 'react'
|
||||||
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import Animated from 'react-native-reanimated'
|
import Animated from 'react-native-reanimated'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {HITSLOP_10} from '#/lib/constants'
|
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 {useMinimalShellHeaderTransform} from '#/lib/hooks/useMinimalShellTransform'
|
||||||
import {usePalette} from '#/lib/hooks/usePalette'
|
import {emitSoftReset} from '#/state/events'
|
||||||
import {isWeb} from '#/platform/detection'
|
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {useSetDrawerOpen} from '#/state/shell/drawer-open'
|
|
||||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||||
import {Logo} from '#/view/icons/Logo'
|
import {Logo} from '#/view/icons/Logo'
|
||||||
import {atoms} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {useTheme} from '#/alf'
|
import {ButtonIcon} from '#/components/Button'
|
||||||
import {atoms as a} from '#/alf'
|
|
||||||
import {ColorPalette_Stroke2_Corner0_Rounded as ColorPalette} from '#/components/icons/ColorPalette'
|
|
||||||
import {Hashtag_Stroke2_Corner0_Rounded as FeedsIcon} from '#/components/icons/Hashtag'
|
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 {Link} from '#/components/Link'
|
||||||
import {IS_DEV} from '#/env'
|
|
||||||
|
|
||||||
export function HomeHeaderLayoutMobile({
|
export function HomeHeaderLayoutMobile({
|
||||||
children,
|
children,
|
||||||
@@ -28,58 +25,50 @@ export function HomeHeaderLayoutMobile({
|
|||||||
tabBarAnchor: JSX.Element | null | undefined
|
tabBarAnchor: JSX.Element | null | undefined
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const pal = usePalette('default')
|
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const setDrawerOpen = useSetDrawerOpen()
|
|
||||||
const {headerHeight} = useShellLayout()
|
const {headerHeight} = useShellLayout()
|
||||||
const headerMinimalShellTransform = useMinimalShellHeaderTransform()
|
const headerMinimalShellTransform = useMinimalShellHeaderTransform()
|
||||||
const {hasSession} = useSession()
|
const {hasSession} = useSession()
|
||||||
|
const playHaptic = useHaptics()
|
||||||
const onPressAvi = React.useCallback(() => {
|
|
||||||
setDrawerOpen(true)
|
|
||||||
}, [setDrawerOpen])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View
|
<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 => {
|
onLayout={e => {
|
||||||
headerHeight.set(e.nativeEvent.layout.height)
|
headerHeight.set(e.nativeEvent.layout.height)
|
||||||
}}>
|
}}>
|
||||||
<View style={[pal.view, styles.topBar]}>
|
<Layout.Header.Outer noBottomBorder>
|
||||||
<View style={[{width: 100}]}>
|
<Layout.Header.Slot>
|
||||||
<TouchableOpacity
|
<Layout.Header.MenuButton />
|
||||||
testID="viewHeaderDrawerBtn"
|
</Layout.Header.Slot>
|
||||||
onPress={onPressAvi}
|
|
||||||
accessibilityRole="button"
|
<View style={[a.flex_1, a.align_center]}>
|
||||||
accessibilityLabel={_(msg`Open navigation`)}
|
<PressableScale
|
||||||
accessibilityHint={_(
|
targetScale={0.9}
|
||||||
msg`Access profile and other navigation links`,
|
onPress={() => {
|
||||||
)}
|
emitSoftReset()
|
||||||
hitSlop={HITSLOP_10}>
|
}}
|
||||||
<Menu size="lg" fill={t.atoms.text_contrast_medium.color} />
|
onPressIn={() => {
|
||||||
</TouchableOpacity>
|
playHaptic('Heavy')
|
||||||
|
}}
|
||||||
|
onPressOut={() => {
|
||||||
|
playHaptic('Light')
|
||||||
|
}}>
|
||||||
|
<Logo width={30} />
|
||||||
|
</PressableScale>
|
||||||
</View>
|
</View>
|
||||||
<View>
|
|
||||||
<Logo width={30} />
|
<Layout.Header.Slot>
|
||||||
</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>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{hasSession && (
|
{hasSession && (
|
||||||
<Link
|
<Link
|
||||||
testID="viewHeaderHomeFeedPrefsBtn"
|
testID="viewHeaderHomeFeedPrefsBtn"
|
||||||
@@ -93,40 +82,15 @@ export function HomeHeaderLayoutMobile({
|
|||||||
style={[
|
style={[
|
||||||
a.justify_center,
|
a.justify_center,
|
||||||
{
|
{
|
||||||
marginTop: 2,
|
marginRight: -Layout.BUTTON_VISUAL_ALIGNMENT_OFFSET,
|
||||||
marginRight: -6,
|
|
||||||
},
|
},
|
||||||
]}>
|
]}>
|
||||||
<FeedsIcon size="lg" fill={t.atoms.text_contrast_medium.color} />
|
<ButtonIcon icon={FeedsIcon} size="lg" />
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</View>
|
</Layout.Header.Slot>
|
||||||
</View>
|
</Layout.Header.Outer>
|
||||||
{children}
|
{children}
|
||||||
</Animated.View>
|
</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'
|
} from '@fortawesome/react-native-fontawesome'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
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 {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
import {colors, s} from '#/lib/styles'
|
import {colors, s} from '#/lib/styles'
|
||||||
import {useLightbox, useLightboxControls} from '#/state/lightbox'
|
import {useLightbox, useLightboxControls} from '#/state/lightbox'
|
||||||
@@ -28,7 +28,6 @@ export function Lightbox() {
|
|||||||
const {activeLightbox} = useLightbox()
|
const {activeLightbox} = useLightbox()
|
||||||
const {closeLightbox} = useLightboxControls()
|
const {closeLightbox} = useLightboxControls()
|
||||||
const isActive = !!activeLightbox
|
const isActive = !!activeLightbox
|
||||||
useWebBodyScrollLock(isActive)
|
|
||||||
|
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
return null
|
return null
|
||||||
@@ -37,11 +36,14 @@ export function Lightbox() {
|
|||||||
const initialIndex = activeLightbox.index
|
const initialIndex = activeLightbox.index
|
||||||
const imgs = activeLightbox.images
|
const imgs = activeLightbox.images
|
||||||
return (
|
return (
|
||||||
<LightboxInner
|
<>
|
||||||
imgs={imgs}
|
<RemoveScrollBar />
|
||||||
initialIndex={initialIndex}
|
<LightboxInner
|
||||||
onClose={closeLightbox}
|
imgs={imgs}
|
||||||
/>
|
initialIndex={initialIndex}
|
||||||
|
onClose={closeLightbox}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {usePalette} from '#/lib/hooks/usePalette'
|
|||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {s} from '#/lib/styles'
|
import {s} from '#/lib/styles'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {isWeb} from '#/platform/detection'
|
|
||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {MyListsFilter, useMyListsQuery} from '#/state/queries/my-lists'
|
import {MyListsFilter, useMyListsQuery} from '#/state/queries/my-lists'
|
||||||
import {EmptyState} from '#/view/com/util/EmptyState'
|
import {EmptyState} from '#/view/com/util/EmptyState'
|
||||||
@@ -110,7 +109,7 @@ export function MyLists({
|
|||||||
) : (
|
) : (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
(index !== 0 || isWeb) && a.border_t,
|
index !== 0 && a.border_t,
|
||||||
t.atoms.border_contrast_low,
|
t.atoms.border_contrast_low,
|
||||||
a.px_lg,
|
a.px_lg,
|
||||||
a.py_lg,
|
a.py_lg,
|
||||||
@@ -141,8 +140,6 @@ export function MyLists({
|
|||||||
}
|
}
|
||||||
contentContainerStyle={[s.contentContainer]}
|
contentContainerStyle={[s.contentContainer]}
|
||||||
removeClippedSubviews={true}
|
removeClippedSubviews={true}
|
||||||
// @ts-ignore our .web version only -prf
|
|
||||||
desktopFixedHeight
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@@ -160,8 +157,8 @@ export function MyLists({
|
|||||||
onRefresh={onRefresh}
|
onRefresh={onRefresh}
|
||||||
contentContainerStyle={[s.contentContainer]}
|
contentContainerStyle={[s.contentContainer]}
|
||||||
removeClippedSubviews={true}
|
removeClippedSubviews={true}
|
||||||
// @ts-ignore our .web version only -prf
|
|
||||||
desktopFixedHeight
|
desktopFixedHeight
|
||||||
|
sideBorders={false}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
|
import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
|
||||||
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
|
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
|
||||||
|
import {RemoveScrollBar} from 'react-remove-scroll-bar'
|
||||||
|
|
||||||
import {usePalette} from '#/lib/hooks/usePalette'
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
|
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
import type {Modal as ModalIface} from '#/state/modals'
|
import type {Modal as ModalIface} from '#/state/modals'
|
||||||
import {useModalControls, useModals} from '#/state/modals'
|
import {useModalControls, useModals} from '#/state/modals'
|
||||||
@@ -22,7 +22,6 @@ import * as VerifyEmailModal from './VerifyEmail'
|
|||||||
|
|
||||||
export function ModalsContainer() {
|
export function ModalsContainer() {
|
||||||
const {isModalActive, activeModals} = useModals()
|
const {isModalActive, activeModals} = useModals()
|
||||||
useWebBodyScrollLock(isModalActive)
|
|
||||||
|
|
||||||
if (!isModalActive) {
|
if (!isModalActive) {
|
||||||
return null
|
return null
|
||||||
@@ -30,6 +29,7 @@ export function ModalsContainer() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<RemoveScrollBar />
|
||||||
{activeModals.map((modal, i) => (
|
{activeModals.map((modal, i) => (
|
||||||
<Modal key={`modal-${i}`} modal={modal} />
|
<Modal key={`modal-${i}`} modal={modal} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {useLingui} from '@lingui/react'
|
|||||||
|
|
||||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||||
import {usePalette} from '#/lib/hooks/usePalette'
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {s} from '#/lib/styles'
|
import {s} from '#/lib/styles'
|
||||||
import {logger} from '#/logger'
|
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 {List, ListRef} from '#/view/com/util/List'
|
||||||
import {NotificationFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
import {NotificationFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||||
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
|
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
|
||||||
import {FeedItem} from './FeedItem'
|
import {FeedItem} from './FeedItem'
|
||||||
|
|
||||||
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
||||||
@@ -46,7 +44,6 @@ export function Feed({
|
|||||||
|
|
||||||
const [isPTRing, setIsPTRing] = React.useState(false)
|
const [isPTRing, setIsPTRing] = React.useState(false)
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {isTabletOrMobile} = useWebMediaQueries()
|
|
||||||
|
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const moderationOpts = useModerationOpts()
|
const moderationOpts = useModerationOpts()
|
||||||
@@ -133,11 +130,7 @@ export function Feed({
|
|||||||
)
|
)
|
||||||
} else if (item === LOADING_ITEM) {
|
} else if (item === LOADING_ITEM) {
|
||||||
return (
|
return (
|
||||||
<View
|
<View style={[pal.border]}>
|
||||||
style={[
|
|
||||||
pal.border,
|
|
||||||
!isTabletOrMobile && {borderTopWidth: StyleSheet.hairlineWidth},
|
|
||||||
]}>
|
|
||||||
<NotificationFeedLoadingPlaceholder />
|
<NotificationFeedLoadingPlaceholder />
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
@@ -146,11 +139,11 @@ export function Feed({
|
|||||||
<FeedItem
|
<FeedItem
|
||||||
item={item}
|
item={item}
|
||||||
moderationOpts={moderationOpts!}
|
moderationOpts={moderationOpts!}
|
||||||
hideTopBorder={index === 0 && isTabletOrMobile}
|
hideTopBorder={index === 0}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[moderationOpts, isTabletOrMobile, _, onPressRetryLoadMore, pal.border],
|
[moderationOpts, _, onPressRetryLoadMore, pal.border],
|
||||||
)
|
)
|
||||||
|
|
||||||
const FeedFooter = React.useCallback(
|
const FeedFooter = React.useCallback(
|
||||||
@@ -168,12 +161,10 @@ export function Feed({
|
|||||||
return (
|
return (
|
||||||
<View style={s.hContentRegion}>
|
<View style={s.hContentRegion}>
|
||||||
{error && (
|
{error && (
|
||||||
<CenteredView>
|
<ErrorMessage
|
||||||
<ErrorMessage
|
message={cleanError(error)}
|
||||||
message={cleanError(error)}
|
onPressTryAgain={onPressTryAgain}
|
||||||
onPressTryAgain={onPressTryAgain}
|
/>
|
||||||
/>
|
|
||||||
</CenteredView>
|
|
||||||
)}
|
)}
|
||||||
<List
|
<List
|
||||||
testID="notifsFeed"
|
testID="notifsFeed"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import * as React from 'react'
|
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 {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 {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 {ListMethods} from '../util/List'
|
||||||
import {TabBar} from './TabBar'
|
import {TabBar} from './TabBar'
|
||||||
|
|
||||||
@@ -121,30 +121,19 @@ let PagerTabBar = ({
|
|||||||
onSelect?: (index: number) => void
|
onSelect?: (index: number) => void
|
||||||
tabBarAnchor?: JSX.Element | null | undefined
|
tabBarAnchor?: JSX.Element | null | undefined
|
||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
const pal = usePalette('default')
|
|
||||||
const {isMobile} = useWebMediaQueries()
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View
|
<Layout.Center>{renderHeader?.()}</Layout.Center>
|
||||||
style={[
|
|
||||||
!isMobile && styles.headerContainerDesktop,
|
|
||||||
pal.border,
|
|
||||||
!isHeaderReady && styles.loadingHeader,
|
|
||||||
]}>
|
|
||||||
{renderHeader?.()}
|
|
||||||
</View>
|
|
||||||
{tabBarAnchor}
|
{tabBarAnchor}
|
||||||
<View
|
<Layout.Center
|
||||||
style={[
|
style={web([
|
||||||
styles.tabBarContainer,
|
a.sticky,
|
||||||
isMobile
|
a.z_10,
|
||||||
? styles.tabBarContainerMobile
|
|
||||||
: styles.tabBarContainerDesktop,
|
|
||||||
pal.border,
|
|
||||||
{
|
{
|
||||||
|
top: 0,
|
||||||
display: isHeaderReady ? undefined : 'none',
|
display: isHeaderReady ? undefined : 'none',
|
||||||
},
|
},
|
||||||
]}>
|
])}>
|
||||||
<TabBar
|
<TabBar
|
||||||
testID={testID}
|
testID={testID}
|
||||||
items={items}
|
items={items}
|
||||||
@@ -154,7 +143,7 @@ let PagerTabBar = ({
|
|||||||
dragProgress={undefined as any /* native-only */}
|
dragProgress={undefined as any /* native-only */}
|
||||||
dragState={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[] {
|
function toArray<T>(v: T | T[]): T[] {
|
||||||
if (Array.isArray(v)) {
|
if (Array.isArray(v)) {
|
||||||
return v
|
return v
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {useLingui} from '@lingui/react'
|
|||||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {isWeb} from '#/platform/detection'
|
|
||||||
import {useLikedByQuery} from '#/state/queries/post-liked-by'
|
import {useLikedByQuery} from '#/state/queries/post-liked-by'
|
||||||
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
|
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
|
||||||
import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
|
import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
|
||||||
@@ -18,7 +17,7 @@ function renderItem({item, index}: {item: GetLikes.Like; index: number}) {
|
|||||||
<ProfileCardWithFollowBtn
|
<ProfileCardWithFollowBtn
|
||||||
key={item.actor.did}
|
key={item.actor.did}
|
||||||
profile={item.actor}
|
profile={item.actor}
|
||||||
noBorder={index === 0 && !isWeb}
|
noBorder={index === 0}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -88,6 +87,7 @@ export function PostLikedBy({uri}: {uri: string}) {
|
|||||||
)}
|
)}
|
||||||
errorMessage={cleanError(resolveError || error)}
|
errorMessage={cleanError(resolveError || error)}
|
||||||
sideBorders={false}
|
sideBorders={false}
|
||||||
|
topBorder={false}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,6 @@ export function PostLikedBy({uri}: {uri: string}) {
|
|||||||
onRetry={fetchNextPage}
|
onRetry={fetchNextPage}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
// @ts-ignore our .web version only -prf
|
|
||||||
desktopFixedHeight
|
desktopFixedHeight
|
||||||
initialNumToRender={initialNumToRender}
|
initialNumToRender={initialNumToRender}
|
||||||
windowSize={11}
|
windowSize={11}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
|||||||
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
|
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {isWeb} from '#/platform/detection'
|
|
||||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||||
import {usePostQuotesQuery} from '#/state/queries/post-quotes'
|
import {usePostQuotesQuery} from '#/state/queries/post-quotes'
|
||||||
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
|
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
|
||||||
@@ -30,7 +29,7 @@ function renderItem({
|
|||||||
}
|
}
|
||||||
index: number
|
index: number
|
||||||
}) {
|
}) {
|
||||||
return <Post post={item.post} hideTopBorder={index === 0 && !isWeb} />
|
return <Post post={item.post} hideTopBorder={index === 0} />
|
||||||
}
|
}
|
||||||
|
|
||||||
function keyExtractor(item: {
|
function keyExtractor(item: {
|
||||||
|
|||||||
@@ -12,8 +12,20 @@ import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
|
|||||||
import {List} from '#/view/com/util/List'
|
import {List} from '#/view/com/util/List'
|
||||||
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||||
|
|
||||||
function renderItem({item}: {item: ActorDefs.ProfileViewBasic}) {
|
function renderItem({
|
||||||
return <ProfileCardWithFollowBtn key={item.did} profile={item} />
|
item,
|
||||||
|
index,
|
||||||
|
}: {
|
||||||
|
item: ActorDefs.ProfileViewBasic
|
||||||
|
index: number
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<ProfileCardWithFollowBtn
|
||||||
|
key={item.did}
|
||||||
|
profile={item}
|
||||||
|
noBorder={index === 0}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function keyExtractor(item: ActorDefs.ProfileViewBasic) {
|
function keyExtractor(item: ActorDefs.ProfileViewBasic) {
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import {usePreferencesQuery} from '#/state/queries/preferences'
|
|||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {useComposerControls} from '#/state/shell'
|
import {useComposerControls} from '#/state/shell'
|
||||||
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
|
import {useMergedThreadgateHiddenReplies} from '#/state/threadgate-hidden-replies'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
@@ -484,7 +483,7 @@ export function PostThread({uri}: {uri: string | undefined}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CenteredView style={[a.flex_1]} sideBorders={true}>
|
<>
|
||||||
{showHeader && (
|
{showHeader && (
|
||||||
<ViewHeader
|
<ViewHeader
|
||||||
title={_(msg({message: `Post`, context: 'description'}))}
|
title={_(msg({message: `Post`, context: 'description'}))}
|
||||||
@@ -531,7 +530,7 @@ export function PostThread({uri}: {uri: string | undefined}) {
|
|||||||
{isMobile && canReply && hasSession && (
|
{isMobile && canReply && hasSession && (
|
||||||
<MobileComposePrompt onPressReply={onPressReply} />
|
<MobileComposePrompt onPressReply={onPressReply} />
|
||||||
)}
|
)}
|
||||||
</CenteredView>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {useLingui} from '@lingui/react'
|
|||||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {isWeb} from '#/platform/detection'
|
|
||||||
import {useProfileFollowersQuery} from '#/state/queries/profile-followers'
|
import {useProfileFollowersQuery} from '#/state/queries/profile-followers'
|
||||||
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
@@ -25,7 +24,7 @@ function renderItem({
|
|||||||
<ProfileCardWithFollowBtn
|
<ProfileCardWithFollowBtn
|
||||||
key={item.did}
|
key={item.did}
|
||||||
profile={item}
|
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 {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {isWeb} from '#/platform/detection'
|
|
||||||
import {useProfileFollowsQuery} from '#/state/queries/profile-follows'
|
import {useProfileFollowsQuery} from '#/state/queries/profile-follows'
|
||||||
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
@@ -25,7 +24,7 @@ function renderItem({
|
|||||||
<ProfileCardWithFollowBtn
|
<ProfileCardWithFollowBtn
|
||||||
key={item.did}
|
key={item.did}
|
||||||
profile={item}
|
profile={item}
|
||||||
noBorder={index === 0 && !isWeb}
|
noBorder={index === 0}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,24 @@
|
|||||||
import React from 'react'
|
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 {MeasuredDimensions, runOnJS, runOnUI} from 'react-native-reanimated'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
|
|
||||||
import {BACK_HITSLOP} from '#/lib/constants'
|
|
||||||
import {measureHandle, useHandleRef} from '#/lib/hooks/useHandleRef'
|
import {measureHandle, useHandleRef} from '#/lib/hooks/useHandleRef'
|
||||||
import {usePalette} from '#/lib/hooks/usePalette'
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
import {makeProfileLink} from '#/lib/routes/links'
|
import {makeProfileLink} from '#/lib/routes/links'
|
||||||
import {NavigationProp} from '#/lib/routes/types'
|
import {NavigationProp} from '#/lib/routes/types'
|
||||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||||
import {isNative} from '#/platform/detection'
|
|
||||||
import {emitSoftReset} from '#/state/events'
|
import {emitSoftReset} from '#/state/events'
|
||||||
import {useLightboxControls} from '#/state/lightbox'
|
import {useLightboxControls} from '#/state/lightbox'
|
||||||
import {useSetDrawerOpen} from '#/state/shell'
|
import {TextLink} from '#/view/com/util/Link'
|
||||||
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
|
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 {StarterPack} from '#/components/icons/StarterPack'
|
||||||
import {TextLink} from '../util/Link'
|
import * as Layout from '#/components/Layout'
|
||||||
import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
|
|
||||||
import {Text} from '../util/text/Text'
|
|
||||||
import {UserAvatar, UserAvatarType} from '../util/UserAvatar'
|
|
||||||
import {CenteredView} from '../util/Views'
|
|
||||||
|
|
||||||
export function ProfileSubpageHeader({
|
export function ProfileSubpageHeader({
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -48,7 +43,6 @@ export function ProfileSubpageHeader({
|
|||||||
| undefined
|
| undefined
|
||||||
avatarType: UserAvatarType | 'starter-pack'
|
avatarType: UserAvatarType | 'starter-pack'
|
||||||
}>) {
|
}>) {
|
||||||
const setDrawerOpen = useSetDrawerOpen()
|
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {isMobile} = useWebMediaQueries()
|
const {isMobile} = useWebMediaQueries()
|
||||||
@@ -57,18 +51,6 @@ export function ProfileSubpageHeader({
|
|||||||
const canGoBack = navigation.canGoBack()
|
const canGoBack = navigation.canGoBack()
|
||||||
const aviRef = useHandleRef()
|
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(
|
const _openLightbox = React.useCallback(
|
||||||
(uri: string, thumbRect: MeasuredDimensions | null) => {
|
(uri: string, thumbRect: MeasuredDimensions | null) => {
|
||||||
openLightbox({
|
openLightbox({
|
||||||
@@ -106,42 +88,17 @@ export function ProfileSubpageHeader({
|
|||||||
}, [_openLightbox, avatar, aviRef])
|
}, [_openLightbox, avatar, aviRef])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CenteredView style={pal.view}>
|
<>
|
||||||
{isMobile && (
|
<Layout.Header.Outer>
|
||||||
<View
|
{canGoBack ? (
|
||||||
style={[
|
<Layout.Header.BackButton />
|
||||||
{
|
) : (
|
||||||
flexDirection: 'row',
|
<Layout.Header.MenuButton />
|
||||||
alignItems: 'center',
|
)}
|
||||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
<Layout.Header.Content />
|
||||||
paddingTop: isNative ? 0 : 8,
|
{children}
|
||||||
paddingBottom: 8,
|
</Layout.Header.Outer>
|
||||||
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>
|
|
||||||
)}
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@@ -206,31 +163,7 @@ export function ProfileSubpageHeader({
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
{!isMobile && (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}>
|
|
||||||
{children}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</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 {batchedUpdates} from '#/lib/batchedUpdates'
|
||||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||||
import {usePalette} from '#/lib/hooks/usePalette'
|
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
|
||||||
import {useScrollHandlers} from '#/lib/ScrollContext'
|
import {useScrollHandlers} from '#/lib/ScrollContext'
|
||||||
import {addStyle} from '#/lib/styles'
|
import {addStyle} from '#/lib/styles'
|
||||||
|
import * as Layout from '#/components/Layout'
|
||||||
|
|
||||||
export type ListMethods = any // TODO: Better types.
|
export type ListMethods = any // TODO: Better types.
|
||||||
export type ListProps<ItemT> = Omit<
|
export type ListProps<ItemT> = Omit<
|
||||||
@@ -24,6 +23,9 @@ export type ListProps<ItemT> = Omit<
|
|||||||
desktopFixedHeight?: number | boolean
|
desktopFixedHeight?: number | boolean
|
||||||
// Web only prop to contain the scroll to the container rather than the window
|
// Web only prop to contain the scroll to the container rather than the window
|
||||||
disableFullWindowScroll?: boolean
|
disableFullWindowScroll?: boolean
|
||||||
|
/**
|
||||||
|
* @deprecated Should be using Layout components
|
||||||
|
*/
|
||||||
sideBorders?: boolean
|
sideBorders?: boolean
|
||||||
}
|
}
|
||||||
export type ListRef = React.MutableRefObject<any | null> // TODO: Better types.
|
export type ListRef = React.MutableRefObject<any | null> // TODO: Better types.
|
||||||
@@ -56,20 +58,11 @@ function ListImpl<ItemT>(
|
|||||||
renderItem,
|
renderItem,
|
||||||
extraData,
|
extraData,
|
||||||
style,
|
style,
|
||||||
sideBorders = true,
|
|
||||||
...props
|
...props
|
||||||
}: ListProps<ItemT>,
|
}: ListProps<ItemT>,
|
||||||
ref: React.Ref<ListMethods>,
|
ref: React.Ref<ListMethods>,
|
||||||
) {
|
) {
|
||||||
const contextScrollHandlers = useScrollHandlers()
|
const contextScrollHandlers = useScrollHandlers()
|
||||||
const pal = usePalette('default')
|
|
||||||
const {isMobile} = useWebMediaQueries()
|
|
||||||
if (!isMobile) {
|
|
||||||
contentContainerStyle = addStyle(
|
|
||||||
contentContainerStyle,
|
|
||||||
styles.containerScroll,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const isEmpty = !data || data.length === 0
|
const isEmpty = !data || data.length === 0
|
||||||
|
|
||||||
@@ -326,53 +319,53 @@ function ListImpl<ItemT>(
|
|||||||
styles.parentTreeVisibilityDetector
|
styles.parentTreeVisibilityDetector
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<View
|
<Layout.Center>
|
||||||
ref={containerRef}
|
<View
|
||||||
style={[
|
ref={containerRef}
|
||||||
!isMobile && sideBorders && styles.sideBorders,
|
style={[
|
||||||
contentContainerStyle,
|
contentContainerStyle,
|
||||||
desktopFixedHeight ? styles.minHeightViewport : null,
|
desktopFixedHeight ? styles.minHeightViewport : null,
|
||||||
pal.border,
|
]}>
|
||||||
]}>
|
<Visibility
|
||||||
<Visibility
|
|
||||||
root={disableFullWindowScroll ? nativeRef : null}
|
|
||||||
onVisibleChange={handleAboveTheFoldVisibleChange}
|
|
||||||
style={[styles.aboveTheFoldDetector, {height: headerOffset}]}
|
|
||||||
/>
|
|
||||||
{onStartReached && !isEmpty && (
|
|
||||||
<EdgeVisibility
|
|
||||||
root={disableFullWindowScroll ? nativeRef : null}
|
root={disableFullWindowScroll ? nativeRef : null}
|
||||||
onVisibleChange={onHeadVisibilityChange}
|
onVisibleChange={handleAboveTheFoldVisibleChange}
|
||||||
topMargin={(onStartReachedThreshold ?? 0) * 100 + '%'}
|
style={[styles.aboveTheFoldDetector, {height: headerOffset}]}
|
||||||
containerRef={containerRef}
|
|
||||||
/>
|
/>
|
||||||
)}
|
{onStartReached && !isEmpty && (
|
||||||
{headerComponent}
|
<EdgeVisibility
|
||||||
{isEmpty
|
root={disableFullWindowScroll ? nativeRef : null}
|
||||||
? emptyComponent
|
onVisibleChange={onHeadVisibilityChange}
|
||||||
: (data as Array<ItemT>)?.map((item, index) => {
|
topMargin={(onStartReachedThreshold ?? 0) * 100 + '%'}
|
||||||
const key = keyExtractor!(item, index)
|
containerRef={containerRef}
|
||||||
return (
|
/>
|
||||||
<Row<ItemT>
|
)}
|
||||||
key={key}
|
{headerComponent}
|
||||||
item={item}
|
{isEmpty
|
||||||
index={index}
|
? emptyComponent
|
||||||
renderItem={renderItem}
|
: (data as Array<ItemT>)?.map((item, index) => {
|
||||||
extraData={extraData}
|
const key = keyExtractor!(item, index)
|
||||||
onItemSeen={onItemSeen}
|
return (
|
||||||
/>
|
<Row<ItemT>
|
||||||
)
|
key={key}
|
||||||
})}
|
item={item}
|
||||||
{onEndReached && !isEmpty && (
|
index={index}
|
||||||
<EdgeVisibility
|
renderItem={renderItem}
|
||||||
root={disableFullWindowScroll ? nativeRef : null}
|
extraData={extraData}
|
||||||
onVisibleChange={onTailVisibilityChange}
|
onItemSeen={onItemSeen}
|
||||||
bottomMargin={(onEndReachedThreshold ?? 0) * 100 + '%'}
|
/>
|
||||||
containerRef={containerRef}
|
)
|
||||||
/>
|
})}
|
||||||
)}
|
{onEndReached && !isEmpty && (
|
||||||
{footerComponent}
|
<EdgeVisibility
|
||||||
</View>
|
root={disableFullWindowScroll ? nativeRef : null}
|
||||||
|
onVisibleChange={onTailVisibilityChange}
|
||||||
|
bottomMargin={(onEndReachedThreshold ?? 0) * 100 + '%'}
|
||||||
|
containerRef={containerRef}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{footerComponent}
|
||||||
|
</View>
|
||||||
|
</Layout.Center>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -558,16 +551,6 @@ export const List = memo(React.forwardRef(ListImpl)) as <ItemT>(
|
|||||||
// https://stackoverflow.com/questions/7944460/detect-safari-browser
|
// https://stackoverflow.com/questions/7944460/detect-safari-browser
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
sideBorders: {
|
|
||||||
borderLeftWidth: 1,
|
|
||||||
borderRightWidth: 1,
|
|
||||||
},
|
|
||||||
containerScroll: {
|
|
||||||
width: '100%',
|
|
||||||
maxWidth: 600,
|
|
||||||
marginLeft: 'auto',
|
|
||||||
marginRight: 'auto',
|
|
||||||
},
|
|
||||||
minHeightViewport: {
|
minHeightViewport: {
|
||||||
// @ts-ignore web only
|
// @ts-ignore web only
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import {ActivityIndicator, View} from 'react-native'
|
import {ActivityIndicator, View} from 'react-native'
|
||||||
|
|
||||||
import {s} from '#/lib/styles'
|
import {s} from '#/lib/styles'
|
||||||
import {CenteredView} from './Views'
|
import * as Layout from '#/components/Layout'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use Layout compoenents directly
|
||||||
|
*/
|
||||||
export function LoadingScreen() {
|
export function LoadingScreen() {
|
||||||
return (
|
return (
|
||||||
<CenteredView>
|
<Layout.Content>
|
||||||
<View style={s.p20}>
|
<View style={s.p20}>
|
||||||
<ActivityIndicator size="large" />
|
<ActivityIndicator size="large" />
|
||||||
</View>
|
</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 {Header} from '#/components/Layout'
|
||||||
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}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy ViewHeader component. Use Layout.Header going forward.
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
export function ViewHeader({
|
export function ViewHeader({
|
||||||
title,
|
title,
|
||||||
subtitle,
|
|
||||||
canGoBack,
|
|
||||||
showBackButton = true,
|
|
||||||
hideOnScroll,
|
|
||||||
showOnDesktop,
|
|
||||||
showBorder,
|
|
||||||
renderButton,
|
renderButton,
|
||||||
}: {
|
}: {
|
||||||
title: string
|
title: string
|
||||||
subtitle?: string
|
subtitle?: string
|
||||||
canGoBack?: boolean
|
|
||||||
showBackButton?: boolean
|
|
||||||
hideOnScroll?: boolean
|
|
||||||
showOnDesktop?: boolean
|
showOnDesktop?: boolean
|
||||||
showBorder?: boolean
|
showBorder?: boolean
|
||||||
renderButton?: () => JSX.Element
|
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 (
|
return (
|
||||||
<CenteredView
|
<Header.Outer>
|
||||||
style={[
|
<Header.BackButton />
|
||||||
styles.header,
|
<Header.Content>
|
||||||
styles.desktopHeader,
|
<Header.TitleText>{title}</Header.TitleText>
|
||||||
pal.border,
|
</Header.Content>
|
||||||
{
|
<Header.Slot>{renderButton?.() ?? null}</Header.Slot>
|
||||||
borderBottomWidth: showBorder ? StyleSheet.hairlineWidth : 0,
|
</Header.Outer>
|
||||||
},
|
|
||||||
{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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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>>,
|
FlatListComponent<ItemT, FlatListPropsWithLayout<ItemT>>,
|
||||||
'CellRendererComponent'
|
'CellRendererComponent'
|
||||||
>
|
>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use `Layout` components
|
||||||
|
*/
|
||||||
export const ScrollView = Animated.ScrollView
|
export const ScrollView = Animated.ScrollView
|
||||||
export type ScrollView = typeof Animated.ScrollView
|
export type ScrollView = typeof Animated.ScrollView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use `Layout` components
|
||||||
|
*/
|
||||||
export const CenteredView = forwardRef<
|
export const CenteredView = forwardRef<
|
||||||
View,
|
View,
|
||||||
React.PropsWithChildren<
|
React.PropsWithChildren<
|
||||||
|
|||||||
@@ -31,10 +31,12 @@ interface AddedProps {
|
|||||||
desktopFixedHeight?: boolean | number
|
desktopFixedHeight?: boolean | number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use `Layout` components
|
||||||
|
*/
|
||||||
export const CenteredView = React.forwardRef(function CenteredView(
|
export const CenteredView = React.forwardRef(function CenteredView(
|
||||||
{
|
{
|
||||||
style,
|
style,
|
||||||
sideBorders,
|
|
||||||
topBorder,
|
topBorder,
|
||||||
...props
|
...props
|
||||||
}: React.PropsWithChildren<
|
}: React.PropsWithChildren<
|
||||||
@@ -47,13 +49,6 @@ export const CenteredView = React.forwardRef(function CenteredView(
|
|||||||
if (!isMobile) {
|
if (!isMobile) {
|
||||||
style = addStyle(style, styles.container)
|
style = addStyle(style, styles.container)
|
||||||
}
|
}
|
||||||
if (sideBorders && !isMobile) {
|
|
||||||
style = addStyle(style, {
|
|
||||||
borderLeftWidth: StyleSheet.hairlineWidth,
|
|
||||||
borderRightWidth: StyleSheet.hairlineWidth,
|
|
||||||
})
|
|
||||||
style = addStyle(style, pal.border)
|
|
||||||
}
|
|
||||||
if (topBorder) {
|
if (topBorder) {
|
||||||
style = addStyle(style, {
|
style = addStyle(style, {
|
||||||
borderTopWidth: 1,
|
borderTopWidth: 1,
|
||||||
@@ -75,7 +70,6 @@ export const FlatList_INTERNAL = React.forwardRef(function FlatListImpl<ItemT>(
|
|||||||
>,
|
>,
|
||||||
ref: React.Ref<FlatList<ItemT>>,
|
ref: React.Ref<FlatList<ItemT>>,
|
||||||
) {
|
) {
|
||||||
const pal = usePalette('default')
|
|
||||||
const {isMobile} = useWebMediaQueries()
|
const {isMobile} = useWebMediaQueries()
|
||||||
if (!isMobile) {
|
if (!isMobile) {
|
||||||
contentContainerStyle = addStyle(
|
contentContainerStyle = addStyle(
|
||||||
@@ -123,11 +117,7 @@ export const FlatList_INTERNAL = React.forwardRef(function FlatListImpl<ItemT>(
|
|||||||
return (
|
return (
|
||||||
<Animated.FlatList
|
<Animated.FlatList
|
||||||
ref={ref}
|
ref={ref}
|
||||||
contentContainerStyle={[
|
contentContainerStyle={[styles.contentContainer, contentContainerStyle]}
|
||||||
styles.contentContainer,
|
|
||||||
contentContainerStyle,
|
|
||||||
pal.border,
|
|
||||||
]}
|
|
||||||
style={style}
|
style={style}
|
||||||
contentOffset={contentOffset}
|
contentOffset={contentOffset}
|
||||||
{...props}
|
{...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(
|
export const ScrollView = React.forwardRef(function ScrollViewImpl(
|
||||||
{contentContainerStyle, ...props}: React.PropsWithChildren<ScrollViewProps>,
|
{contentContainerStyle, ...props}: React.PropsWithChildren<ScrollViewProps>,
|
||||||
ref: React.Ref<Animated.ScrollView>,
|
ref: React.Ref<Animated.ScrollView>,
|
||||||
) {
|
) {
|
||||||
const pal = usePalette('default')
|
|
||||||
|
|
||||||
const {isMobile} = useWebMediaQueries()
|
const {isMobile} = useWebMediaQueries()
|
||||||
if (!isMobile) {
|
if (!isMobile) {
|
||||||
contentContainerStyle = addStyle(
|
contentContainerStyle = addStyle(
|
||||||
@@ -150,11 +141,7 @@ export const ScrollView = React.forwardRef(function ScrollViewImpl(
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Animated.ScrollView
|
<Animated.ScrollView
|
||||||
contentContainerStyle={[
|
contentContainerStyle={[styles.contentContainer, contentContainerStyle]}
|
||||||
styles.contentContainer,
|
|
||||||
contentContainerStyle,
|
|
||||||
pal.border,
|
|
||||||
]}
|
|
||||||
// @ts-ignore something is wrong with the reanimated types -prf
|
// @ts-ignore something is wrong with the reanimated types -prf
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -164,8 +151,6 @@ export const ScrollView = React.forwardRef(function ScrollViewImpl(
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
contentContainer: {
|
contentContainer: {
|
||||||
borderLeftWidth: StyleSheet.hairlineWidth,
|
|
||||||
borderRightWidth: StyleSheet.hairlineWidth,
|
|
||||||
// @ts-ignore web only
|
// @ts-ignore web only
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ export function ErrorScreen({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{showHeader && isMobile && <ViewHeader title="Error" showBorder />}
|
{showHeader && isMobile && (
|
||||||
|
<ViewHeader title={_(msg`Error`)} showBorder />
|
||||||
|
)}
|
||||||
<CenteredView testID={testID} style={[styles.outer, pal.view]}>
|
<CenteredView testID={testID} style={[styles.outer, pal.view]}>
|
||||||
<View style={styles.errorIconContainer}>
|
<View style={styles.errorIconContainer}>
|
||||||
<View
|
<View
|
||||||
|
|||||||
+45
-83
@@ -24,14 +24,13 @@ import {useSetMinimalShellMode} from '#/state/shell'
|
|||||||
import {useComposerControls} from '#/state/shell/composer'
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
||||||
import {FAB} from '#/view/com/util/fab/FAB'
|
import {FAB} from '#/view/com/util/fab/FAB'
|
||||||
import {TextLink} from '#/view/com/util/Link'
|
|
||||||
import {List, ListMethods} from '#/view/com/util/List'
|
import {List, ListMethods} from '#/view/com/util/List'
|
||||||
import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
|
||||||
import {Text} from '#/view/com/util/text/Text'
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
|
||||||
import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
|
import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
|
||||||
import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
|
import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {ButtonIcon} from '#/components/Button'
|
||||||
import {Divider} from '#/components/Divider'
|
import {Divider} from '#/components/Divider'
|
||||||
import * as FeedCard from '#/components/FeedCard'
|
import * as FeedCard from '#/components/FeedCard'
|
||||||
import {SearchInput} from '#/components/forms/SearchInput'
|
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 {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline'
|
||||||
import {ListMagnifyingGlass_Stroke2_Corner0_Rounded} from '#/components/icons/ListMagnifyingGlass'
|
import {ListMagnifyingGlass_Stroke2_Corner0_Rounded} from '#/components/icons/ListMagnifyingGlass'
|
||||||
import {ListSparkle_Stroke2_Corner0_Rounded} from '#/components/icons/ListSparkle'
|
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 * as Layout from '#/components/Layout'
|
||||||
|
import {Link} from '#/components/Link'
|
||||||
import * as ListCard from '#/components/ListCard'
|
import * as ListCard from '#/components/ListCard'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Feeds'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Feeds'>
|
||||||
@@ -102,7 +103,7 @@ type FlatlistSlice =
|
|||||||
export function FeedsScreen(_props: Props) {
|
export function FeedsScreen(_props: Props) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {openComposer} = useComposerControls()
|
const {openComposer} = useComposerControls()
|
||||||
const {isMobile, isTabletOrDesktop} = useWebMediaQueries()
|
const {isMobile} = useWebMediaQueries()
|
||||||
const [query, setQuery] = React.useState('')
|
const [query, setQuery] = React.useState('')
|
||||||
const [isPTR, setIsPTR] = React.useState(false)
|
const [isPTR, setIsPTR] = React.useState(false)
|
||||||
const {
|
const {
|
||||||
@@ -374,22 +375,6 @@ export function FeedsScreen(_props: Props) {
|
|||||||
isUserSearching,
|
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(
|
const searchBarIndex = items.findIndex(
|
||||||
item => item.type === 'popularFeedsHeader',
|
item => item.type === 'popularFeedsHeader',
|
||||||
)
|
)
|
||||||
@@ -430,36 +415,7 @@ export function FeedsScreen(_props: Props) {
|
|||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
} else if (item.type === 'savedFeedsHeader') {
|
} else if (item.type === 'savedFeedsHeader') {
|
||||||
return (
|
return <FeedsSavedHeader />
|
||||||
<>
|
|
||||||
{!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 />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
} else if (item.type === 'savedFeedNoResults') {
|
} else if (item.type === 'savedFeedNoResults') {
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
@@ -530,13 +486,8 @@ export function FeedsScreen(_props: Props) {
|
|||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
isMobile,
|
|
||||||
pal.view,
|
|
||||||
pal.border,
|
pal.border,
|
||||||
pal.text,
|
|
||||||
pal.textLight,
|
pal.textLight,
|
||||||
pal.link,
|
|
||||||
_,
|
|
||||||
query,
|
query,
|
||||||
onChangeQuery,
|
onChangeQuery,
|
||||||
onPressCancelSearch,
|
onPressCancelSearch,
|
||||||
@@ -547,31 +498,45 @@ export function FeedsScreen(_props: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen testID="FeedsScreen">
|
<Layout.Screen testID="FeedsScreen">
|
||||||
{isMobile && (
|
<Layout.Center>
|
||||||
<ViewHeader
|
<Layout.Header.Outer>
|
||||||
title={_(msg`Feeds`)}
|
<Layout.Header.BackButton />
|
||||||
renderButton={renderHeaderBtn}
|
<Layout.Header.Content>
|
||||||
showBorder
|
<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
|
<List
|
||||||
ref={listRef}
|
ref={listRef}
|
||||||
style={[!isTabletOrDesktop && s.flex1, styles.list]}
|
data={items}
|
||||||
data={items}
|
keyExtractor={item => item.key}
|
||||||
keyExtractor={item => item.key}
|
contentContainerStyle={styles.contentContainer}
|
||||||
contentContainerStyle={styles.contentContainer}
|
renderItem={renderItem}
|
||||||
renderItem={renderItem}
|
refreshing={isPTR}
|
||||||
refreshing={isPTR}
|
onRefresh={isUserSearching ? undefined : onPullToRefresh}
|
||||||
onRefresh={isUserSearching ? undefined : onPullToRefresh}
|
initialNumToRender={10}
|
||||||
initialNumToRender={10}
|
onEndReached={onEndReached}
|
||||||
onEndReached={onEndReached}
|
desktopFixedHeight
|
||||||
// @ts-ignore our .web version only -prf
|
keyboardShouldPersistTaps="handled"
|
||||||
desktopFixedHeight
|
keyboardDismissMode="on-drag"
|
||||||
scrollIndicatorInsets={{right: 1}}
|
sideBorders={false}
|
||||||
keyboardShouldPersistTaps="handled"
|
/>
|
||||||
keyboardDismissMode="on-drag"
|
</Layout.Center>
|
||||||
/>
|
|
||||||
|
|
||||||
{hasSession && (
|
{hasSession && (
|
||||||
<FAB
|
<FAB
|
||||||
@@ -728,7 +693,7 @@ function FeedsSavedHeader() {
|
|||||||
}>
|
}>
|
||||||
<IconCircle icon={ListSparkle_Stroke2_Corner0_Rounded} size="lg" />
|
<IconCircle icon={ListSparkle_Stroke2_Corner0_Rounded} size="lg" />
|
||||||
<View style={[a.flex_1, a.gap_xs]}>
|
<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>
|
<Trans>My Feeds</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[t.atoms.text_contrast_high]}>
|
<Text style={[t.atoms.text_contrast_high]}>
|
||||||
@@ -754,7 +719,7 @@ function FeedsAboutHeader() {
|
|||||||
size="lg"
|
size="lg"
|
||||||
/>
|
/>
|
||||||
<View style={[a.flex_1, a.gap_sm]}>
|
<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>
|
<Trans>Discover New Feeds</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[t.atoms.text_contrast_high]}>
|
<Text style={[t.atoms.text_contrast_high]}>
|
||||||
@@ -769,9 +734,6 @@ function FeedsAboutHeader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
list: {
|
|
||||||
height: '100%',
|
|
||||||
},
|
|
||||||
contentContainer: {
|
contentContainer: {
|
||||||
paddingBottom: 100,
|
paddingBottom: 100,
|
||||||
},
|
},
|
||||||
|
|||||||
+26
-46
@@ -1,33 +1,26 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyleSheet, View} from 'react-native'
|
|
||||||
import {AtUri} from '@atproto/api'
|
import {AtUri} from '@atproto/api'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useFocusEffect, useNavigation} from '@react-navigation/native'
|
import {useFocusEffect, useNavigation} from '@react-navigation/native'
|
||||||
|
|
||||||
import {useEmail} from '#/lib/hooks/useEmail'
|
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 {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||||
import {NavigationProp} from '#/lib/routes/types'
|
import {NavigationProp} from '#/lib/routes/types'
|
||||||
import {s} from '#/lib/styles'
|
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {useSetMinimalShellMode} from '#/state/shell'
|
import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
import {MyLists} from '#/view/com/lists/MyLists'
|
import {MyLists} from '#/view/com/lists/MyLists'
|
||||||
import {Button} from '#/view/com/util/forms/Button'
|
import {atoms as a} from '#/alf'
|
||||||
import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {Text} from '#/view/com/util/text/Text'
|
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
|
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
|
||||||
|
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Lists'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Lists'>
|
||||||
export function ListsScreen({}: Props) {
|
export function ListsScreen({}: Props) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const pal = usePalette('default')
|
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const {isMobile} = useWebMediaQueries()
|
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
const {openModal} = useModalControls()
|
const {openModal} = useModalControls()
|
||||||
const {needsEmailVerification} = useEmail()
|
const {needsEmailVerification} = useEmail()
|
||||||
@@ -62,43 +55,30 @@ export function ListsScreen({}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen testID="listsScreen">
|
<Layout.Screen testID="listsScreen">
|
||||||
<SimpleViewHeader
|
<Layout.Header.Outer>
|
||||||
showBackButton={isMobile}
|
<Layout.Header.BackButton />
|
||||||
style={[
|
<Layout.Header.Content align="left">
|
||||||
pal.border,
|
<Layout.Header.TitleText>
|
||||||
isMobile
|
<Trans>Lists</Trans>
|
||||||
? {borderBottomWidth: StyleSheet.hairlineWidth}
|
</Layout.Header.TitleText>
|
||||||
: {
|
<Layout.Header.SubtitleText>
|
||||||
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}>
|
|
||||||
<Trans>Public, shareable lists which can drive feeds.</Trans>
|
<Trans>Public, shareable lists which can drive feeds.</Trans>
|
||||||
</Text>
|
</Layout.Header.SubtitleText>
|
||||||
</View>
|
</Layout.Header.Content>
|
||||||
<View style={[{marginLeft: 18}, isMobile && {marginLeft: 12}]}>
|
<Button
|
||||||
<Button
|
label={_(msg`New list`)}
|
||||||
testID="newUserListBtn"
|
testID="newUserListBtn"
|
||||||
type="default"
|
color="secondary"
|
||||||
onPress={onPressNewList}
|
variant="solid"
|
||||||
style={{
|
size="small"
|
||||||
flexDirection: 'row',
|
onPress={onPressNewList}>
|
||||||
alignItems: 'center',
|
<ButtonIcon icon={PlusIcon} />
|
||||||
gap: 8,
|
<ButtonText>
|
||||||
}}>
|
<Trans context="action">New</Trans>
|
||||||
<FontAwesomeIcon icon="plus" color={pal.colors.text} />
|
</ButtonText>
|
||||||
<Text type="button" style={pal.text}>
|
</Button>
|
||||||
<Trans context="action">New</Trans>
|
</Layout.Header.Outer>
|
||||||
</Text>
|
<MyLists filter="curate" style={a.flex_grow} />
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
</SimpleViewHeader>
|
|
||||||
<MyLists filter="curate" style={s.flexGrow1} />
|
|
||||||
<VerifyEmailDialog
|
<VerifyEmailDialog
|
||||||
reasonText={_(
|
reasonText={_(
|
||||||
msg`Before creating a list, you must first verify your email.`,
|
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 {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
||||||
import {Text} from '#/view/com/util/text/Text'
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<
|
type Props = NativeStackScreenProps<
|
||||||
@@ -97,14 +96,7 @@ export function ModerationBlockedAccounts({}: Props) {
|
|||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<Layout.Screen testID="blockedAccountsScreen">
|
<Layout.Screen testID="blockedAccountsScreen">
|
||||||
<CenteredView
|
<Layout.Center>
|
||||||
style={[
|
|
||||||
styles.container,
|
|
||||||
isTabletOrDesktop && styles.containerDesktop,
|
|
||||||
pal.view,
|
|
||||||
pal.border,
|
|
||||||
]}
|
|
||||||
testID="blockedAccountsScreen">
|
|
||||||
<ViewHeader title={_(msg`Blocked Accounts`)} showOnDesktop />
|
<ViewHeader title={_(msg`Blocked Accounts`)} showOnDesktop />
|
||||||
<Text
|
<Text
|
||||||
type="sm"
|
type="sm"
|
||||||
@@ -112,6 +104,9 @@ export function ModerationBlockedAccounts({}: Props) {
|
|||||||
styles.description,
|
styles.description,
|
||||||
pal.text,
|
pal.text,
|
||||||
isTabletOrDesktop && styles.descriptionDesktop,
|
isTabletOrDesktop && styles.descriptionDesktop,
|
||||||
|
{
|
||||||
|
marginTop: 20,
|
||||||
|
},
|
||||||
]}>
|
]}>
|
||||||
<Trans>
|
<Trans>
|
||||||
Blocked accounts cannot reply in your threads, mention you, or
|
Blocked accounts cannot reply in your threads, mention you, or
|
||||||
@@ -120,7 +115,7 @@ export function ModerationBlockedAccounts({}: Props) {
|
|||||||
</Trans>
|
</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
{isEmpty ? (
|
{isEmpty ? (
|
||||||
<View style={[pal.border, !isTabletOrDesktop && styles.flex1]}>
|
<View style={[pal.border]}>
|
||||||
{isError ? (
|
{isError ? (
|
||||||
<ErrorScreen
|
<ErrorScreen
|
||||||
title="Oops!"
|
title="Oops!"
|
||||||
@@ -166,21 +161,12 @@ export function ModerationBlockedAccounts({}: Props) {
|
|||||||
desktopFixedHeight
|
desktopFixedHeight
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</CenteredView>
|
</Layout.Center>
|
||||||
</Layout.Screen>
|
</Layout.Screen>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
paddingBottom: 100,
|
|
||||||
},
|
|
||||||
containerDesktop: {
|
|
||||||
borderLeftWidth: 1,
|
|
||||||
borderRightWidth: 1,
|
|
||||||
paddingBottom: 0,
|
|
||||||
},
|
|
||||||
title: {
|
title: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
marginTop: 12,
|
marginTop: 12,
|
||||||
|
|||||||
@@ -1,33 +1,26 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {View} from 'react-native'
|
|
||||||
import {AtUri} from '@atproto/api'
|
import {AtUri} from '@atproto/api'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useFocusEffect, useNavigation} from '@react-navigation/native'
|
import {useFocusEffect, useNavigation} from '@react-navigation/native'
|
||||||
|
|
||||||
import {useEmail} from '#/lib/hooks/useEmail'
|
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 {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||||
import {NavigationProp} from '#/lib/routes/types'
|
import {NavigationProp} from '#/lib/routes/types'
|
||||||
import {s} from '#/lib/styles'
|
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {useSetMinimalShellMode} from '#/state/shell'
|
import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
import {MyLists} from '#/view/com/lists/MyLists'
|
import {MyLists} from '#/view/com/lists/MyLists'
|
||||||
import {Button} from '#/view/com/util/forms/Button'
|
import {atoms as a} from '#/alf'
|
||||||
import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {Text} from '#/view/com/util/text/Text'
|
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
|
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
|
||||||
|
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ModerationModlists'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ModerationModlists'>
|
||||||
export function ModerationModlistsScreen({}: Props) {
|
export function ModerationModlistsScreen({}: Props) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const pal = usePalette('default')
|
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const {isMobile} = useWebMediaQueries()
|
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
const {openModal} = useModalControls()
|
const {openModal} = useModalControls()
|
||||||
const {needsEmailVerification} = useEmail()
|
const {needsEmailVerification} = useEmail()
|
||||||
@@ -62,39 +55,32 @@ export function ModerationModlistsScreen({}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen testID="moderationModlistsScreen">
|
<Layout.Screen testID="moderationModlistsScreen">
|
||||||
<SimpleViewHeader
|
<Layout.Header.Outer>
|
||||||
showBackButton={isMobile}
|
<Layout.Header.BackButton />
|
||||||
style={
|
<Layout.Header.Content align="left">
|
||||||
!isMobile && [pal.border, {borderLeftWidth: 1, borderRightWidth: 1}]
|
<Layout.Header.TitleText>
|
||||||
}>
|
|
||||||
<View style={{flex: 1}}>
|
|
||||||
<Text type="title-lg" style={[pal.text, {fontWeight: '600'}]}>
|
|
||||||
<Trans>Moderation Lists</Trans>
|
<Trans>Moderation Lists</Trans>
|
||||||
</Text>
|
</Layout.Header.TitleText>
|
||||||
<Text style={pal.textLight}>
|
<Layout.Header.SubtitleText>
|
||||||
<Trans>
|
<Trans>
|
||||||
Public, shareable lists of users to mute or block in bulk.
|
Public, shareable lists of users to mute or block in bulk.
|
||||||
</Trans>
|
</Trans>
|
||||||
</Text>
|
</Layout.Header.SubtitleText>
|
||||||
</View>
|
</Layout.Header.Content>
|
||||||
<View style={[{marginLeft: 18}, isMobile && {marginLeft: 12}]}>
|
<Button
|
||||||
<Button
|
label={_(msg`New list`)}
|
||||||
testID="newModListBtn"
|
testID="newModListBtn"
|
||||||
type="default"
|
color="secondary"
|
||||||
onPress={onPressNewList}
|
variant="solid"
|
||||||
style={{
|
size="small"
|
||||||
flexDirection: 'row',
|
onPress={onPressNewList}>
|
||||||
alignItems: 'center',
|
<ButtonIcon icon={PlusIcon} />
|
||||||
gap: 8,
|
<ButtonText>
|
||||||
}}>
|
<Trans context="action">New</Trans>
|
||||||
<FontAwesomeIcon icon="plus" color={pal.colors.text} />
|
</ButtonText>
|
||||||
<Text type="button" style={pal.text}>
|
</Button>
|
||||||
<Trans>New</Trans>
|
</Layout.Header.Outer>
|
||||||
</Text>
|
<MyLists filter="mod" style={a.flex_grow} />
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
</SimpleViewHeader>
|
|
||||||
<MyLists filter="mod" style={s.flexGrow1} />
|
|
||||||
<VerifyEmailDialog
|
<VerifyEmailDialog
|
||||||
reasonText={_(
|
reasonText={_(
|
||||||
msg`Before creating a list, you must first verify your email.`,
|
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 {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
||||||
import {Text} from '#/view/com/util/text/Text'
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<
|
type Props = NativeStackScreenProps<
|
||||||
@@ -97,21 +96,17 @@ export function ModerationMutedAccounts({}: Props) {
|
|||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<Layout.Screen testID="mutedAccountsScreen">
|
<Layout.Screen testID="mutedAccountsScreen">
|
||||||
<CenteredView
|
<ViewHeader title={_(msg`Muted Accounts`)} showOnDesktop />
|
||||||
style={[
|
<Layout.Center>
|
||||||
styles.container,
|
|
||||||
isTabletOrDesktop && styles.containerDesktop,
|
|
||||||
pal.view,
|
|
||||||
pal.border,
|
|
||||||
]}
|
|
||||||
testID="mutedAccountsScreen">
|
|
||||||
<ViewHeader title={_(msg`Muted Accounts`)} showOnDesktop />
|
|
||||||
<Text
|
<Text
|
||||||
type="sm"
|
type="sm"
|
||||||
style={[
|
style={[
|
||||||
styles.description,
|
styles.description,
|
||||||
pal.text,
|
pal.text,
|
||||||
isTabletOrDesktop && styles.descriptionDesktop,
|
isTabletOrDesktop && styles.descriptionDesktop,
|
||||||
|
{
|
||||||
|
marginTop: 20,
|
||||||
|
},
|
||||||
]}>
|
]}>
|
||||||
<Trans>
|
<Trans>
|
||||||
Muted accounts have their posts removed from your feed and from your
|
Muted accounts have their posts removed from your feed and from your
|
||||||
@@ -119,7 +114,7 @@ export function ModerationMutedAccounts({}: Props) {
|
|||||||
</Trans>
|
</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
{isEmpty ? (
|
{isEmpty ? (
|
||||||
<View style={[pal.border, !isTabletOrDesktop && styles.flex1]}>
|
<View style={[pal.border]}>
|
||||||
{isError ? (
|
{isError ? (
|
||||||
<ErrorScreen
|
<ErrorScreen
|
||||||
title="Oops!"
|
title="Oops!"
|
||||||
@@ -165,21 +160,12 @@ export function ModerationMutedAccounts({}: Props) {
|
|||||||
desktopFixedHeight
|
desktopFixedHeight
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</CenteredView>
|
</Layout.Center>
|
||||||
</Layout.Screen>
|
</Layout.Screen>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
paddingBottom: 100,
|
|
||||||
},
|
|
||||||
containerDesktop: {
|
|
||||||
borderLeftWidth: 1,
|
|
||||||
borderRightWidth: 1,
|
|
||||||
paddingBottom: 0,
|
|
||||||
},
|
|
||||||
title: {
|
title: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
marginTop: 12,
|
marginTop: 12,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, {useCallback} from 'react'
|
import React from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
@@ -6,7 +6,6 @@ import {useFocusEffect, useIsFocused} from '@react-navigation/native'
|
|||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
|
||||||
import {ComposeIcon2} from '#/lib/icons'
|
import {ComposeIcon2} from '#/lib/icons'
|
||||||
import {
|
import {
|
||||||
NativeStackScreenProps,
|
NativeStackScreenProps,
|
||||||
@@ -14,7 +13,7 @@ import {
|
|||||||
} from '#/lib/routes/types'
|
} from '#/lib/routes/types'
|
||||||
import {s} from '#/lib/styles'
|
import {s} from '#/lib/styles'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {isNative} from '#/platform/detection'
|
import {isNative, isWeb} from '#/platform/detection'
|
||||||
import {emitSoftReset, listenSoftReset} from '#/state/events'
|
import {emitSoftReset, listenSoftReset} from '#/state/events'
|
||||||
import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed'
|
import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed'
|
||||||
import {
|
import {
|
||||||
@@ -29,28 +28,25 @@ import {FAB} from '#/view/com/util/fab/FAB'
|
|||||||
import {ListMethods} from '#/view/com/util/List'
|
import {ListMethods} from '#/view/com/util/List'
|
||||||
import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
|
import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
|
||||||
import {MainScrollProvider} from '#/view/com/util/MainScrollProvider'
|
import {MainScrollProvider} from '#/view/com/util/MainScrollProvider'
|
||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
import {Button, ButtonIcon} from '#/components/Button'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
|
||||||
import {Button} from '#/components/Button'
|
|
||||||
import {SettingsGear2_Stroke2_Corner0_Rounded as SettingsIcon} from '#/components/icons/SettingsGear2'
|
import {SettingsGear2_Stroke2_Corner0_Rounded as SettingsIcon} from '#/components/icons/SettingsGear2'
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
import {Link} from '#/components/Link'
|
import {Link} from '#/components/Link'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {Text} from '#/components/Typography'
|
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<
|
type Props = NativeStackScreenProps<
|
||||||
NotificationsTabNavigatorParams,
|
NotificationsTabNavigatorParams,
|
||||||
'Notifications'
|
'Notifications'
|
||||||
>
|
>
|
||||||
export function NotificationsScreen({route: {params}}: Props) {
|
export function NotificationsScreen({route: {params}}: Props) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {gtTablet} = useBreakpoints()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
|
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
|
||||||
const [isLoadingLatest, setIsLoadingLatest] = React.useState(false)
|
const [isLoadingLatest, setIsLoadingLatest] = React.useState(false)
|
||||||
const scrollElRef = React.useRef<ListMethods>(null)
|
const scrollElRef = React.useRef<ListMethods>(null)
|
||||||
const t = useTheme()
|
|
||||||
const {isDesktop} = useWebMediaQueries()
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const unreadNotifs = useUnreadNotifications()
|
const unreadNotifs = useUnreadNotifications()
|
||||||
const unreadApi = useUnreadNotificationsApi()
|
const unreadApi = useUnreadNotificationsApi()
|
||||||
@@ -110,121 +106,77 @@ export function NotificationsScreen({route: {params}}: Props) {
|
|||||||
return listenSoftReset(onPressLoadLatest)
|
return listenSoftReset(onPressLoadLatest)
|
||||||
}, [onPressLoadLatest, isScreenFocused])
|
}, [onPressLoadLatest, isScreenFocused])
|
||||||
|
|
||||||
const renderButton = useCallback(() => {
|
return (
|
||||||
return (
|
<Layout.Screen testID="notificationsScreen">
|
||||||
<Link
|
<Layout.Header.Outer>
|
||||||
to="/notifications/settings"
|
<Layout.Header.MenuButton />
|
||||||
label={_(msg`Notification settings`)}
|
<Layout.Header.Content>
|
||||||
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,
|
|
||||||
]}>
|
|
||||||
<Button
|
<Button
|
||||||
label={_(msg`Notifications`)}
|
label={_(msg`Notifications`)}
|
||||||
accessibilityHint={_(msg`Refresh notifications`)}
|
accessibilityHint={_(msg`Refresh notifications`)}
|
||||||
onPress={emitSoftReset}>
|
onPress={emitSoftReset}
|
||||||
{({hovered, pressed}) => (
|
style={[a.justify_start]}>
|
||||||
<Text
|
{({hovered}) => (
|
||||||
style={[
|
<Layout.Header.TitleText
|
||||||
a.text_2xl,
|
style={[a.w_full, hovered && a.underline]}>
|
||||||
a.font_bold,
|
|
||||||
(hovered || pressed) && a.underline,
|
|
||||||
]}>
|
|
||||||
<Trans>Notifications</Trans>
|
<Trans>Notifications</Trans>
|
||||||
{hasNew && (
|
{isWeb && gtTablet && hasNew && (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={[
|
||||||
left: 4,
|
a.rounded_full,
|
||||||
top: -8,
|
{
|
||||||
backgroundColor: t.palette.primary_500,
|
width: 8,
|
||||||
width: 8,
|
height: 8,
|
||||||
height: 8,
|
bottom: 3,
|
||||||
borderRadius: 4,
|
left: 6,
|
||||||
}}
|
backgroundColor: t.palette.primary_500,
|
||||||
|
},
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Layout.Header.TitleText>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
|
</Layout.Header.Content>
|
||||||
{isLoadingLatest ? <Loader size="md" /> : <></>}
|
<Layout.Header.Slot>
|
||||||
{renderButton()}
|
<Link
|
||||||
</View>
|
to="/notifications/settings"
|
||||||
</View>
|
label={_(msg`Notification settings`)}
|
||||||
)
|
size="small"
|
||||||
}
|
variant="ghost"
|
||||||
return <></>
|
color="secondary"
|
||||||
}, [isDesktop, t, hasNew, renderButton, _, isLoadingLatest])
|
shape="round"
|
||||||
|
style={[a.justify_center]}>
|
||||||
|
<ButtonIcon
|
||||||
|
icon={isLoadingLatest ? Loader : SettingsIcon}
|
||||||
|
size="lg"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</Layout.Header.Slot>
|
||||||
|
</Layout.Header.Outer>
|
||||||
|
|
||||||
const renderHeaderSpinner = React.useCallback(() => {
|
<MainScrollProvider>
|
||||||
return (
|
<Feed
|
||||||
<View
|
onScrolledDownChange={setIsScrolledDown}
|
||||||
style={[
|
scrollElRef={scrollElRef}
|
||||||
{width: 30, height: 20},
|
overridePriorityNotifications={params?.show === 'all'}
|
||||||
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>
|
</MainScrollProvider>
|
||||||
<Feed
|
{(isScrolledDown || hasNew) && (
|
||||||
onScrolledDownChange={setIsScrolledDown}
|
<LoadLatestBtn
|
||||||
scrollElRef={scrollElRef}
|
onPress={onPressLoadLatest}
|
||||||
ListHeaderComponent={ListHeaderComponent}
|
label={_(msg`Load new notifications`)}
|
||||||
overridePriorityNotifications={params?.show === 'all'}
|
showIndicator={hasNew}
|
||||||
/>
|
|
||||||
</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=""
|
|
||||||
/>
|
/>
|
||||||
</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>
|
</Layout.Screen>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {View} from 'react-native'
|
|
||||||
import {useFocusEffect} from '@react-navigation/native'
|
import {useFocusEffect} from '@react-navigation/native'
|
||||||
|
|
||||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||||
import {makeRecordUri} from '#/lib/strings/url-helpers'
|
import {makeRecordUri} from '#/lib/strings/url-helpers'
|
||||||
import {useSetMinimalShellMode} from '#/state/shell'
|
import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
import {PostThread as PostThreadComponent} from '#/view/com/post-thread/PostThread'
|
import {PostThread as PostThreadComponent} from '#/view/com/post-thread/PostThread'
|
||||||
import {atoms as a} from '#/alf'
|
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'>
|
||||||
@@ -24,9 +22,7 @@ export function PostThreadScreen({route}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen testID="postThreadScreen">
|
<Layout.Screen testID="postThreadScreen">
|
||||||
<View style={a.flex_1}>
|
<PostThreadComponent uri={uri} />
|
||||||
<PostThreadComponent uri={uri} />
|
|
||||||
</View>
|
|
||||||
</Layout.Screen>
|
</Layout.Screen>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,11 +40,9 @@ import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader'
|
|||||||
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
||||||
import {FAB} from '#/view/com/util/fab/FAB'
|
import {FAB} from '#/view/com/util/fab/FAB'
|
||||||
import {ListRef} from '#/view/com/util/List'
|
import {ListRef} from '#/view/com/util/List'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
|
||||||
import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header'
|
import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header'
|
||||||
import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed'
|
import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed'
|
||||||
import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels'
|
import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels'
|
||||||
import {web} from '#/alf'
|
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
import {ScreenHider} from '#/components/moderation/ScreenHider'
|
import {ScreenHider} from '#/components/moderation/ScreenHider'
|
||||||
import {ProfileStarterPacks} from '#/components/StarterPack/ProfileStarterPacks'
|
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
|
// Most pushes will happen here, since we will have only placeholder data
|
||||||
if (isLoadingDid || isLoadingProfile || starterPacksQuery.isLoading) {
|
if (isLoadingDid || isLoadingProfile || starterPacksQuery.isLoading) {
|
||||||
return (
|
return (
|
||||||
<CenteredView sideBorders style={web({height: '100vh'})}>
|
<Layout.Content>
|
||||||
<ProfileHeaderLoading />
|
<ProfileHeaderLoading />
|
||||||
</CenteredView>
|
</Layout.Content>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (resolveError || profileError) {
|
if (resolveError || profileError) {
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
|
|||||||
import {LoadingScreen} from '#/view/com/util/LoadingScreen'
|
import {LoadingScreen} from '#/view/com/util/LoadingScreen'
|
||||||
import {Text} from '#/view/com/util/text/Text'
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Button as NewButton, ButtonText} from '#/components/Button'
|
import {Button as NewButton, ButtonText} from '#/components/Button'
|
||||||
import {useRichText} from '#/components/hooks/useRichText'
|
import {useRichText} from '#/components/hooks/useRichText'
|
||||||
@@ -98,7 +97,7 @@ export function ProfileFeedScreen(props: Props) {
|
|||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<Layout.Screen testID="profileFeedScreenError">
|
<Layout.Screen testID="profileFeedScreenError">
|
||||||
<CenteredView>
|
<Layout.Content>
|
||||||
<View style={[pal.view, pal.border, styles.notFoundContainer]}>
|
<View style={[pal.view, pal.border, styles.notFoundContainer]}>
|
||||||
<Text type="title-lg" style={[pal.text, s.mb10]}>
|
<Text type="title-lg" style={[pal.text, s.mb10]}>
|
||||||
<Trans>Could not load feed</Trans>
|
<Trans>Could not load feed</Trans>
|
||||||
@@ -120,7 +119,7 @@ export function ProfileFeedScreen(props: Props) {
|
|||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</CenteredView>
|
</Layout.Content>
|
||||||
</Layout.Screen>
|
</Layout.Screen>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -394,7 +393,7 @@ export function ProfileFeedScreenInner({
|
|||||||
])
|
])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={s.hContentRegion}>
|
<>
|
||||||
<ReportDialog
|
<ReportDialog
|
||||||
control={reportDialogControl}
|
control={reportDialogControl}
|
||||||
params={{
|
params={{
|
||||||
@@ -434,7 +433,7 @@ export function ProfileFeedScreenInner({
|
|||||||
accessibilityHint=""
|
accessibilityHint=""
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {ProfileFollowers as ProfileFollowersComponent} from '#/view/com/profile/
|
|||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
import {CenteredView} from '#/view/com/util/Views'
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
import {ListHeaderDesktop} from '#/components/Lists'
|
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollowers'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollowers'>
|
||||||
export const ProfileFollowersScreen = ({route}: Props) => {
|
export const ProfileFollowersScreen = ({route}: Props) => {
|
||||||
@@ -27,7 +26,6 @@ export const ProfileFollowersScreen = ({route}: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Layout.Screen testID="profileFollowersScreen">
|
<Layout.Screen testID="profileFollowersScreen">
|
||||||
<CenteredView sideBorders={true}>
|
<CenteredView sideBorders={true}>
|
||||||
<ListHeaderDesktop title={_(msg`Followers`)} />
|
|
||||||
<ViewHeader title={_(msg`Followers`)} showBorder={!isWeb} />
|
<ViewHeader title={_(msg`Followers`)} showBorder={!isWeb} />
|
||||||
<ProfileFollowersComponent name={name} />
|
<ProfileFollowersComponent name={name} />
|
||||||
</CenteredView>
|
</CenteredView>
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {ProfileFollows as ProfileFollowsComponent} from '#/view/com/profile/Prof
|
|||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
import {CenteredView} from '#/view/com/util/Views'
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
import {ListHeaderDesktop} from '#/components/Lists'
|
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollows'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollows'>
|
||||||
export const ProfileFollowsScreen = ({route}: Props) => {
|
export const ProfileFollowsScreen = ({route}: Props) => {
|
||||||
@@ -27,7 +26,6 @@ export const ProfileFollowsScreen = ({route}: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Layout.Screen testID="profileFollowsScreen">
|
<Layout.Screen testID="profileFollowsScreen">
|
||||||
<CenteredView sideBorders={true}>
|
<CenteredView sideBorders={true}>
|
||||||
<ListHeaderDesktop title={_(msg`Following`)} />
|
|
||||||
<ViewHeader title={_(msg`Following`)} showBorder={!isWeb} />
|
<ViewHeader title={_(msg`Following`)} showBorder={!isWeb} />
|
||||||
<ProfileFollowsComponent name={name} />
|
<ProfileFollowsComponent name={name} />
|
||||||
</CenteredView>
|
</CenteredView>
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
|
|||||||
import {LoadingScreen} from '#/view/com/util/LoadingScreen'
|
import {LoadingScreen} from '#/view/com/util/LoadingScreen'
|
||||||
import {Text} from '#/view/com/util/text/Text'
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
|
||||||
import {ListHiddenScreen} from '#/screens/List/ListHiddenScreen'
|
import {ListHiddenScreen} from '#/screens/List/ListHiddenScreen'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
@@ -107,20 +106,20 @@ function ProfileListScreenInner(props: Props) {
|
|||||||
|
|
||||||
if (resolveError) {
|
if (resolveError) {
|
||||||
return (
|
return (
|
||||||
<CenteredView>
|
<Layout.Content>
|
||||||
<ErrorScreen
|
<ErrorScreen
|
||||||
error={_(
|
error={_(
|
||||||
msg`We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @${handleOrDid}.`,
|
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) {
|
if (listError) {
|
||||||
return (
|
return (
|
||||||
<CenteredView>
|
<Layout.Content>
|
||||||
<ErrorScreen error={cleanError(listError)} />
|
<ErrorScreen error={cleanError(listError)} />
|
||||||
</CenteredView>
|
</Layout.Content>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1010,7 +1009,6 @@ function ErrorScreen({error}: {error: string}) {
|
|||||||
pal.view,
|
pal.view,
|
||||||
pal.border,
|
pal.border,
|
||||||
{
|
{
|
||||||
marginTop: 10,
|
|
||||||
paddingHorizontal: 18,
|
paddingHorizontal: 18,
|
||||||
paddingVertical: 14,
|
paddingVertical: 14,
|
||||||
borderTopWidth: StyleSheet.hairlineWidth,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
|
|||||||
+117
-127
@@ -25,13 +25,12 @@ import {FeedSourceCard} from '#/view/com/feeds/FeedSourceCard'
|
|||||||
import {TextLink} from '#/view/com/util/Link'
|
import {TextLink} from '#/view/com/util/Link'
|
||||||
import {Text} from '#/view/com/util/text/Text'
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
|
||||||
import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
|
||||||
import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
|
import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
|
||||||
import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
|
import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline'
|
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 * as Layout from '#/components/Layout'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
|
|
||||||
@@ -51,7 +50,7 @@ function SavedFeedsInner({
|
|||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {isMobile, isTabletOrDesktop, isDesktop} = useWebMediaQueries()
|
const {isMobile, isDesktop} = useWebMediaQueries()
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const {mutateAsync: overwriteSavedFeeds, isPending: isOverwritePending} =
|
const {mutateAsync: overwriteSavedFeeds, isPending: isOverwritePending} =
|
||||||
useOverwriteSavedFeedsMutation()
|
useOverwriteSavedFeedsMutation()
|
||||||
@@ -88,136 +87,128 @@ function SavedFeedsInner({
|
|||||||
}
|
}
|
||||||
}, [_, overwriteSavedFeeds, currentFeeds, navigation])
|
}, [_, 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 (
|
return (
|
||||||
<Layout.Screen>
|
<Layout.Screen>
|
||||||
<CenteredView
|
<Layout.Header.Outer>
|
||||||
style={[a.util_screen_outer]}
|
<Layout.Header.BackButton />
|
||||||
sideBorders={isTabletOrDesktop}>
|
<Layout.Header.Content align="left">
|
||||||
<ViewHeader
|
<Layout.Header.TitleText>
|
||||||
title={_(msg`Edit My Feeds`)}
|
<Trans>Feeds</Trans>
|
||||||
showOnDesktop
|
</Layout.Header.TitleText>
|
||||||
showBorder
|
</Layout.Header.Content>
|
||||||
renderButton={renderHeaderBtn}
|
<Button
|
||||||
/>
|
testID="saveChangesBtn"
|
||||||
<ScrollView style={[a.flex_1]} contentContainerStyle={[a.border_0]}>
|
size="small"
|
||||||
{noSavedFeedsOfAnyType && (
|
variant={hasUnsavedChanges ? 'solid' : 'solid'}
|
||||||
<View style={[pal.border, a.border_b]}>
|
color={hasUnsavedChanges ? 'primary' : 'secondary'}
|
||||||
<NoSavedFeedsOfAnyType />
|
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>
|
||||||
)}
|
|
||||||
|
|
||||||
<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 && (
|
{noFollowingFeed && (
|
||||||
<View style={[pal.border, a.border_b]}>
|
<View style={[pal.border, a.border_b]}>
|
||||||
<NoFollowingFeed />
|
<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>
|
||||||
)}
|
|
||||||
|
|
||||||
<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}>
|
<View style={styles.footerText}>
|
||||||
<Text type="sm" style={pal.textLight}>
|
<Text type="sm" style={pal.textLight}>
|
||||||
<Trans>
|
<Trans>
|
||||||
Feeds are custom algorithms that users build with a little
|
Feeds are custom algorithms that users build with a little coding
|
||||||
coding expertise.{' '}
|
expertise.{' '}
|
||||||
<TextLink
|
<TextLink
|
||||||
type="sm"
|
type="sm"
|
||||||
style={pal.link}
|
style={pal.link}
|
||||||
href="https://github.com/bluesky-social/feed-generator"
|
href="https://github.com/bluesky-social/feed-generator"
|
||||||
text={_(msg`See this guide`)}
|
text={_(msg`See this guide`)}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
for more information.
|
for more information.
|
||||||
</Trans>
|
</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={{height: 100}} />
|
</Layout.Content>
|
||||||
</ScrollView>
|
|
||||||
</CenteredView>
|
|
||||||
</Layout.Screen>
|
</Layout.Screen>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -456,7 +447,6 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
footerText: {
|
footerText: {
|
||||||
paddingHorizontal: 26,
|
paddingHorizontal: 26,
|
||||||
paddingTop: 22,
|
paddingVertical: 22,
|
||||||
paddingBottom: 100,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
+105
-131
@@ -55,7 +55,6 @@ import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
|
|||||||
import {Link} from '#/view/com/util/Link'
|
import {Link} from '#/view/com/util/Link'
|
||||||
import {List} from '#/view/com/util/List'
|
import {List} from '#/view/com/util/List'
|
||||||
import {Text} from '#/view/com/util/text/Text'
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
|
||||||
import {Explore} from '#/view/screens/Search/Explore'
|
import {Explore} from '#/view/screens/Search/Explore'
|
||||||
import {SearchLinkCard, SearchProfileCard} from '#/view/shell/desktop/Search'
|
import {SearchLinkCard, SearchProfileCard} from '#/view/shell/desktop/Search'
|
||||||
import {makeSearchQuery, parseSearchQuery} from '#/screens/Search/utils'
|
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'
|
import * as Layout from '#/components/Layout'
|
||||||
|
|
||||||
function Loader() {
|
function Loader() {
|
||||||
const pal = usePalette('default')
|
|
||||||
const {isMobile} = useWebMediaQueries()
|
|
||||||
return (
|
return (
|
||||||
<CenteredView
|
<Layout.Content>
|
||||||
style={[
|
<View style={[a.py_xl]}>
|
||||||
// @ts-ignore web only -prf
|
<ActivityIndicator />
|
||||||
{
|
</View>
|
||||||
padding: 18,
|
</Layout.Content>
|
||||||
height: isWeb ? '100vh' : undefined,
|
|
||||||
},
|
|
||||||
pal.border,
|
|
||||||
]}
|
|
||||||
sideBorders={!isMobile}>
|
|
||||||
<ActivityIndicator />
|
|
||||||
</CenteredView>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function EmptyState({message, error}: {message: string; error?: string}) {
|
function EmptyState({message, error}: {message: string; error?: string}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {isMobile} = useWebMediaQueries()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CenteredView
|
<Layout.Content>
|
||||||
sideBorders={!isMobile}
|
<View style={[a.p_xl]}>
|
||||||
style={[
|
<View style={[pal.viewLight, {padding: 18, borderRadius: 8}]}>
|
||||||
pal.border,
|
<Text style={[pal.text]}>{message}</Text>
|
||||||
// @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>
|
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<>
|
<>
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
{
|
{
|
||||||
marginVertical: 12,
|
marginVertical: 12,
|
||||||
height: 1,
|
height: 1,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
backgroundColor: pal.text.color,
|
backgroundColor: pal.text.color,
|
||||||
opacity: 0.2,
|
opacity: 0.2,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Text style={[pal.textLight]}>
|
<Text style={[pal.textLight]}>
|
||||||
<Trans>Error:</Trans> {error}
|
<Trans>Error:</Trans> {error}
|
||||||
</Text>
|
</Text>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</CenteredView>
|
</Layout.Content>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +206,7 @@ let SearchScreenPostResults = ({
|
|||||||
if (item.type === 'post') {
|
if (item.type === 'post') {
|
||||||
return <Post post={item.post} />
|
return <Post post={item.post} />
|
||||||
} else {
|
} else {
|
||||||
return <Loader />
|
return null
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
keyExtractor={item => item.key}
|
keyExtractor={item => item.key}
|
||||||
@@ -550,19 +532,13 @@ let SearchScreenInner = ({
|
|||||||
<Pager
|
<Pager
|
||||||
onPageSelected={onPageSelected}
|
onPageSelected={onPageSelected}
|
||||||
renderTabBar={props => (
|
renderTabBar={props => (
|
||||||
<CenteredView
|
<Layout.Center
|
||||||
sideBorders
|
|
||||||
style={[
|
style={[
|
||||||
pal.border,
|
web([a.sticky, a.z_10]),
|
||||||
pal.view,
|
|
||||||
web({
|
|
||||||
position: isWeb ? 'sticky' : '',
|
|
||||||
zIndex: 1,
|
|
||||||
}),
|
|
||||||
{top: isWeb ? headerHeight : undefined},
|
{top: isWeb ? headerHeight : undefined},
|
||||||
]}>
|
]}>
|
||||||
<TabBar items={sections.map(section => section.title)} {...props} />
|
<TabBar items={sections.map(section => section.title)} {...props} />
|
||||||
</CenteredView>
|
</Layout.Center>
|
||||||
)}
|
)}
|
||||||
initialPage={0}>
|
initialPage={0}>
|
||||||
{sections.map((section, i) => (
|
{sections.map((section, i) => (
|
||||||
@@ -572,7 +548,7 @@ let SearchScreenInner = ({
|
|||||||
) : hasSession ? (
|
) : hasSession ? (
|
||||||
<Explore />
|
<Explore />
|
||||||
) : (
|
) : (
|
||||||
<CenteredView sideBorders style={pal.border}>
|
<Layout.Center>
|
||||||
<View
|
<View
|
||||||
// @ts-ignore web only -esb
|
// @ts-ignore web only -esb
|
||||||
style={{
|
style={{
|
||||||
@@ -614,7 +590,7 @@ let SearchScreenInner = ({
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</CenteredView>
|
</Layout.Center>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SearchScreenInner = React.memo(SearchScreenInner)
|
SearchScreenInner = React.memo(SearchScreenInner)
|
||||||
@@ -650,7 +626,7 @@ export function SearchScreen(
|
|||||||
* Arbitrary sizing, so guess and check, used for sticky header alignment and
|
* Arbitrary sizing, so guess and check, used for sticky header alignment and
|
||||||
* sizing.
|
* sizing.
|
||||||
*/
|
*/
|
||||||
const headerHeight = 64 + (showFilters ? 40 : 0)
|
const headerHeight = 60 + (showFilters ? 40 : 0)
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useNonReactiveCallback(() => {
|
useNonReactiveCallback(() => {
|
||||||
@@ -861,73 +837,79 @@ export function SearchScreen(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Screen testID="searchScreen">
|
<Layout.Screen testID="searchScreen">
|
||||||
<CenteredView
|
<View
|
||||||
style={[
|
style={[
|
||||||
a.p_md,
|
|
||||||
a.pb_sm,
|
|
||||||
a.gap_sm,
|
|
||||||
t.atoms.bg,
|
|
||||||
web({
|
web({
|
||||||
height: headerHeight,
|
height: headerHeight,
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: 0,
|
top: 0,
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
}),
|
}),
|
||||||
]}
|
]}>
|
||||||
sideBorders={gtMobile}>
|
<Layout.Center>
|
||||||
<View style={[a.flex_row, a.gap_sm]}>
|
<View style={[a.p_md, a.pb_sm, a.gap_sm, t.atoms.bg]}>
|
||||||
{!gtMobile && (
|
<View style={[a.flex_row, a.gap_sm]}>
|
||||||
<Button
|
{!gtMobile && (
|
||||||
testID="viewHeaderBackOrMenuBtn"
|
<Button
|
||||||
onPress={onPressMenu}
|
testID="viewHeaderBackOrMenuBtn"
|
||||||
hitSlop={HITSLOP_10}
|
onPress={onPressMenu}
|
||||||
label={_(msg`Menu`)}
|
hitSlop={HITSLOP_10}
|
||||||
accessibilityHint={_(msg`Access navigation links and settings`)}
|
label={_(msg`Menu`)}
|
||||||
size="large"
|
accessibilityHint={_(
|
||||||
variant="solid"
|
msg`Access navigation links and settings`,
|
||||||
color="secondary"
|
)}
|
||||||
shape="square">
|
size="large"
|
||||||
<ButtonIcon icon={Menu} size="lg" />
|
variant="solid"
|
||||||
</Button>
|
color="secondary"
|
||||||
)}
|
shape="square">
|
||||||
<View style={[a.flex_1]}>
|
<ButtonIcon icon={Menu} size="lg" />
|
||||||
<SearchInput
|
</Button>
|
||||||
ref={textInput}
|
)}
|
||||||
value={searchText}
|
<View style={[a.flex_1]}>
|
||||||
onFocus={onSearchInputFocus}
|
<SearchInput
|
||||||
onChangeText={onChangeText}
|
ref={textInput}
|
||||||
onClearText={onPressClearQuery}
|
value={searchText}
|
||||||
onSubmitEditing={onSubmit}
|
onFocus={onSearchInputFocus}
|
||||||
/>
|
onChangeText={onChangeText}
|
||||||
</View>
|
onClearText={onPressClearQuery}
|
||||||
{showAutocomplete && (
|
onSubmitEditing={onSubmit}
|
||||||
<Button
|
/>
|
||||||
label={_(msg`Cancel search`)}
|
</View>
|
||||||
size="large"
|
{showAutocomplete && (
|
||||||
variant="ghost"
|
<Button
|
||||||
color="secondary"
|
label={_(msg`Cancel search`)}
|
||||||
style={[a.px_sm]}
|
size="large"
|
||||||
onPress={onPressCancelSearch}
|
variant="ghost"
|
||||||
hitSlop={HITSLOP_10}>
|
color="secondary"
|
||||||
<ButtonText>
|
style={[a.px_sm]}
|
||||||
<Trans>Cancel</Trans>
|
onPress={onPressCancelSearch}
|
||||||
</ButtonText>
|
hitSlop={HITSLOP_10}>
|
||||||
</Button>
|
<ButtonText>
|
||||||
)}
|
<Trans>Cancel</Trans>
|
||||||
</View>
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
{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>
|
||||||
|
|
||||||
|
{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>
|
</View>
|
||||||
)}
|
</Layout.Center>
|
||||||
</CenteredView>
|
</View>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
@@ -992,10 +974,7 @@ let AutocompleteResults = ({
|
|||||||
!moderationOpts ? (
|
!moderationOpts ? (
|
||||||
<Loader />
|
<Loader />
|
||||||
) : (
|
) : (
|
||||||
<ScrollView
|
<Layout.Content
|
||||||
style={{height: '100%'}}
|
|
||||||
// @ts-ignore web only -prf
|
|
||||||
dataSet={{stableGutters: '1'}}
|
|
||||||
keyboardShouldPersistTaps="handled"
|
keyboardShouldPersistTaps="handled"
|
||||||
keyboardDismissMode="on-drag">
|
keyboardDismissMode="on-drag">
|
||||||
<SearchLinkCard
|
<SearchLinkCard
|
||||||
@@ -1020,7 +999,7 @@ let AutocompleteResults = ({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<View style={{height: 200}} />
|
<View style={{height: 200}} />
|
||||||
</ScrollView>
|
</Layout.Content>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@@ -1042,17 +1021,12 @@ function SearchHistory({
|
|||||||
onRemoveItemClick: (item: string) => void
|
onRemoveItemClick: (item: string) => void
|
||||||
onRemoveProfileClick: (profile: AppBskyActorDefs.ProfileViewBasic) => void
|
onRemoveProfileClick: (profile: AppBskyActorDefs.ProfileViewBasic) => void
|
||||||
}) {
|
}) {
|
||||||
const {isTabletOrDesktop, isMobile} = useWebMediaQueries()
|
const {isMobile} = useWebMediaQueries()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CenteredView
|
<Layout.Content>
|
||||||
sideBorders={isTabletOrDesktop}
|
|
||||||
// @ts-ignore web only -prf
|
|
||||||
style={{
|
|
||||||
height: isWeb ? '100vh' : undefined,
|
|
||||||
}}>
|
|
||||||
<View style={styles.searchHistoryContainer}>
|
<View style={styles.searchHistoryContainer}>
|
||||||
{(searchHistory.length > 0 || selectedProfiles.length > 0) && (
|
{(searchHistory.length > 0 || selectedProfiles.length > 0) && (
|
||||||
<Text style={[pal.text, styles.searchHistoryTitle]}>
|
<Text style={[pal.text, styles.searchHistoryTitle]}>
|
||||||
@@ -1152,7 +1126,7 @@ function SearchHistory({
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</CenteredView>
|
</Layout.Content>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import {StyleSheet, View} from 'react-native'
|
|||||||
import {DismissableLayer} from '@radix-ui/react-dismissable-layer'
|
import {DismissableLayer} from '@radix-ui/react-dismissable-layer'
|
||||||
import {useFocusGuards} from '@radix-ui/react-focus-guards'
|
import {useFocusGuards} from '@radix-ui/react-focus-guards'
|
||||||
import {FocusScope} from '@radix-ui/react-focus-scope'
|
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 {useModals} from '#/state/modals'
|
||||||
import {ComposerOpts, useComposerState} from '#/state/shell/composer'
|
import {ComposerOpts, useComposerState} from '#/state/shell/composer'
|
||||||
import {
|
import {
|
||||||
@@ -20,8 +20,6 @@ export function Composer({}: {winHeight: number}) {
|
|||||||
const state = useComposerState()
|
const state = useComposerState()
|
||||||
const isActive = !!state
|
const isActive = !!state
|
||||||
|
|
||||||
useWebBodyScrollLock(isActive)
|
|
||||||
|
|
||||||
// rendering
|
// rendering
|
||||||
// =
|
// =
|
||||||
|
|
||||||
@@ -29,7 +27,12 @@ export function Composer({}: {winHeight: number}) {
|
|||||||
return <View />
|
return <View />
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Inner state={state} />
|
return (
|
||||||
|
<>
|
||||||
|
<RemoveScrollBar />
|
||||||
|
<Inner state={state} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Inner({state}: {state: ComposerOpts}) {
|
function Inner({state}: {state: ComposerOpts}) {
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
import {StyleSheet, View} from 'react-native'
|
||||||
import {
|
import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
|
||||||
FontAwesomeIcon,
|
|
||||||
FontAwesomeIconStyle,
|
|
||||||
} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {
|
import {
|
||||||
@@ -14,9 +11,9 @@ import {
|
|||||||
|
|
||||||
import {usePalette} from '#/lib/hooks/usePalette'
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
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 {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 {isInvalidHandle} from '#/lib/strings/handles'
|
||||||
import {emitSoftReset} from '#/state/events'
|
import {emitSoftReset} from '#/state/events'
|
||||||
import {useFetchHandle} from '#/state/queries/handle'
|
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 {
|
interface NavItemProps {
|
||||||
count?: string
|
count?: string
|
||||||
href: string
|
href: string
|
||||||
@@ -220,35 +176,44 @@ function NavItem({count, href, icon, iconFilled, label}: NavItemProps) {
|
|||||||
]}>
|
]}>
|
||||||
{isCurrent ? iconFilled : icon}
|
{isCurrent ? iconFilled : icon}
|
||||||
{typeof count === 'string' && count ? (
|
{typeof count === 'string' && count ? (
|
||||||
<Text
|
<View
|
||||||
accessibilityLabel={_(msg`${count} unread items`)}
|
|
||||||
accessibilityHint=""
|
|
||||||
accessible={true}
|
|
||||||
style={[
|
style={[
|
||||||
a.absolute,
|
a.absolute,
|
||||||
a.text_xs,
|
a.inset_0,
|
||||||
a.font_bold,
|
{right: -20}, // more breathing room
|
||||||
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%',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
]}>
|
]}>
|
||||||
{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}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
{gtTablet && (
|
{gtTablet && (
|
||||||
@@ -366,9 +331,9 @@ export function DesktopLeftNav() {
|
|||||||
<View
|
<View
|
||||||
role="navigation"
|
role="navigation"
|
||||||
style={[
|
style={[
|
||||||
|
a.px_xl,
|
||||||
styles.leftNav,
|
styles.leftNav,
|
||||||
isTablet && styles.leftNavTablet,
|
isTablet && styles.leftNavTablet,
|
||||||
pal.view,
|
|
||||||
pal.border,
|
pal.border,
|
||||||
]}>
|
]}>
|
||||||
{hasSession ? (
|
{hasSession ? (
|
||||||
@@ -381,8 +346,6 @@ export function DesktopLeftNav() {
|
|||||||
|
|
||||||
{hasSession && (
|
{hasSession && (
|
||||||
<>
|
<>
|
||||||
<BackBtn />
|
|
||||||
|
|
||||||
<NavItem
|
<NavItem
|
||||||
href="/"
|
href="/"
|
||||||
icon={
|
icon={
|
||||||
@@ -525,8 +488,17 @@ const styles = StyleSheet.create({
|
|||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
top: 10,
|
top: 10,
|
||||||
// @ts-ignore web only
|
// @ts-ignore web only
|
||||||
left: 'calc(50vw - 300px - 220px - 20px)',
|
left: '50%',
|
||||||
width: 220,
|
transform: [
|
||||||
|
{
|
||||||
|
translateX: -300,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
translateX: '-100%',
|
||||||
|
},
|
||||||
|
...a.scrollbar_offset.transform,
|
||||||
|
],
|
||||||
|
width: 240,
|
||||||
// @ts-ignore web only
|
// @ts-ignore web only
|
||||||
maxHeight: 'calc(100vh - 10px)',
|
maxHeight: 'calc(100vh - 10px)',
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
@@ -538,7 +510,10 @@ const styles = StyleSheet.create({
|
|||||||
borderRightWidth: 1,
|
borderRightWidth: 1,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
width: 76,
|
width: 76,
|
||||||
|
paddingLeft: 0,
|
||||||
|
paddingRight: 0,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
transform: [],
|
||||||
},
|
},
|
||||||
|
|
||||||
profileCard: {
|
profileCard: {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.rightNav, pal.view]}>
|
<View style={[a.px_xl, styles.rightNav]}>
|
||||||
<View style={{paddingVertical: 20}}>
|
<View style={{paddingVertical: 20}}>
|
||||||
{routeName === 'Search' ? (
|
{routeName === 'Search' ? (
|
||||||
<View style={{marginBottom: 18}}>
|
<View style={{marginBottom: 18}}>
|
||||||
@@ -122,8 +122,13 @@ const styles = StyleSheet.create({
|
|||||||
// @ts-ignore web only
|
// @ts-ignore web only
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
// @ts-ignore web only
|
// @ts-ignore web only
|
||||||
left: 'calc(50vw + 300px + 20px)',
|
left: '50%',
|
||||||
width: 300,
|
transform: [
|
||||||
|
{
|
||||||
|
translateX: 300,
|
||||||
|
},
|
||||||
|
...a.scrollbar_offset.transform,
|
||||||
|
],
|
||||||
maxHeight: '100%',
|
maxHeight: '100%',
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
|
|||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
|
import {RemoveScrollBar} from 'react-remove-scroll-bar'
|
||||||
|
|
||||||
import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
|
import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
|
||||||
import {useIntentHandler} from '#/lib/hooks/useIntentHandler'
|
import {useIntentHandler} from '#/lib/hooks/useIntentHandler'
|
||||||
import {useWebBodyScrollLock} from '#/lib/hooks/useWebBodyScrollLock'
|
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
import {NavigationProp} from '#/lib/routes/types'
|
import {NavigationProp} from '#/lib/routes/types'
|
||||||
import {colors} from '#/lib/styles'
|
import {colors} from '#/lib/styles'
|
||||||
@@ -34,7 +34,6 @@ function ShellInner() {
|
|||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const showDrawer = !isDesktop && isDrawerOpen
|
const showDrawer = !isDesktop && isDrawerOpen
|
||||||
|
|
||||||
useWebBodyScrollLock(showDrawer)
|
|
||||||
useComposerKeyboardShortcut()
|
useComposerKeyboardShortcut()
|
||||||
useIntentHandler()
|
useIntentHandler()
|
||||||
|
|
||||||
@@ -58,31 +57,34 @@ function ShellInner() {
|
|||||||
<PortalOutlet />
|
<PortalOutlet />
|
||||||
|
|
||||||
{showDrawer && (
|
{showDrawer && (
|
||||||
<TouchableWithoutFeedback
|
<>
|
||||||
onPress={ev => {
|
<RemoveScrollBar />
|
||||||
// Only close if press happens outside of the drawer
|
<TouchableWithoutFeedback
|
||||||
if (ev.target === ev.currentTarget) {
|
onPress={ev => {
|
||||||
setDrawerOpen(false)
|
// 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
|
accessibilityLabel={_(msg`Close navigation footer`)}
|
||||||
style={[
|
accessibilityHint={_(msg`Closes bottom navigation bar`)}>
|
||||||
styles.drawerMask,
|
<View
|
||||||
{
|
style={[
|
||||||
backgroundColor: select(t.name, {
|
styles.drawerMask,
|
||||||
light: 'rgba(0, 57, 117, 0.1)',
|
{
|
||||||
dark: 'rgba(1, 82, 168, 0.1)',
|
backgroundColor: select(t.name, {
|
||||||
dim: 'rgba(10, 13, 16, 0.8)',
|
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 style={styles.drawerContainer}>
|
||||||
|
<DrawerContent />
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</TouchableWithoutFeedback>
|
||||||
</TouchableWithoutFeedback>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
+7
-2
@@ -45,7 +45,6 @@
|
|||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
scrollbar-gutter: stable both-edges;
|
|
||||||
}
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
html {
|
html {
|
||||||
@@ -81,9 +80,15 @@
|
|||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateX(-50%) translateY(-50%) translateY(-50px);
|
transform: translateX(-50%) translateY(-50%) translateY(-50px);
|
||||||
}
|
}
|
||||||
/* We need this style to prevent web dropdowns from shifting the display when opening */
|
/**
|
||||||
|
* We need these styles to prevent shifting due to scrollbar show/hide on
|
||||||
|
* OSs that have them enabled by default. This also handles cases where the
|
||||||
|
* screen wouldn't otherwise scroll, and therefore hide the scrollbar and
|
||||||
|
* shift the content, by forcing the page to show a scrollbar.
|
||||||
|
*/
|
||||||
body {
|
body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -17616,16 +17616,7 @@ string-natural-compare@^3.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
||||||
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0":
|
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||||
version "4.2.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
|
||||||
dependencies:
|
|
||||||
emoji-regex "^8.0.0"
|
|
||||||
is-fullwidth-code-point "^3.0.0"
|
|
||||||
strip-ansi "^6.0.1"
|
|
||||||
|
|
||||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@@ -17725,7 +17716,7 @@ string_decoder@~1.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.1.0"
|
safe-buffer "~5.1.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
@@ -17739,13 +17730,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ansi-regex "^4.1.0"
|
ansi-regex "^4.1.0"
|
||||||
|
|
||||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
|
||||||
version "6.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
|
||||||
dependencies:
|
|
||||||
ansi-regex "^5.0.1"
|
|
||||||
|
|
||||||
strip-ansi@^7.0.1:
|
strip-ansi@^7.0.1:
|
||||||
version "7.1.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||||
@@ -19068,7 +19052,7 @@ wordwrap@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
|
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
|
||||||
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
|
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
|
||||||
|
|
||||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
@@ -19086,15 +19070,6 @@ wrap-ansi@^6.2.0:
|
|||||||
string-width "^4.1.0"
|
string-width "^4.1.0"
|
||||||
strip-ansi "^6.0.0"
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
wrap-ansi@^7.0.0:
|
|
||||||
version "7.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
|
||||||
dependencies:
|
|
||||||
ansi-styles "^4.0.0"
|
|
||||||
string-width "^4.1.0"
|
|
||||||
strip-ansi "^6.0.0"
|
|
||||||
|
|
||||||
wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
|
wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
|
||||||
version "8.1.0"
|
version "8.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||||
|
|||||||
Reference in New Issue
Block a user