Merge remote-tracking branch 'origin/main' into samuel/alf-selflabel
This commit is contained in:
+10
-1
@@ -23,7 +23,16 @@ module.exports = {
|
||||
'bsky-internal/avoid-unwrapped-text': [
|
||||
'error',
|
||||
{
|
||||
impliedTextComponents: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P'],
|
||||
impliedTextComponents: [
|
||||
'H1',
|
||||
'H2',
|
||||
'H3',
|
||||
'H4',
|
||||
'H5',
|
||||
'H6',
|
||||
'P',
|
||||
'Admonition',
|
||||
],
|
||||
impliedTextProps: [],
|
||||
suggestedTextWrappers: {
|
||||
Button: 'ButtonText',
|
||||
|
||||
@@ -75,6 +75,10 @@
|
||||
top: 50%;
|
||||
transform: translateX(-50%) translateY(-50%) translateY(-50px);
|
||||
}
|
||||
/* We need this style to prevent web dropdowns from shifting the display when opening */
|
||||
body {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% include "scripts.html" %}
|
||||
|
||||
@@ -14,7 +14,6 @@ import * as SplashScreen from 'expo-splash-screen'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useIntentHandler} from '#/lib/hooks/useIntentHandler'
|
||||
import {QueryProvider} from '#/lib/react-query'
|
||||
import {
|
||||
initialize,
|
||||
@@ -85,7 +84,6 @@ function InnerApp() {
|
||||
const theme = useColorModeTheme()
|
||||
const {_} = useLingui()
|
||||
|
||||
useIntentHandler()
|
||||
const hasCheckedReferrer = useStarterPackEntry()
|
||||
|
||||
// init
|
||||
|
||||
@@ -9,7 +9,6 @@ import {SafeAreaProvider} from 'react-native-safe-area-context'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useIntentHandler} from '#/lib/hooks/useIntentHandler'
|
||||
import {QueryProvider} from '#/lib/react-query'
|
||||
import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
|
||||
import {ThemeProvider} from '#/lib/ThemeContext'
|
||||
@@ -42,7 +41,6 @@ import {
|
||||
import {readLastActiveAccount} from '#/state/session/util'
|
||||
import {Provider as ShellStateProvider} from '#/state/shell'
|
||||
import {Provider as ComposerProvider} from '#/state/shell/composer'
|
||||
import {useComposerKeyboardShortcut} from '#/state/shell/composer/useComposerKeyboardShortcut'
|
||||
import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out'
|
||||
import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
|
||||
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
|
||||
@@ -72,11 +70,8 @@ function InnerApp() {
|
||||
const {resumeSession} = useSessionApi()
|
||||
const theme = useColorModeTheme()
|
||||
const {_} = useLingui()
|
||||
useIntentHandler()
|
||||
const hasCheckedReferrer = useStarterPackEntry()
|
||||
|
||||
useComposerKeyboardShortcut()
|
||||
|
||||
// init
|
||||
useEffect(() => {
|
||||
async function onLaunch(account?: SessionAccount) {
|
||||
|
||||
+13
-13
@@ -17,7 +17,6 @@ import {
|
||||
|
||||
import {timeout} from '#/lib/async/timeout'
|
||||
import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebScrollRestoration} from '#/lib/hooks/useWebScrollRestoration'
|
||||
import {buildStateObject} from '#/lib/routes/helpers'
|
||||
import {
|
||||
@@ -93,6 +92,7 @@ import {
|
||||
StarterPackScreenShort,
|
||||
} from '#/screens/StarterPack/StarterPackScreen'
|
||||
import {Wizard} from '#/screens/StarterPack/Wizard'
|
||||
import {useTheme} from '#/alf'
|
||||
import {router} from '#/routes'
|
||||
import {Referrer} from '../modules/expo-bluesky-swiss-army'
|
||||
|
||||
@@ -412,7 +412,7 @@ function TabsNavigator() {
|
||||
}
|
||||
|
||||
function HomeTabNavigator() {
|
||||
const pal = usePalette('default')
|
||||
const t = useTheme()
|
||||
|
||||
return (
|
||||
<HomeTab.Navigator
|
||||
@@ -422,7 +422,7 @@ function HomeTabNavigator() {
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: pal.view,
|
||||
contentStyle: t.atoms.bg,
|
||||
}}>
|
||||
<HomeTab.Screen name="Home" getComponent={() => HomeScreen} />
|
||||
<HomeTab.Screen name="Start" getComponent={() => HomeScreen} />
|
||||
@@ -432,7 +432,7 @@ function HomeTabNavigator() {
|
||||
}
|
||||
|
||||
function SearchTabNavigator() {
|
||||
const pal = usePalette('default')
|
||||
const t = useTheme()
|
||||
return (
|
||||
<SearchTab.Navigator
|
||||
screenOptions={{
|
||||
@@ -441,7 +441,7 @@ function SearchTabNavigator() {
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: pal.view,
|
||||
contentStyle: t.atoms.bg,
|
||||
}}>
|
||||
<SearchTab.Screen name="Search" getComponent={() => SearchScreen} />
|
||||
{commonScreens(SearchTab as typeof HomeTab)}
|
||||
@@ -450,7 +450,7 @@ function SearchTabNavigator() {
|
||||
}
|
||||
|
||||
function NotificationsTabNavigator() {
|
||||
const pal = usePalette('default')
|
||||
const t = useTheme()
|
||||
return (
|
||||
<NotificationsTab.Navigator
|
||||
screenOptions={{
|
||||
@@ -459,7 +459,7 @@ function NotificationsTabNavigator() {
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: pal.view,
|
||||
contentStyle: t.atoms.bg,
|
||||
}}>
|
||||
<NotificationsTab.Screen
|
||||
name="Notifications"
|
||||
@@ -472,7 +472,7 @@ function NotificationsTabNavigator() {
|
||||
}
|
||||
|
||||
function MyProfileTabNavigator() {
|
||||
const pal = usePalette('default')
|
||||
const t = useTheme()
|
||||
return (
|
||||
<MyProfileTab.Navigator
|
||||
screenOptions={{
|
||||
@@ -481,7 +481,7 @@ function MyProfileTabNavigator() {
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: pal.view,
|
||||
contentStyle: t.atoms.bg,
|
||||
}}>
|
||||
<MyProfileTab.Screen
|
||||
// @ts-ignore // TODO: fix this broken type in ProfileScreen
|
||||
@@ -498,7 +498,7 @@ function MyProfileTabNavigator() {
|
||||
}
|
||||
|
||||
function MessagesTabNavigator() {
|
||||
const pal = usePalette('default')
|
||||
const t = useTheme()
|
||||
return (
|
||||
<MessagesTab.Navigator
|
||||
screenOptions={{
|
||||
@@ -507,7 +507,7 @@ function MessagesTabNavigator() {
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: pal.view,
|
||||
contentStyle: t.atoms.bg,
|
||||
}}>
|
||||
<MessagesTab.Screen
|
||||
name="Messages"
|
||||
@@ -527,7 +527,7 @@ function MessagesTabNavigator() {
|
||||
* in a single ("flat") stack.
|
||||
*/
|
||||
const FlatNavigator = () => {
|
||||
const pal = usePalette('default')
|
||||
const t = useTheme()
|
||||
const numUnread = useUnreadNotifications()
|
||||
const screenListeners = useWebScrollRestoration()
|
||||
const title = (page: MessageDescriptor) => bskyTitle(i18n._(page), numUnread)
|
||||
@@ -541,7 +541,7 @@ const FlatNavigator = () => {
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: pal.view,
|
||||
contentStyle: t.atoms.bg,
|
||||
}}>
|
||||
<Flat.Screen
|
||||
name="Home"
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@ import {useFonts} from 'expo-font'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {Device, device} from '#/storage'
|
||||
|
||||
const FAMILIES = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Liberation Sans", Helvetica, Arial, sans-serif`
|
||||
const WEB_FONT_FAMILIES = `system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"`
|
||||
|
||||
const factor = 0.0625 // 1 - (15/16)
|
||||
const fontScaleMultipliers: Record<Device['fontScale'], number> = {
|
||||
@@ -48,7 +48,7 @@ export function applyFonts(style: TextStyle, fontFamily: 'system' | 'theme') {
|
||||
|
||||
// fallback families only supported on web
|
||||
if (isWeb) {
|
||||
style.fontFamily += `, ${FAMILIES}`
|
||||
style.fontFamily += `, ${WEB_FONT_FAMILIES}`
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,7 +59,7 @@ export function applyFonts(style: TextStyle, fontFamily: 'system' | 'theme') {
|
||||
} else {
|
||||
// fallback families only supported on web
|
||||
if (isWeb) {
|
||||
style.fontFamily = style.fontFamily || FAMILIES
|
||||
style.fontFamily = style.fontFamily || WEB_FONT_FAMILIES
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import React from 'react'
|
||||
import {StyleProp, View, ViewStyle} from 'react-native'
|
||||
|
||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||
import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo'
|
||||
import {Eye_Stroke2_Corner0_Rounded as InfoIcon} from '#/components/icons/Eye'
|
||||
import {Leaf_Stroke2_Corner0_Rounded as TipIcon} from '#/components/icons/Leaf'
|
||||
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
|
||||
import {Text as BaseText, TextProps} from '#/components/Typography'
|
||||
|
||||
const colors = {
|
||||
warning: {
|
||||
light: '#DFBC00',
|
||||
dark: '#BFAF1F',
|
||||
},
|
||||
}
|
||||
|
||||
type Context = {
|
||||
type: 'info' | 'tip' | 'warning' | 'error'
|
||||
}
|
||||
|
||||
const Context = React.createContext<Context>({
|
||||
type: 'info',
|
||||
})
|
||||
|
||||
export function Icon() {
|
||||
const t = useTheme()
|
||||
const {type} = React.useContext(Context)
|
||||
const Icon = {
|
||||
info: InfoIcon,
|
||||
tip: TipIcon,
|
||||
warning: WarningIcon,
|
||||
error: ErrorIcon,
|
||||
}[type]
|
||||
const fill = {
|
||||
info: t.atoms.text_contrast_medium.color,
|
||||
tip: t.palette.primary_500,
|
||||
warning: colors.warning.light,
|
||||
error: t.palette.negative_500,
|
||||
}[type]
|
||||
return <Icon fill={fill} size="md" />
|
||||
}
|
||||
|
||||
export function Text({
|
||||
children,
|
||||
style,
|
||||
...rest
|
||||
}: Pick<TextProps, 'children' | 'style'>) {
|
||||
return (
|
||||
<BaseText
|
||||
{...rest}
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_sm,
|
||||
a.leading_snug,
|
||||
{
|
||||
paddingTop: 1,
|
||||
},
|
||||
style,
|
||||
]}>
|
||||
{children}
|
||||
</BaseText>
|
||||
)
|
||||
}
|
||||
|
||||
export function Row({children}: {children: React.ReactNode}) {
|
||||
return <View style={[a.flex_row, a.gap_sm]}>{children}</View>
|
||||
}
|
||||
|
||||
export function Outer({
|
||||
children,
|
||||
type = 'info',
|
||||
style,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
type?: Context['type']
|
||||
style?: StyleProp<ViewStyle>
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const borderColor = {
|
||||
info: t.atoms.border_contrast_low.borderColor,
|
||||
tip: t.atoms.border_contrast_low.borderColor,
|
||||
warning: t.atoms.border_contrast_low.borderColor,
|
||||
error: t.atoms.border_contrast_low.borderColor,
|
||||
}[type]
|
||||
return (
|
||||
<Context.Provider value={{type}}>
|
||||
<View
|
||||
style={[
|
||||
gtMobile ? a.p_md : a.p_sm,
|
||||
a.rounded_sm,
|
||||
a.border,
|
||||
t.atoms.bg_contrast_25,
|
||||
{borderColor},
|
||||
style,
|
||||
]}>
|
||||
{children}
|
||||
</View>
|
||||
</Context.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function Admonition({
|
||||
children,
|
||||
type,
|
||||
style,
|
||||
}: {
|
||||
children: TextProps['children']
|
||||
type?: Context['type']
|
||||
style?: StyleProp<ViewStyle>
|
||||
}) {
|
||||
return (
|
||||
<Outer type={type} style={style}>
|
||||
<Row>
|
||||
<Icon />
|
||||
<Text>{children}</Text>
|
||||
</Row>
|
||||
</Outer>
|
||||
)
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from 'react-native'
|
||||
import {
|
||||
KeyboardAwareScrollView,
|
||||
useKeyboardController,
|
||||
useKeyboardHandler,
|
||||
} from 'react-native-keyboard-controller'
|
||||
import {runOnJS} from 'react-native-reanimated'
|
||||
@@ -189,7 +190,21 @@ export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>(
|
||||
function ScrollableInner({children, style, ...props}, ref) {
|
||||
const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext()
|
||||
const insets = useSafeAreaInsets()
|
||||
const {setEnabled} = useKeyboardController()
|
||||
|
||||
const [keyboardHeight, setKeyboardHeight] = React.useState(0)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isIOS) {
|
||||
return
|
||||
}
|
||||
|
||||
setEnabled(true)
|
||||
return () => {
|
||||
setEnabled(false)
|
||||
}
|
||||
})
|
||||
|
||||
useKeyboardHandler({
|
||||
onEnd: e => {
|
||||
'worklet'
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from 'react'
|
||||
import {View, ViewStyle} from 'react-native'
|
||||
import {StyleProp} from 'react-native'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
|
||||
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.
|
||||
//
|
||||
// In a future PR I will add a unified header component to this file and
|
||||
// things like a preconfigured scrollview.
|
||||
|
||||
/**
|
||||
* 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,
|
||||
style,
|
||||
...props
|
||||
}: React.ComponentProps<typeof View> & {
|
||||
disableTopPadding?: boolean
|
||||
style?: StyleProp<ViewStyle>
|
||||
}): React.ReactNode => {
|
||||
const {top} = useSafeAreaInsets()
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
{paddingTop: disableTopPadding ? 0 : top},
|
||||
a.util_screen_outer,
|
||||
style,
|
||||
]}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Screen = React.memo(Screen)
|
||||
export {Screen}
|
||||
+20
-1
@@ -10,6 +10,7 @@ import {StackActions, useLinkProps} from '@react-navigation/native'
|
||||
|
||||
import {BSKY_DOWNLOAD_URL} from '#/lib/constants'
|
||||
import {useNavigationDeduped} from '#/lib/hooks/useNavigationDeduped'
|
||||
import {useOpenLink} from '#/lib/hooks/useOpenLink'
|
||||
import {AllNavigatorParams} from '#/lib/routes/types'
|
||||
import {shareUrl} from '#/lib/sharing'
|
||||
import {
|
||||
@@ -21,7 +22,6 @@ import {
|
||||
import {isNative, isWeb} from '#/platform/detection'
|
||||
import {shouldClickOpenNewTab} from '#/platform/urls'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useOpenLink} from '#/state/preferences/in-app-browser'
|
||||
import {atoms as a, flatten, TextStyleProp, useTheme, web} from '#/alf'
|
||||
import {Button, ButtonProps} from '#/components/Button'
|
||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||
@@ -329,6 +329,25 @@ export function InlineLinkText({
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to create a static `onPress` handler for a `Link` that would otherwise link to a URI
|
||||
*
|
||||
* Example:
|
||||
* `<Link {...createStaticClick(e => {...})} />`
|
||||
*/
|
||||
export function createStaticClick(
|
||||
onPressHandler: Exclude<BaseLinkProps['onPress'], undefined>,
|
||||
): Pick<BaseLinkProps, 'to' | 'onPress'> {
|
||||
return {
|
||||
to: '#',
|
||||
onPress(e: GestureResponderEvent) {
|
||||
e.preventDefault()
|
||||
onPressHandler(e)
|
||||
return false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Pressable that uses useLink to handle navigation. It is unstyled, so can be used in cases where the Button styles
|
||||
* in Link are not desired.
|
||||
|
||||
@@ -4,7 +4,6 @@ import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {makeSearchLink} from '#/lib/routes/links'
|
||||
import {NavigationProp} from '#/lib/routes/types'
|
||||
import {isInvalidHandle} from '#/lib/strings/handles'
|
||||
import {
|
||||
@@ -19,7 +18,7 @@ import {Divider} from '#/components/Divider'
|
||||
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2'
|
||||
import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute'
|
||||
import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person'
|
||||
import {Link} from '#/components/Link'
|
||||
import {createStaticClick, Link} from '#/components/Link'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
@@ -101,19 +100,14 @@ export function TagMenu({
|
||||
t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
<Link
|
||||
label={_(msg`Search for all posts with tag ${displayTag}`)}
|
||||
to={makeSearchLink({query: displayTag})}
|
||||
onPress={e => {
|
||||
e.preventDefault()
|
||||
|
||||
label={_(msg`View all posts with tag ${displayTag}`)}
|
||||
{...createStaticClick(() => {
|
||||
control.close(() => {
|
||||
navigation.push('Hashtag', {
|
||||
tag: encodeURIComponent(tag),
|
||||
})
|
||||
})
|
||||
|
||||
return false
|
||||
}}>
|
||||
})}>
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
@@ -152,24 +146,16 @@ export function TagMenu({
|
||||
|
||||
<Link
|
||||
label={_(
|
||||
msg`Search for all posts by @${authorHandle} with tag ${displayTag}`,
|
||||
msg`View all posts by @${authorHandle} with tag ${displayTag}`,
|
||||
)}
|
||||
to={makeSearchLink({
|
||||
query: displayTag,
|
||||
from: authorHandle,
|
||||
})}
|
||||
onPress={e => {
|
||||
e.preventDefault()
|
||||
|
||||
{...createStaticClick(() => {
|
||||
control.close(() => {
|
||||
navigation.push('Hashtag', {
|
||||
tag: encodeURIComponent(tag),
|
||||
author: authorHandle,
|
||||
})
|
||||
})
|
||||
|
||||
return false
|
||||
}}>
|
||||
})}>
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
|
||||
@@ -5,12 +5,12 @@ import {ChatBskyConvoDefs, RichText} from '@atproto/api'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useOpenLink} from '#/lib/hooks/useOpenLink'
|
||||
import {richTextToString} from '#/lib/strings/rich-text-helpers'
|
||||
import {getTranslatorLink} from '#/locale/helpers'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useConvoActive} from '#/state/messages/convo'
|
||||
import {useLanguagePrefs} from '#/state/preferences'
|
||||
import {useOpenLink} from '#/state/preferences/in-app-browser'
|
||||
import {useSession} from '#/state/session'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
|
||||
+10
-9
@@ -11,6 +11,7 @@ import {
|
||||
ComAtprotoRepoStrongRef,
|
||||
RichText,
|
||||
} from '@atproto/api'
|
||||
import {t} from '@lingui/macro'
|
||||
import {QueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {isNetworkError} from '#/lib/strings/errors'
|
||||
@@ -52,7 +53,7 @@ export async function post(
|
||||
{cleanNewlines: true},
|
||||
)
|
||||
|
||||
opts.onStateChange?.('Processing...')
|
||||
opts.onStateChange?.(t`Processing...`)
|
||||
|
||||
await rt.detectFacets(agent)
|
||||
|
||||
@@ -102,7 +103,7 @@ export async function post(
|
||||
|
||||
let res
|
||||
try {
|
||||
opts.onStateChange?.('Posting...')
|
||||
opts.onStateChange?.(t`Posting...`)
|
||||
res = await agent.post({
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
@@ -117,7 +118,7 @@ export async function post(
|
||||
})
|
||||
if (isNetworkError(e)) {
|
||||
throw new Error(
|
||||
'Post failed to upload. Please check your Internet connection and try again.',
|
||||
t`Post failed to upload. Please check your Internet connection and try again.`,
|
||||
)
|
||||
} else {
|
||||
throw e
|
||||
@@ -141,7 +142,7 @@ export async function post(
|
||||
safeMessage: e.message,
|
||||
})
|
||||
throw new Error(
|
||||
'Failed to save post interaction settings. Your post was created but users may be able to interact with it.',
|
||||
t`Failed to save post interaction settings. Your post was created but users may be able to interact with it.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -166,7 +167,7 @@ export async function post(
|
||||
safeMessage: e.message,
|
||||
})
|
||||
throw new Error(
|
||||
'Failed to save post interaction settings. Your post was created but users may be able to interact with it.',
|
||||
t`Failed to save post interaction settings. Your post was created but users may be able to interact with it.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -248,7 +249,7 @@ async function resolveMedia(
|
||||
logger.debug(`Uploading images`, {
|
||||
count: imagesDraft.length,
|
||||
})
|
||||
onStateChange?.(`Uploading images...`)
|
||||
onStateChange?.(t`Uploading images...`)
|
||||
const images: AppBskyEmbedImages.Image[] = await Promise.all(
|
||||
imagesDraft.map(async (image, i) => {
|
||||
logger.debug(`Compressing image #${i}`)
|
||||
@@ -302,7 +303,7 @@ async function resolveMedia(
|
||||
)
|
||||
let blob: BlobRef | undefined
|
||||
if (resolvedGif.thumb) {
|
||||
onStateChange?.('Uploading link thumbnail...')
|
||||
onStateChange?.(t`Uploading link thumbnail...`)
|
||||
const {path, mime} = resolvedGif.thumb.source
|
||||
const response = await uploadBlob(agent, path, mime)
|
||||
blob = response.data.blob
|
||||
@@ -326,7 +327,7 @@ async function resolveMedia(
|
||||
if (resolvedLink.type === 'external') {
|
||||
let blob: BlobRef | undefined
|
||||
if (resolvedLink.thumb) {
|
||||
onStateChange?.('Uploading link thumbnail...')
|
||||
onStateChange?.(t`Uploading link thumbnail...`)
|
||||
const {path, mime} = resolvedLink.thumb.source
|
||||
const response = await uploadBlob(agent, path, mime)
|
||||
blob = response.data.blob
|
||||
@@ -352,7 +353,7 @@ async function resolveRecord(
|
||||
): Promise<ComAtprotoRepoStrongRef.Main> {
|
||||
const resolvedLink = await fetchResolveLinkQuery(queryClient, agent, uri)
|
||||
if (resolvedLink.type !== 'record') {
|
||||
throw Error('Expected uri to resolve to a record')
|
||||
throw Error(t`Expected uri to resolve to a record`)
|
||||
}
|
||||
return resolvedLink.record
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ type IntentType = 'compose' | 'verify-email'
|
||||
|
||||
const VALID_IMAGE_REGEX = /^[\w.:\-_/]+\|\d+(\.\d+)?\|\d+(\.\d+)?$/
|
||||
|
||||
// This needs to stay outside of react to persist between account switches
|
||||
let previousIntentUrl = ''
|
||||
|
||||
export function useIntentHandler() {
|
||||
const incomingUrl = Linking.useURL()
|
||||
const composeIntent = useComposeIntent()
|
||||
@@ -68,7 +71,13 @@ export function useIntentHandler() {
|
||||
}
|
||||
}
|
||||
|
||||
if (incomingUrl) handleIncomingURL(incomingUrl)
|
||||
if (incomingUrl) {
|
||||
if (previousIntentUrl === incomingUrl) {
|
||||
return
|
||||
}
|
||||
handleIncomingURL(incomingUrl)
|
||||
previousIntentUrl = incomingUrl
|
||||
}
|
||||
}, [incomingUrl, composeIntent, verifyEmailIntent])
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import {useCallback} from 'react'
|
||||
import {Linking} from 'react-native'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
|
||||
import {
|
||||
createBskyAppAbsoluteUrl,
|
||||
isBskyRSSUrl,
|
||||
isRelativeUrl,
|
||||
} from '#/lib/strings/url-helpers'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useInAppBrowser} from '#/state/preferences/in-app-browser'
|
||||
import {useTheme} from '#/alf'
|
||||
import {useSheetWrapper} from '#/components/Dialog/sheet-wrapper'
|
||||
|
||||
export function useOpenLink() {
|
||||
const {openModal} = useModalControls()
|
||||
const enabled = useInAppBrowser()
|
||||
const t = useTheme()
|
||||
const sheetWrapper = useSheetWrapper()
|
||||
|
||||
const openLink = useCallback(
|
||||
async (url: string, override?: boolean) => {
|
||||
if (isBskyRSSUrl(url) && isRelativeUrl(url)) {
|
||||
url = createBskyAppAbsoluteUrl(url)
|
||||
}
|
||||
|
||||
if (isNative && !url.startsWith('mailto:')) {
|
||||
if (override === undefined && enabled === undefined) {
|
||||
openModal({
|
||||
name: 'in-app-browser-consent',
|
||||
href: url,
|
||||
})
|
||||
return
|
||||
} else if (override ?? enabled) {
|
||||
await sheetWrapper(
|
||||
WebBrowser.openBrowserAsync(url, {
|
||||
presentationStyle:
|
||||
WebBrowser.WebBrowserPresentationStyle.PAGE_SHEET,
|
||||
toolbarColor: t.atoms.bg.backgroundColor,
|
||||
controlsColor: t.palette.primary_500,
|
||||
createTask: false,
|
||||
}),
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
Linking.openURL(url)
|
||||
},
|
||||
[enabled, openModal, t, sheetWrapper],
|
||||
)
|
||||
|
||||
return openLink
|
||||
}
|
||||
@@ -3,8 +3,10 @@ import {
|
||||
launchImageLibraryAsync,
|
||||
MediaTypeOptions,
|
||||
} from 'expo-image-picker'
|
||||
// TODO: replace global i18n instance with one returned from useLingui -sfn
|
||||
import {t} from '@lingui/macro'
|
||||
|
||||
import * as Toast from 'view/com/util/Toast'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
import {getDataUriSize} from './util'
|
||||
|
||||
export async function openPicker(opts?: ImagePickerOptions) {
|
||||
@@ -17,14 +19,14 @@ export async function openPicker(opts?: ImagePickerOptions) {
|
||||
})
|
||||
|
||||
if (response.assets && response.assets.length > 4) {
|
||||
Toast.show('You may only select up to 4 images', 'exclamation-circle')
|
||||
Toast.show(t`You may only select up to 4 images`, 'exclamation-circle')
|
||||
}
|
||||
|
||||
return (response.assets ?? [])
|
||||
.slice(0, 4)
|
||||
.filter(asset => {
|
||||
if (asset.mimeType?.startsWith('image/')) return true
|
||||
Toast.show('Only image files are supported', 'exclamation-circle')
|
||||
Toast.show(t`Only image files are supported`, 'exclamation-circle')
|
||||
return false
|
||||
})
|
||||
.map(image => ({
|
||||
|
||||
+4
-2
@@ -1,8 +1,10 @@
|
||||
import {Share} from 'react-native'
|
||||
// import * as Sharing from 'expo-sharing'
|
||||
import {setStringAsync} from 'expo-clipboard'
|
||||
// TODO: replace global i18n instance with one returned from useLingui -sfn
|
||||
import {t} from '@lingui/macro'
|
||||
|
||||
import {isAndroid, isIOS} from 'platform/detection'
|
||||
import {isAndroid, isIOS} from '#/platform/detection'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
|
||||
/**
|
||||
@@ -20,6 +22,6 @@ export async function shareUrl(url: string) {
|
||||
// React Native Share is not supported by web. Web Share API
|
||||
// has increasing but not full support, so default to clipboard
|
||||
setStringAsync(url)
|
||||
Toast.show('Copied to clipboard', 'clipboard-check')
|
||||
Toast.show(t`Copied to clipboard`, 'clipboard-check')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {ModerationUI} from '@atproto/api'
|
||||
const CHECK_MARKS_RE = /[\u2705\u2713\u2714\u2611]/gu
|
||||
const CONTROL_CHARS_RE =
|
||||
/[\u0000-\u001F\u007F-\u009F\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/g
|
||||
const MULTIPLE_SPACES_RE = /[\s][\s]+/g
|
||||
const MULTIPLE_SPACES_RE = /[\s][\s\u200B]+/g
|
||||
|
||||
export function sanitizeDisplayName(
|
||||
str: string,
|
||||
|
||||
+435
-375
File diff suppressed because it is too large
Load Diff
+393
-333
File diff suppressed because it is too large
Load Diff
+393
-333
File diff suppressed because it is too large
Load Diff
+393
-333
File diff suppressed because it is too large
Load Diff
+393
-333
File diff suppressed because it is too large
Load Diff
+393
-333
File diff suppressed because it is too large
Load Diff
+393
-333
File diff suppressed because it is too large
Load Diff
+393
-333
File diff suppressed because it is too large
Load Diff
+393
-333
File diff suppressed because it is too large
Load Diff
+393
-333
File diff suppressed because it is too large
Load Diff
+392
-340
File diff suppressed because it is too large
Load Diff
+393
-333
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+393
-333
File diff suppressed because it is too large
Load Diff
+393
-333
File diff suppressed because it is too large
Load Diff
+393
-333
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,10 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {ScrollView} from 'view/com/util/Views'
|
||||
import {ScrollView} from '#/view/com/util/Views'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {SharedPrefs} from '../../../modules/expo-bluesky-swiss-army'
|
||||
|
||||
@@ -11,6 +12,7 @@ export function SharedPreferencesTesterScreen() {
|
||||
const [currentTestOutput, setCurrentTestOutput] = React.useState<string>('')
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<ScrollView contentContainerStyle={{backgroundColor: 'red'}}>
|
||||
<View style={[a.flex_1]}>
|
||||
<View>
|
||||
@@ -109,5 +111,6 @@ export function SharedPreferencesTesterScreen() {
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
+16
-15
@@ -6,23 +6,24 @@ import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||
|
||||
import {HITSLOP_10} from 'lib/constants'
|
||||
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
|
||||
import {CommonNavigatorParams} from 'lib/routes/types'
|
||||
import {shareUrl} from 'lib/sharing'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
import {sanitizeHandle} from 'lib/strings/handles'
|
||||
import {enforceLen} from 'lib/strings/helpers'
|
||||
import {isNative, isWeb} from 'platform/detection'
|
||||
import {useSearchPostsQuery} from 'state/queries/search-posts'
|
||||
import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from 'state/shell'
|
||||
import {HITSLOP_10} from '#/lib/constants'
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
import {CommonNavigatorParams} from '#/lib/routes/types'
|
||||
import {shareUrl} from '#/lib/sharing'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {enforceLen} from '#/lib/strings/helpers'
|
||||
import {isNative, isWeb} from '#/platform/detection'
|
||||
import {useSearchPostsQuery} from '#/state/queries/search-posts'
|
||||
import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
|
||||
import {Pager} from '#/view/com/pager/Pager'
|
||||
import {TabBar} from '#/view/com/pager/TabBar'
|
||||
import {Post} from '#/view/com/post/Post'
|
||||
import {List} from '#/view/com/util/List'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import {Post} from 'view/com/post/Post'
|
||||
import {List} from 'view/com/util/List'
|
||||
import {ViewHeader} from 'view/com/util/ViewHeader'
|
||||
import {ArrowOutOfBox_Stroke2_Corner0_Rounded} from '#/components/icons/ArrowOutOfBox'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||
|
||||
const renderItem = ({item}: ListRenderItemInfo<PostView>) => {
|
||||
@@ -108,7 +109,7 @@ export default function HashtagScreen({
|
||||
}, [_, fullTag, author, activeTab])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Layout.Screen>
|
||||
<CenteredView sideBorders={true}>
|
||||
<ViewHeader
|
||||
showOnDesktop
|
||||
@@ -155,7 +156,7 @@ export default function HashtagScreen({
|
||||
<View key={i}>{section.component}</View>
|
||||
))}
|
||||
</Pager>
|
||||
</>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/ico
|
||||
import {Message_Stroke2_Corner0_Rounded as Message} from '#/components/icons/Message'
|
||||
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
|
||||
import {SettingsSliderVertical_Stroke2_Corner0_Rounded as SettingsSlider} from '#/components/icons/SettingsSlider'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Link} from '#/components/Link'
|
||||
import {ListFooter} from '#/components/Lists'
|
||||
import {Loader} from '#/components/Loader'
|
||||
@@ -149,7 +150,7 @@ export function MessagesScreen({navigation, route}: Props) {
|
||||
|
||||
if (conversations.length < 1) {
|
||||
return (
|
||||
<View style={a.flex_1}>
|
||||
<Layout.Screen>
|
||||
<CenteredView sideBorders={gtMobile} style={[a.h_full_vh]}>
|
||||
{gtMobile ? (
|
||||
<DesktopHeader
|
||||
@@ -231,12 +232,12 @@ export function MessagesScreen({navigation, route}: Props) {
|
||||
{!isLoading && !isError && (
|
||||
<NewChat onNewChat={onNewChat} control={newChatControl} />
|
||||
)}
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={a.flex_1}>
|
||||
<Layout.Screen testID="messagesScreen">
|
||||
{!gtMobile && (
|
||||
<ViewHeader
|
||||
title={_(msg`Messages`)}
|
||||
@@ -276,7 +277,7 @@ export function MessagesScreen({navigation, route}: Props) {
|
||||
// @ts-ignore our .web version only -sfn
|
||||
desktopFixedHeight
|
||||
/>
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,11 @@ import {useProfileQuery} from '#/state/queries/profile'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import {MessagesList} from '#/screens/Messages/components/MessagesList'
|
||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
|
||||
import {MessagesListBlockedFooter} from '#/components/dms/MessagesListBlockedFooter'
|
||||
import {MessagesListHeader} from '#/components/dms/MessagesListHeader'
|
||||
import {Error} from '#/components/Error'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Loader} from '#/components/Loader'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
@@ -64,9 +65,11 @@ export function MessagesConversationScreen({route}: Props) {
|
||||
)
|
||||
|
||||
return (
|
||||
<Layout.Screen testID="convoScreen" style={web([{minHeight: 0}, a.flex_1])}>
|
||||
<ConvoProvider key={convoId} convoId={convoId}>
|
||||
<Inner />
|
||||
</ConvoProvider>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -100,7 +103,7 @@ function Inner() {
|
||||
|
||||
if (convoState.status === ConvoStatus.Error) {
|
||||
return (
|
||||
<CenteredView style={a.flex_1} sideBorders>
|
||||
<CenteredView style={[a.flex_1]} sideBorders>
|
||||
<MessagesListHeader />
|
||||
<Error
|
||||
title={_(msg`Something went wrong`)}
|
||||
|
||||
@@ -12,9 +12,11 @@ import {useSession} from '#/state/session'
|
||||
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, useTheme} from '#/alf'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {Divider} from '#/components/Divider'
|
||||
import * as Toggle from '#/components/forms/Toggle'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {useBackgroundNotificationPreferences} from '../../../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
|
||||
|
||||
@@ -23,7 +25,6 @@ type AllowIncoming = 'all' | 'none' | 'following'
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'MessagesSettings'>
|
||||
export function MessagesSettingsScreen({}: Props) {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
const {currentAccount} = useSession()
|
||||
const {data: profile} = useProfileQuery({
|
||||
did: currentAccount!.did,
|
||||
@@ -55,6 +56,7 @@ export function MessagesSettingsScreen({}: Props) {
|
||||
)
|
||||
|
||||
return (
|
||||
<Layout.Screen testID="messagesSettingsScreen">
|
||||
<ScrollView stickyHeaderIndices={[0]}>
|
||||
<ViewHeader title={_(msg`Chat Settings`)} showOnDesktop showBorder />
|
||||
<View style={[a.p_lg, a.gap_md]}>
|
||||
@@ -99,21 +101,12 @@ export function MessagesSettingsScreen({}: Props) {
|
||||
</Toggle.Item>
|
||||
</View>
|
||||
</Toggle.Group>
|
||||
<View
|
||||
style={[
|
||||
a.mt_sm,
|
||||
a.px_xl,
|
||||
a.py_lg,
|
||||
a.rounded_md,
|
||||
t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
<Text style={[t.atoms.text_contrast_high, a.leading_snug]}>
|
||||
<Admonition type="tip">
|
||||
<Trans>
|
||||
You can continue ongoing conversations regardless of which setting
|
||||
you choose.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
</Admonition>
|
||||
{isNative && (
|
||||
<>
|
||||
<Divider style={a.my_md} />
|
||||
@@ -150,5 +143,6 @@ export function MessagesSettingsScreen({}: Props) {
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import {Filter_Stroke2_Corner0_Rounded as Filter} from '#/components/icons/Filte
|
||||
import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group'
|
||||
import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person'
|
||||
import * as LabelingService from '#/components/LabelingServiceCard'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {InlineLinkText, Link} from '#/components/Link'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {GlobalLabelPreference} from '#/components/moderation/LabelPreference'
|
||||
@@ -94,6 +95,7 @@ export function ModerationScreen(
|
||||
const error = preferencesError
|
||||
|
||||
return (
|
||||
<Layout.Screen testID="moderationScreen">
|
||||
<CenteredView
|
||||
testID="moderationScreen"
|
||||
style={[
|
||||
@@ -119,6 +121,7 @@ export function ModerationScreen(
|
||||
<ModerationScreenInner preferences={preferences} />
|
||||
)}
|
||||
</CenteredView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@ import {useFocusEffect} from '@react-navigation/native'
|
||||
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {makeRecordUri} from '#/lib/strings/url-helpers'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {PostLikedBy as PostLikedByComponent} from '#/view/com/post-thread/PostLikedBy'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {CenteredView} from 'view/com/util/Views'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ListHeaderDesktop} from '#/components/Lists'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostLikedBy'>
|
||||
@@ -27,10 +27,12 @@ export const PostLikedByScreen = ({route}: Props) => {
|
||||
)
|
||||
|
||||
return (
|
||||
<CenteredView style={a.util_screen_outer} sideBorders={true}>
|
||||
<Layout.Screen>
|
||||
<CenteredView sideBorders={true}>
|
||||
<ListHeaderDesktop title={_(msg`Liked By`)} />
|
||||
<ViewHeader title={_(msg`Liked By`)} showBorder={!isWeb} />
|
||||
<PostLikedByComponent uri={uri} />
|
||||
</CenteredView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ import {useFocusEffect} from '@react-navigation/native'
|
||||
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {makeRecordUri} from '#/lib/strings/url-helpers'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {PostQuotes as PostQuotesComponent} from '#/view/com/post-thread/PostQuotes'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {CenteredView} from 'view/com/util/Views'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ListHeaderDesktop} from '#/components/Lists'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostQuotes'>
|
||||
@@ -27,10 +27,12 @@ export const PostQuotesScreen = ({route}: Props) => {
|
||||
)
|
||||
|
||||
return (
|
||||
<CenteredView style={a.util_screen_outer} sideBorders={true}>
|
||||
<Layout.Screen>
|
||||
<CenteredView sideBorders={true}>
|
||||
<ListHeaderDesktop title={_(msg`Quotes`)} />
|
||||
<ViewHeader title={_(msg`Quotes`)} showBorder={!isWeb} />
|
||||
<PostQuotesComponent uri={uri} />
|
||||
</CenteredView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ import {useFocusEffect} from '@react-navigation/native'
|
||||
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {makeRecordUri} from '#/lib/strings/url-helpers'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {PostRepostedBy as PostRepostedByComponent} from '#/view/com/post-thread/PostRepostedBy'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {CenteredView} from 'view/com/util/Views'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ListHeaderDesktop} from '#/components/Lists'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostRepostedBy'>
|
||||
@@ -27,10 +27,12 @@ export const PostRepostedByScreen = ({route}: Props) => {
|
||||
)
|
||||
|
||||
return (
|
||||
<CenteredView style={a.util_screen_outer} sideBorders={true}>
|
||||
<Layout.Screen>
|
||||
<CenteredView sideBorders={true}>
|
||||
<ListHeaderDesktop title={_(msg`Reposted By`)} />
|
||||
<ViewHeader title={_(msg`Reposted By`)} showBorder={!isWeb} />
|
||||
<PostRepostedByComponent uri={uri} />
|
||||
</CenteredView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -36,8 +36,8 @@ export function GrowableBanner({
|
||||
if (!pagerContext || !isIOS) {
|
||||
return (
|
||||
<View style={[a.w_full, a.h_full]}>
|
||||
{backButton}
|
||||
{children}
|
||||
{backButton}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ let ProfileHeaderShell = ({
|
||||
return (
|
||||
<View style={t.atoms.bg} pointerEvents={isIOS ? 'auto' : 'box-none'}>
|
||||
<View
|
||||
pointerEvents={isIOS ? 'auto' : 'none'}
|
||||
pointerEvents={isIOS ? 'auto' : 'box-none'}
|
||||
style={[a.relative, {height: 150}]}>
|
||||
<GrowableBanner
|
||||
backButton={
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {AppBskyActorDefs} from '@atproto/api'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {useProfileKnownFollowersQuery} from '#/state/queries/known-followers'
|
||||
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
||||
import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
|
||||
import {List} from '#/view/com/util/List'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {
|
||||
ListFooter,
|
||||
ListHeaderDesktop,
|
||||
@@ -92,6 +92,7 @@ export const ProfileKnownFollowersScreen = ({route}: Props) => {
|
||||
|
||||
if (followers.length < 1) {
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<ListMaybePlaceholder
|
||||
isLoading={isDidLoading || isFollowersLoading}
|
||||
isError={isError}
|
||||
@@ -100,11 +101,12 @@ export const ProfileKnownFollowersScreen = ({route}: Props) => {
|
||||
errorMessage={cleanError(resolveError || error)}
|
||||
onRetry={isError ? refetch : undefined}
|
||||
/>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Layout.Screen>
|
||||
<ViewHeader title={_(msg`Followers you know`)} />
|
||||
<List
|
||||
data={followers}
|
||||
@@ -129,6 +131,6 @@ export const ProfileKnownFollowersScreen = ({route}: Props) => {
|
||||
initialNumToRender={initialNumToRender}
|
||||
windowSize={11}
|
||||
/>
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
@@ -8,6 +7,7 @@ import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {makeRecordUri} from '#/lib/strings/url-helpers'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {LikedByList} from '#/components/LikedByList'
|
||||
|
||||
export function ProfileLabelerLikedByScreen({
|
||||
@@ -25,9 +25,9 @@ export function ProfileLabelerLikedByScreen({
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Layout.Screen>
|
||||
<ViewHeader title={_(msg`Liked By`)} />
|
||||
<LikedByList uri={uri} />
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {s} from '#/lib/styles'
|
||||
import {useSetThemePrefs, useThemePrefs} from '#/state/shell'
|
||||
import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
|
||||
import {ScrollView} from '#/view/com/util/Views'
|
||||
@@ -21,6 +20,7 @@ import {Moon_Stroke2_Corner0_Rounded as MoonIcon} from '#/components/icons/Moon'
|
||||
import {Phone_Stroke2_Corner0_Rounded as PhoneIcon} from '#/components/icons/Phone'
|
||||
import {TextSize_Stroke2_Corner0_Rounded as TextSize} from '#/components/icons/TextSize'
|
||||
import {TitleCase_Stroke2_Corner0_Rounded as Aa} from '#/components/icons/TitleCase'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppearanceSettings'>
|
||||
@@ -76,7 +76,7 @@ export function AppearanceSettingsScreen({}: Props) {
|
||||
|
||||
return (
|
||||
<LayoutAnimationConfig skipExiting skipEntering>
|
||||
<View testID="preferencesThreadsScreen" style={s.hContentRegion}>
|
||||
<Layout.Screen testID="preferencesThreadsScreen">
|
||||
<ScrollView
|
||||
// @ts-ignore web only -prf
|
||||
dataSet={{'stable-gutters': 1}}
|
||||
@@ -180,7 +180,7 @@ export function AppearanceSettingsScreen({}: Props) {
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
</LayoutAnimationConfig>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/ico
|
||||
import {DotGrid_Stroke2_Corner0_Rounded as Ellipsis} from '#/components/icons/DotGrid'
|
||||
import {Pencil_Stroke2_Corner0_Rounded as Pencil} from '#/components/icons/Pencil'
|
||||
import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ListMaybePlaceholder} from '#/components/Lists'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import * as Menu from '#/components/Menu'
|
||||
@@ -76,7 +77,11 @@ type StarterPackScreenShortProps = NativeStackScreenProps<
|
||||
>
|
||||
|
||||
export function StarterPackScreen({route}: StarterPackScreeProps) {
|
||||
return <StarterPackScreenInner routeParams={route.params} />
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<StarterPackScreenInner routeParams={route.params} />
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
export function StarterPackScreenShort({route}: StarterPackScreenShortProps) {
|
||||
@@ -91,15 +96,21 @@ export function StarterPackScreenShort({route}: StarterPackScreenShortProps) {
|
||||
|
||||
if (isLoading || isError || !resolvedStarterPack) {
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<ListMaybePlaceholder
|
||||
isLoading={isLoading}
|
||||
isError={isError}
|
||||
errorMessage={_(msg`That starter pack could not be found.`)}
|
||||
emptyMessage={_(msg`That starter pack could not be found.`)}
|
||||
/>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
return <StarterPackScreenInner routeParams={resolvedStarterPack} />
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<StarterPackScreenInner routeParams={resolvedStarterPack} />
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
export function StarterPackScreenInner({
|
||||
|
||||
@@ -19,32 +19,32 @@ import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect, useNavigation} from '@react-navigation/native'
|
||||
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {HITSLOP_10, STARTER_PACK_MAX_SIZE} from 'lib/constants'
|
||||
import {createSanitizedDisplayName} from 'lib/moderation/create-sanitized-display-name'
|
||||
import {CommonNavigatorParams, NavigationProp} from 'lib/routes/types'
|
||||
import {logEvent} from 'lib/statsig/statsig'
|
||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||
import {sanitizeHandle} from 'lib/strings/handles'
|
||||
import {enforceLen} from 'lib/strings/helpers'
|
||||
import {HITSLOP_10, STARTER_PACK_MAX_SIZE} from '#/lib/constants'
|
||||
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
|
||||
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
|
||||
import {logEvent} from '#/lib/statsig/statsig'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {enforceLen} from '#/lib/strings/helpers'
|
||||
import {
|
||||
getStarterPackOgCard,
|
||||
parseStarterPackUri,
|
||||
} from 'lib/strings/starter-pack'
|
||||
import {isAndroid, isNative, isWeb} from 'platform/detection'
|
||||
import {useModerationOpts} from 'state/preferences/moderation-opts'
|
||||
import {useAllListMembersQuery} from 'state/queries/list-members'
|
||||
import {useProfileQuery} from 'state/queries/profile'
|
||||
} from '#/lib/strings/starter-pack'
|
||||
import {logger} from '#/logger'
|
||||
import {isAndroid, isNative, isWeb} from '#/platform/detection'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useAllListMembersQuery} from '#/state/queries/list-members'
|
||||
import {useProfileQuery} from '#/state/queries/profile'
|
||||
import {
|
||||
useCreateStarterPackMutation,
|
||||
useEditStarterPackMutation,
|
||||
useStarterPackQuery,
|
||||
} from 'state/queries/starter-packs'
|
||||
import {useSession} from 'state/session'
|
||||
import {useSetMinimalShellMode} from 'state/shell'
|
||||
} from '#/state/queries/starter-packs'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
import {UserAvatar} from 'view/com/util/UserAvatar'
|
||||
import {CenteredView} from 'view/com/util/Views'
|
||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import {useWizardState, WizardStep} from '#/screens/StarterPack/Wizard/State'
|
||||
import {StepDetails} from '#/screens/StarterPack/Wizard/StepDetails'
|
||||
import {StepFeeds} from '#/screens/StarterPack/Wizard/StepFeeds'
|
||||
@@ -52,6 +52,7 @@ import {StepProfiles} from '#/screens/StarterPack/Wizard/StepProfiles'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ListMaybePlaceholder} from '#/components/Lists'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {WizardEditListDialog} from '#/components/StarterPack/Wizard/WizardEditListDialog'
|
||||
@@ -97,6 +98,7 @@ export function Wizard({
|
||||
|
||||
if (!isReady) {
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<ListMaybePlaceholder
|
||||
isLoading={
|
||||
isLoadingStarterPack || isLoadingProfiles || isLoadingProfile
|
||||
@@ -104,18 +106,22 @@ export function Wizard({
|
||||
isError={isErrorStarterPack || isErrorProfiles || isErrorProfile}
|
||||
errorMessage={_(msg`That starter pack could not be found.`)}
|
||||
/>
|
||||
</Layout.Screen>
|
||||
)
|
||||
} else if (isEdit && starterPack?.creator.did !== currentAccount?.did) {
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<ListMaybePlaceholder
|
||||
isLoading={false}
|
||||
isError={true}
|
||||
errorMessage={_(msg`That starter pack could not be found.`)}
|
||||
/>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<Provider starterPack={starterPack} listItems={listItems}>
|
||||
<WizardInner
|
||||
currentStarterPack={starterPack}
|
||||
@@ -124,6 +130,7 @@ export function Wizard({
|
||||
moderationOpts={moderationOpts}
|
||||
/>
|
||||
</Provider>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import React from 'react'
|
||||
import {Linking} from 'react-native'
|
||||
import * as WebBrowser from 'expo-web-browser'
|
||||
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {
|
||||
createBskyAppAbsoluteUrl,
|
||||
isBskyRSSUrl,
|
||||
isRelativeUrl,
|
||||
} from '#/lib/strings/url-helpers'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import * as persisted from '#/state/persisted'
|
||||
import {useModalControls} from '../modals'
|
||||
|
||||
type StateContext = persisted.Schema['useInAppBrowser']
|
||||
type SetContext = (v: persisted.Schema['useInAppBrowser']) => void
|
||||
@@ -55,39 +45,3 @@ export function useInAppBrowser() {
|
||||
export function useSetInAppBrowser() {
|
||||
return React.useContext(setContext)
|
||||
}
|
||||
|
||||
export function useOpenLink() {
|
||||
const {openModal} = useModalControls()
|
||||
const enabled = useInAppBrowser()
|
||||
const pal = usePalette('default')
|
||||
|
||||
const openLink = React.useCallback(
|
||||
async (url: string, override?: boolean) => {
|
||||
if (isBskyRSSUrl(url) && isRelativeUrl(url)) {
|
||||
url = createBskyAppAbsoluteUrl(url)
|
||||
}
|
||||
|
||||
if (isNative && !url.startsWith('mailto:')) {
|
||||
if (override === undefined && enabled === undefined) {
|
||||
openModal({
|
||||
name: 'in-app-browser-consent',
|
||||
href: url,
|
||||
})
|
||||
return
|
||||
} else if (override ?? enabled) {
|
||||
await WebBrowser.openBrowserAsync(url, {
|
||||
presentationStyle:
|
||||
WebBrowser.WebBrowserPresentationStyle.FULL_SCREEN,
|
||||
toolbarColor: pal.colors.backgroundLight,
|
||||
createTask: false,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
Linking.openURL(url)
|
||||
},
|
||||
[enabled, openModal, pal.colors.backgroundLight],
|
||||
)
|
||||
|
||||
return openLink
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from 'react'
|
||||
import {useDialogStateContext} from '#/state/dialogs'
|
||||
import {useLightbox} from '#/state/lightbox'
|
||||
import {useModals} from '#/state/modals'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useIsDrawerOpen} from '#/state/shell/drawer-open'
|
||||
import {useComposerControls} from './'
|
||||
|
||||
@@ -43,8 +44,13 @@ export function useComposerKeyboardShortcut() {
|
||||
const {isModalActive} = useModals()
|
||||
const {activeLightbox} = useLightbox()
|
||||
const isDrawerOpen = useIsDrawerOpen()
|
||||
const {hasSession} = useSession()
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!hasSession) {
|
||||
return
|
||||
}
|
||||
|
||||
function handler(event: KeyboardEvent) {
|
||||
if (shouldIgnore(event)) return
|
||||
if (
|
||||
@@ -60,5 +66,12 @@ export function useComposerKeyboardShortcut() {
|
||||
}
|
||||
document.addEventListener('keydown', handler)
|
||||
return () => document.removeEventListener('keydown', handler)
|
||||
}, [openComposer, isModalActive, openDialogs, activeLightbox, isDrawerOpen])
|
||||
}, [
|
||||
openComposer,
|
||||
isModalActive,
|
||||
openDialogs,
|
||||
activeLightbox,
|
||||
isDrawerOpen,
|
||||
hasSession,
|
||||
])
|
||||
}
|
||||
|
||||
@@ -360,6 +360,7 @@ export const ComposePost = ({
|
||||
richtext.text.trim().length === 0 &&
|
||||
images.length === 0 &&
|
||||
!extLink &&
|
||||
!extGif &&
|
||||
!quote &&
|
||||
videoState.status === 'idle'
|
||||
) {
|
||||
@@ -451,6 +452,7 @@ export const ComposePost = ({
|
||||
agent,
|
||||
draft,
|
||||
extLink,
|
||||
extGif,
|
||||
images,
|
||||
graphemeLength,
|
||||
isAltTextRequiredAndMissing,
|
||||
|
||||
@@ -1,34 +1,26 @@
|
||||
import React from 'react'
|
||||
import {TouchableOpacity} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {View} from 'react-native'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {s} from 'lib/styles'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Button, ButtonIcon} from '#/components/Button'
|
||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
||||
|
||||
export function ExternalEmbedRemoveBtn({onRemove}: {onRemove: () => void}) {
|
||||
const {_} = useLingui()
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 10,
|
||||
right: 10,
|
||||
height: 36,
|
||||
width: 36,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.75)',
|
||||
borderRadius: 18,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: 1,
|
||||
}}
|
||||
<View style={[a.absolute, a.pt_sm, a.pr_sm, {top: 0, right: 0}]}>
|
||||
<Button
|
||||
label={_(msg`Remove attachment`)}
|
||||
onPress={onRemove}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Remove attachment`)}
|
||||
accessibilityHint={_(msg`Removes the attachment`)}
|
||||
onAccessibilityEscape={onRemove}>
|
||||
<FontAwesomeIcon size={18} icon="xmark" style={s.white} />
|
||||
</TouchableOpacity>
|
||||
size="small"
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
shape="round">
|
||||
<ButtonIcon icon={X} size="sm" />
|
||||
</Button>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import {isAndroid} from '#/platform/detection'
|
||||
import {useResolveGifQuery} from '#/state/queries/resolve-link'
|
||||
import {Gif} from '#/state/queries/tenor'
|
||||
import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper'
|
||||
import {atoms as a, native, useTheme} from '#/alf'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {DialogControlProps} from '#/components/Dialog'
|
||||
@@ -213,7 +213,7 @@ function AltTextInner({
|
||||
isPreferredAltText={true}
|
||||
params={params}
|
||||
hideAlt
|
||||
style={[native({maxHeight: 225})]}
|
||||
style={[{height: 225}]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -3,16 +3,14 @@ import {StyleSheet, View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useOpenLink} from '#/lib/hooks/useOpenLink'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {s} from '#/lib/styles'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {
|
||||
useOpenLink,
|
||||
useSetInAppBrowser,
|
||||
} from '#/state/preferences/in-app-browser'
|
||||
import {Button} from '../util/forms/Button'
|
||||
import {Text} from '../util/text/Text'
|
||||
import {ScrollView} from './util'
|
||||
import {useSetInAppBrowser} from '#/state/preferences/in-app-browser'
|
||||
import {ScrollView} from '#/view/com/modals/util'
|
||||
import {Button} from '#/view/com/util/forms/Button'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
|
||||
export const snapPoints = [350]
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useOpenLink} from '#/lib/hooks/useOpenLink'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {shareUrl} from '#/lib/sharing'
|
||||
@@ -11,7 +12,6 @@ import {isPossiblyAUrl, splitApexDomain} from '#/lib/strings/url-helpers'
|
||||
import {colors, s} from '#/lib/styles'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useOpenLink} from '#/state/preferences/in-app-browser'
|
||||
import {Button} from '#/view/com/util/forms/Button'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {ScrollView} from './util'
|
||||
|
||||
@@ -13,6 +13,7 @@ import {msg, Plural, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {MAX_POST_LINES} from '#/lib/constants'
|
||||
import {useOpenLink} from '#/lib/hooks/useOpenLink'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
@@ -22,7 +23,6 @@ import {niceDate} from '#/lib/strings/time'
|
||||
import {s} from '#/lib/styles'
|
||||
import {POST_TOMBSTONE, Shadow, usePostShadow} from '#/state/cache/post-shadow'
|
||||
import {useLanguagePrefs} from '#/state/preferences'
|
||||
import {useOpenLink} from '#/state/preferences/in-app-browser'
|
||||
import {ThreadPost} from '#/state/queries/post-thread'
|
||||
import {useSession} from '#/state/session'
|
||||
import {useComposerControls} from '#/state/shell/composer'
|
||||
|
||||
@@ -5,7 +5,6 @@ import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {createHitslop} from '#/lib/constants'
|
||||
import {NavigationProp} from '#/lib/routes/types'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
@@ -85,7 +84,12 @@ export function AviFollowButton({
|
||||
{!isFollowing && (
|
||||
<Button
|
||||
label={_(msg`Open ${name} profile shortcut menu`)}
|
||||
hitSlop={createHitslop(3)}
|
||||
hitSlop={{
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 5,
|
||||
bottom: 5,
|
||||
}}
|
||||
style={[
|
||||
a.rounded_full,
|
||||
a.absolute,
|
||||
|
||||
@@ -13,20 +13,20 @@ import {
|
||||
import {sanitizeUrl} from '@braintree/sanitize-url'
|
||||
import {StackActions, useLinkProps} from '@react-navigation/native'
|
||||
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useOpenLink} from '#/state/preferences/in-app-browser'
|
||||
import {
|
||||
DebouncedNavigationProp,
|
||||
useNavigationDeduped,
|
||||
} from 'lib/hooks/useNavigationDeduped'
|
||||
} from '#/lib/hooks/useNavigationDeduped'
|
||||
import {useOpenLink} from '#/lib/hooks/useOpenLink'
|
||||
import {
|
||||
convertBskyAppUrlIfNeeded,
|
||||
isExternalUrl,
|
||||
linkRequiresWarning,
|
||||
} from 'lib/strings/url-helpers'
|
||||
import {TypographyVariant} from 'lib/ThemeContext'
|
||||
import {isAndroid, isWeb} from 'platform/detection'
|
||||
import {WebAuxClickWrapper} from 'view/com/util/WebAuxClickWrapper'
|
||||
} from '#/lib/strings/url-helpers'
|
||||
import {TypographyVariant} from '#/lib/ThemeContext'
|
||||
import {isAndroid, isWeb} from '#/platform/detection'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {WebAuxClickWrapper} from '#/view/com/util/WebAuxClickWrapper'
|
||||
import {useTheme} from '#/alf'
|
||||
import {router} from '../../../routes'
|
||||
import {PressableWithHover} from './PressableWithHover'
|
||||
|
||||
@@ -18,6 +18,7 @@ import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {useOpenLink} from '#/lib/hooks/useOpenLink'
|
||||
import {getCurrentRoute} from '#/lib/routes/helpers'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
|
||||
@@ -33,7 +34,6 @@ import {Shadow} from '#/state/cache/post-shadow'
|
||||
import {useFeedFeedbackContext} from '#/state/feed-feedback'
|
||||
import {useLanguagePrefs} from '#/state/preferences'
|
||||
import {useHiddenPosts, useHiddenPostsApi} from '#/state/preferences'
|
||||
import {useOpenLink} from '#/state/preferences/in-app-browser'
|
||||
import {usePinnedPostMutation} from '#/state/queries/pinned-post'
|
||||
import {
|
||||
usePostDeleteMutation,
|
||||
@@ -268,8 +268,8 @@ let PostDropdownBtn = ({
|
||||
item: postUri,
|
||||
feedContext: postFeedContext,
|
||||
})
|
||||
Toast.show('Feedback sent!')
|
||||
}, [feedFeedback, postUri, postFeedContext])
|
||||
Toast.show(_(msg`Feedback sent!`))
|
||||
}, [feedFeedback, postUri, postFeedContext, _])
|
||||
|
||||
const onPressShowLess = React.useCallback(() => {
|
||||
feedFeedback.sendInteraction({
|
||||
@@ -277,8 +277,8 @@ let PostDropdownBtn = ({
|
||||
item: postUri,
|
||||
feedContext: postFeedContext,
|
||||
})
|
||||
Toast.show('Feedback sent!')
|
||||
}, [feedFeedback, postUri, postFeedContext])
|
||||
Toast.show(_(msg`Feedback sent!`))
|
||||
}, [feedFeedback, postUri, postFeedContext, _])
|
||||
|
||||
const onSelectChatToShareTo = React.useCallback(
|
||||
(conversation: string) => {
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
GestureResponderEvent,
|
||||
LayoutChangeEvent,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
} from 'react-native'
|
||||
import {Image, ImageLoadEventData} from 'expo-image'
|
||||
import {AppBskyEmbedExternal} from '@atproto/api'
|
||||
@@ -18,7 +17,6 @@ import {atoms as a, useTheme} from '#/alf'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
|
||||
import {Fill} from '#/components/Fill'
|
||||
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
|
||||
|
||||
export function ExternalGifEmbed({
|
||||
@@ -116,8 +114,7 @@ export function ExternalGifEmbed({
|
||||
<Pressable
|
||||
style={[
|
||||
{height: imageDims.height},
|
||||
styles.gifContainer,
|
||||
a.rounded_md,
|
||||
a.w_full,
|
||||
a.overflow_hidden,
|
||||
{
|
||||
borderBottomLeftRadius: 0,
|
||||
@@ -166,42 +163,7 @@ export function ExternalGifEmbed({
|
||||
)}
|
||||
</Fill>
|
||||
)}
|
||||
<MediaInsetBorder
|
||||
opaque
|
||||
style={[
|
||||
{
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Pressable>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
topRadius: {
|
||||
borderTopLeftRadius: 6,
|
||||
borderTopRightRadius: 6,
|
||||
},
|
||||
layer: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
overlayContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
overlayLayer: {
|
||||
zIndex: 2,
|
||||
},
|
||||
gifContainer: {
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -6,8 +6,6 @@ import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {parseAltFromGIFDescription} from '#/lib/gif-alt-text'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {shareUrl} from '#/lib/sharing'
|
||||
import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player'
|
||||
import {
|
||||
@@ -17,13 +15,14 @@ import {
|
||||
import {toNiceDomain} from '#/lib/strings/url-helpers'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {useExternalEmbedsPrefs} from '#/state/preferences'
|
||||
import {Link} from '#/view/com/util/Link'
|
||||
import {ExternalGifEmbed} from '#/view/com/util/post-embeds/ExternalGifEmbed'
|
||||
import {ExternalPlayer} from '#/view/com/util/post-embeds/ExternalPlayerEmbed'
|
||||
import {GifEmbed} from '#/view/com/util/post-embeds/GifEmbed'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||
import {Text} from '../text/Text'
|
||||
import {Divider} from '#/components/Divider'
|
||||
import {Earth_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
|
||||
import {Link} from '#/components/Link'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export const ExternalLinkEmbed = ({
|
||||
link,
|
||||
@@ -37,16 +36,13 @@ export const ExternalLinkEmbed = ({
|
||||
hideAlt?: boolean
|
||||
}) => {
|
||||
const {_} = useLingui()
|
||||
const pal = usePalette('default')
|
||||
const t = useTheme()
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const externalEmbedPrefs = useExternalEmbedsPrefs()
|
||||
|
||||
const niceUrl = toNiceDomain(link.uri)
|
||||
const starterPackParsed = parseStarterPackUri(link.uri)
|
||||
const imageUri = starterPackParsed
|
||||
? getStarterPackOgCard(starterPackParsed.name, starterPackParsed.rkey)
|
||||
: link.thumb
|
||||
|
||||
const embedPlayerParams = React.useMemo(() => {
|
||||
const params = parseEmbedPlayerFromUrl(link.uri)
|
||||
|
||||
@@ -54,10 +50,18 @@ export const ExternalLinkEmbed = ({
|
||||
return params
|
||||
}
|
||||
}, [link.uri, externalEmbedPrefs])
|
||||
const hasMedia = Boolean(imageUri || embedPlayerParams)
|
||||
|
||||
const onShareExternal = useCallback(() => {
|
||||
if (link.uri && isNative) {
|
||||
shareUrl(link.uri)
|
||||
}
|
||||
}, [link.uri])
|
||||
|
||||
if (embedPlayerParams?.source === 'tenor') {
|
||||
const parsedAlt = parseAltFromGIFDescription(link.description)
|
||||
return (
|
||||
<View style={style}>
|
||||
<GifEmbed
|
||||
params={embedPlayerParams}
|
||||
thumb={link.thumb}
|
||||
@@ -65,111 +69,112 @@ export const ExternalLinkEmbed = ({
|
||||
isPreferredAltText={parsedAlt.isPreferred}
|
||||
hideAlt={hideAlt}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[a.flex_col, a.rounded_md, a.w_full]}>
|
||||
<LinkWrapper link={link} onOpen={onOpen} style={style}>
|
||||
<Link
|
||||
label={link.title || _(msg`Open link to ${niceUrl}`)}
|
||||
to={link.uri}
|
||||
onPress={onOpen}
|
||||
onLongPress={onShareExternal}>
|
||||
{({hovered}) => (
|
||||
<View
|
||||
style={[
|
||||
a.transition_color,
|
||||
a.flex_col,
|
||||
a.rounded_md,
|
||||
a.overflow_hidden,
|
||||
a.w_full,
|
||||
a.border,
|
||||
style,
|
||||
hovered
|
||||
? t.atoms.border_contrast_high
|
||||
: t.atoms.border_contrast_low,
|
||||
]}>
|
||||
{imageUri && !embedPlayerParams ? (
|
||||
<View>
|
||||
<Image
|
||||
style={{
|
||||
aspectRatio: 1.91,
|
||||
borderTopRightRadius: a.rounded_md.borderRadius,
|
||||
borderTopLeftRadius: a.rounded_md.borderRadius,
|
||||
}}
|
||||
source={{uri: imageUri}}
|
||||
accessibilityIgnoresInvertColors
|
||||
accessibilityLabel={starterPackParsed ? link.title : undefined}
|
||||
accessibilityHint={
|
||||
starterPackParsed ? _(msg`Navigate to starter pack`) : undefined
|
||||
}
|
||||
/>
|
||||
<MediaInsetBorder
|
||||
opaque
|
||||
style={[
|
||||
{
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
) : undefined}
|
||||
|
||||
{embedPlayerParams?.isGif ? (
|
||||
<ExternalGifEmbed link={link} params={embedPlayerParams} />
|
||||
) : embedPlayerParams ? (
|
||||
<ExternalPlayer link={link} params={embedPlayerParams} />
|
||||
) : undefined}
|
||||
|
||||
<View
|
||||
style={[
|
||||
a.border_b,
|
||||
a.border_l,
|
||||
a.border_r,
|
||||
a.flex_1,
|
||||
a.py_sm,
|
||||
t.atoms.border_contrast_low,
|
||||
{
|
||||
borderBottomRightRadius: a.rounded_md.borderRadius,
|
||||
borderBottomLeftRadius: a.rounded_md.borderRadius,
|
||||
paddingHorizontal: isMobile ? 10 : 14,
|
||||
},
|
||||
!imageUri && !embedPlayerParams && [a.border, a.rounded_md],
|
||||
a.pt_sm,
|
||||
{gap: 3},
|
||||
hasMedia && a.border_t,
|
||||
hovered
|
||||
? t.atoms.border_contrast_high
|
||||
: t.atoms.border_contrast_low,
|
||||
]}>
|
||||
<Text
|
||||
type="sm"
|
||||
numberOfLines={1}
|
||||
style={[pal.textLight, {marginVertical: 2}]}>
|
||||
{toNiceDomain(link.uri)}
|
||||
</Text>
|
||||
|
||||
<View style={[{gap: 3}, a.pb_xs, a.px_md]}>
|
||||
{!embedPlayerParams?.isGif && !embedPlayerParams?.dimensions && (
|
||||
<Text emoji type="lg-bold" numberOfLines={3} style={[pal.text]}>
|
||||
<Text
|
||||
emoji
|
||||
numberOfLines={3}
|
||||
style={[a.text_md, a.font_bold, a.leading_snug]}>
|
||||
{link.title || link.uri}
|
||||
</Text>
|
||||
)}
|
||||
{link.description ? (
|
||||
<Text
|
||||
emoji
|
||||
type="md"
|
||||
numberOfLines={link.thumb ? 2 : 4}
|
||||
style={[pal.text, a.mt_xs]}>
|
||||
style={[a.text_sm, a.leading_snug]}>
|
||||
{link.description}
|
||||
</Text>
|
||||
) : undefined}
|
||||
</View>
|
||||
</LinkWrapper>
|
||||
<View style={[a.px_md]}>
|
||||
<Divider />
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.gap_2xs,
|
||||
a.pb_sm,
|
||||
{
|
||||
paddingTop: 6, // off menu
|
||||
},
|
||||
]}>
|
||||
<Globe
|
||||
size="xs"
|
||||
style={[
|
||||
a.transition_color,
|
||||
hovered
|
||||
? t.atoms.text_contrast_medium
|
||||
: t.atoms.text_contrast_low,
|
||||
]}
|
||||
/>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[
|
||||
a.transition_color,
|
||||
a.text_xs,
|
||||
a.leading_tight,
|
||||
hovered
|
||||
? t.atoms.text_contrast_high
|
||||
: t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
{toNiceDomain(link.uri)}
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function LinkWrapper({
|
||||
link,
|
||||
onOpen,
|
||||
style,
|
||||
children,
|
||||
}: {
|
||||
link: AppBskyEmbedExternal.ViewExternal
|
||||
onOpen?: () => void
|
||||
style?: StyleProp<ViewStyle>
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const onShareExternal = useCallback(() => {
|
||||
if (link.uri && isNative) {
|
||||
shareUrl(link.uri)
|
||||
}
|
||||
}, [link.uri])
|
||||
|
||||
return (
|
||||
<Link
|
||||
asAnchor
|
||||
anchorNoUnderline
|
||||
href={link.uri}
|
||||
style={[a.flex_1, a.rounded_sm, style]}
|
||||
onBeforePress={onOpen}
|
||||
onLongPress={onShareExternal}>
|
||||
{children}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ import {atoms as a, useTheme} from '#/alf'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent'
|
||||
import {Fill} from '#/components/Fill'
|
||||
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
|
||||
import {EventStopper} from '../EventStopper'
|
||||
|
||||
@@ -59,7 +58,7 @@ function PlaceholderOverlay({
|
||||
accessibilityLabel={_(msg`Play Video`)}
|
||||
accessibilityHint={_(msg`Play Video`)}
|
||||
onPress={onPress}
|
||||
style={[styles.overlayContainer, styles.topRadius]}>
|
||||
style={[styles.overlayContainer]}>
|
||||
{!isPlayerActive ? (
|
||||
<PlayButtonIcon />
|
||||
) : (
|
||||
@@ -108,16 +107,6 @@ function Player({
|
||||
style={styles.webview}
|
||||
setSupportMultipleWindows={false} // Prevent any redirects from opening a new window (ads)
|
||||
/>
|
||||
|
||||
<MediaInsetBorder
|
||||
opaque
|
||||
style={[
|
||||
{
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</EventStopper>
|
||||
)
|
||||
}
|
||||
@@ -227,66 +216,34 @@ export function ExternalPlayer({
|
||||
<Animated.View
|
||||
ref={viewRef}
|
||||
collapsable={false}
|
||||
style={[
|
||||
aspect,
|
||||
a.rounded_md,
|
||||
a.overflow_hidden,
|
||||
{
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
},
|
||||
]}>
|
||||
style={[aspect, a.overflow_hidden]}>
|
||||
{link.thumb && (!isPlayerActive || isLoading) ? (
|
||||
<>
|
||||
<Image
|
||||
style={[a.flex_1, styles.topRadius]}
|
||||
style={[a.flex_1]}
|
||||
source={{uri: link.thumb}}
|
||||
accessibilityIgnoresInvertColors
|
||||
/>
|
||||
<Fill
|
||||
style={[
|
||||
a.rounded_md,
|
||||
t.name === 'light' ? t.atoms.bg_contrast_975 : t.atoms.bg,
|
||||
{
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
opacity: 0.3,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<MediaInsetBorder
|
||||
opaque
|
||||
style={[
|
||||
{
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Fill
|
||||
style={[
|
||||
a.rounded_md,
|
||||
{
|
||||
backgroundColor:
|
||||
t.name === 'light' ? t.palette.contrast_975 : 'black',
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
opacity: 0.3,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<MediaInsetBorder
|
||||
opaque
|
||||
style={[
|
||||
{
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<PlaceholderOverlay
|
||||
isLoading={isLoading}
|
||||
isPlayerActive={isPlayerActive}
|
||||
@@ -303,10 +260,6 @@ export function ExternalPlayer({
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
topRadius: {
|
||||
borderTopLeftRadius: a.rounded_md.borderRadius,
|
||||
borderTopRightRadius: a.rounded_md.borderRadius,
|
||||
},
|
||||
overlayContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
@@ -319,8 +272,6 @@ const styles = StyleSheet.create({
|
||||
zIndex: 3,
|
||||
},
|
||||
webview: {
|
||||
borderTopRightRadius: a.rounded_md.borderRadius,
|
||||
borderTopLeftRadius: a.rounded_md.borderRadius,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
gifContainer: {
|
||||
|
||||
@@ -18,7 +18,6 @@ import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Fill} from '#/components/Fill'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
|
||||
@@ -51,7 +50,6 @@ function PlaybackControls({
|
||||
a.inset_0,
|
||||
a.w_full,
|
||||
a.h_full,
|
||||
a.rounded_md,
|
||||
{
|
||||
zIndex: 2,
|
||||
backgroundColor: !isLoaded
|
||||
@@ -114,12 +112,27 @@ export function GifEmbed({
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View style={[a.rounded_md, a.overflow_hidden, a.mt_sm, style]}>
|
||||
<View
|
||||
style={[
|
||||
a.rounded_md,
|
||||
a.overflow_hidden,
|
||||
a.border,
|
||||
t.atoms.border_contrast_low,
|
||||
{aspectRatio: params.dimensions!.width / params.dimensions!.height},
|
||||
style,
|
||||
]}>
|
||||
<View
|
||||
style={[
|
||||
a.absolute,
|
||||
/*
|
||||
* Aspect ratio was being clipped weirdly on web -esb
|
||||
*/
|
||||
{
|
||||
top: -2,
|
||||
bottom: -2,
|
||||
left: -2,
|
||||
right: -2,
|
||||
},
|
||||
]}>
|
||||
<PlaybackControls
|
||||
onPress={onPress}
|
||||
@@ -129,7 +142,7 @@ export function GifEmbed({
|
||||
<GifView
|
||||
source={params.playerUri}
|
||||
placeholderSource={thumb}
|
||||
style={[a.flex_1, a.rounded_md]}
|
||||
style={[a.flex_1]}
|
||||
autoplay={!autoplayDisabled}
|
||||
onPlayerStateChange={onPlayerStateChange}
|
||||
ref={playerRef}
|
||||
@@ -146,7 +159,6 @@ export function GifEmbed({
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<MediaInsetBorder />
|
||||
{!hideAlt && isPreferredAltText && <AltText text={altText} />}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -27,6 +27,7 @@ import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {ScrollView} from '#/view/com/util/Views'
|
||||
import {atoms as a} from '#/alf'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
CommonNavigatorParams,
|
||||
@@ -54,7 +55,7 @@ export function AccessibilitySettingsScreen({}: Props) {
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={s.hContentRegion} testID="accessibilitySettingsScreen">
|
||||
<Layout.Screen testID="accessibilitySettingsScreen">
|
||||
<SimpleViewHeader
|
||||
showBackButton={isTabletOrMobile}
|
||||
style={[
|
||||
@@ -128,7 +129,7 @@ export function AccessibilitySettingsScreen({}: Props) {
|
||||
</>
|
||||
)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -30,10 +30,19 @@ import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'AppPasswords'>
|
||||
export function AppPasswords({}: Props) {
|
||||
return (
|
||||
<Layout.Screen testID="AppPasswordsScreen">
|
||||
<AppPasswordsInner />
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
function AppPasswordsInner() {
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const setMinimalShellMode = useSetMinimalShellMode()
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
import {Text} from 'view/com/util/text/Text'
|
||||
import {TextLink} from 'view/com/util/Link'
|
||||
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
||||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
import {ScrollView} from 'view/com/util/Views'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {s} from 'lib/styles'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {s} from '#/lib/styles'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {TextLink} from '#/view/com/util/Link'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {ScrollView} from '#/view/com/util/Views'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
CommonNavigatorParams,
|
||||
@@ -28,7 +30,7 @@ export const CommunityGuidelinesScreen = (_props: Props) => {
|
||||
)
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Layout.Screen>
|
||||
<ViewHeader title={_(msg`Community Guidelines`)} />
|
||||
<ScrollView style={[s.hContentRegion, pal.view]}>
|
||||
<View style={[s.p20]}>
|
||||
@@ -45,6 +47,6 @@ export const CommunityGuidelinesScreen = (_props: Props) => {
|
||||
</View>
|
||||
<View style={s.footerSpacer} />
|
||||
</ScrollView>
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
import {Text} from 'view/com/util/text/Text'
|
||||
import {TextLink} from 'view/com/util/Link'
|
||||
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
||||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
import {ScrollView} from 'view/com/util/Views'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {s} from 'lib/styles'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {s} from '#/lib/styles'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {TextLink} from '#/view/com/util/Link'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {ScrollView} from '#/view/com/util/Views'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'CopyrightPolicy'>
|
||||
export const CopyrightPolicyScreen = (_props: Props) => {
|
||||
@@ -25,7 +27,7 @@ export const CopyrightPolicyScreen = (_props: Props) => {
|
||||
)
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Layout.Screen>
|
||||
<ViewHeader title={_(msg`Copyright Policy`)} />
|
||||
<ScrollView style={[s.hContentRegion, pal.view]}>
|
||||
<View style={[s.p20]}>
|
||||
@@ -42,6 +44,6 @@ export const CopyrightPolicyScreen = (_props: Props) => {
|
||||
</View>
|
||||
<View style={s.footerSpacer} />
|
||||
</ScrollView>
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
+23
-16
@@ -1,24 +1,29 @@
|
||||
import React from 'react'
|
||||
import {ScrollView, View} from 'react-native'
|
||||
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
||||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
import {ThemeProvider, PaletteColorName} from 'lib/ThemeContext'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {s} from 'lib/styles'
|
||||
import * as Toast from 'view/com/util/Toast'
|
||||
import {Text} from '../com/util/text/Text'
|
||||
import {ViewSelector} from '../com/util/ViewSelector'
|
||||
import {EmptyState} from '../com/util/EmptyState'
|
||||
import * as LoadingPlaceholder from '../com/util/LoadingPlaceholder'
|
||||
import {Button, ButtonType} from '../com/util/forms/Button'
|
||||
import {DropdownButton, DropdownItem} from '../com/util/forms/DropdownButton'
|
||||
import {ToggleButton} from '../com/util/forms/ToggleButton'
|
||||
import {RadioGroup} from '../com/util/forms/RadioGroup'
|
||||
import {ErrorScreen} from '../com/util/error/ErrorScreen'
|
||||
import {ErrorMessage} from '../com/util/error/ErrorMessage'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {s} from '#/lib/styles'
|
||||
import {PaletteColorName, ThemeProvider} from '#/lib/ThemeContext'
|
||||
import {EmptyState} from '#/view/com/util/EmptyState'
|
||||
import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
|
||||
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
||||
import {Button, ButtonType} from '#/view/com/util/forms/Button'
|
||||
import {
|
||||
DropdownButton,
|
||||
DropdownItem,
|
||||
} from '#/view/com/util/forms/DropdownButton'
|
||||
import {RadioGroup} from '#/view/com/util/forms/RadioGroup'
|
||||
import {ToggleButton} from '#/view/com/util/forms/ToggleButton'
|
||||
import * as LoadingPlaceholder from '#/view/com/util/LoadingPlaceholder'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {ViewSelector} from '#/view/com/util/ViewSelector'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
const MAIN_VIEWS = ['Base', 'Controls', 'Error', 'Notifs']
|
||||
|
||||
export const DebugScreen = ({}: NativeStackScreenProps<
|
||||
@@ -33,10 +38,12 @@ export const DebugScreen = ({}: NativeStackScreenProps<
|
||||
}
|
||||
return (
|
||||
<ThemeProvider theme={colorScheme}>
|
||||
<Layout.Screen>
|
||||
<DebugInner
|
||||
colorScheme={colorScheme}
|
||||
onToggleColorScheme={onToggleColorScheme}
|
||||
/>
|
||||
</Layout.Screen>
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {moderationOptsOverrideContext} from '#/state/preferences/moderation-opts'
|
||||
import {FeedNotification} from '#/state/queries/notifications/types'
|
||||
import {
|
||||
@@ -28,7 +29,6 @@ import {
|
||||
shouldFilterNotif,
|
||||
} from '#/state/queries/notifications/util'
|
||||
import {useSession} from '#/state/session'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
||||
import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
||||
import {ProfileHeaderStandard} from '#/screens/Profile/Header/ProfileHeaderStandard'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
ChevronBottom_Stroke2_Corner0_Rounded as ChevronBottom,
|
||||
ChevronTop_Stroke2_Corner0_Rounded as ChevronTop,
|
||||
} from '#/components/icons/Chevron'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {H1, H3, P, Text} from '#/components/Typography'
|
||||
import {ScreenHider} from '../../components/moderation/ScreenHider'
|
||||
import {FeedItem as NotifFeedItem} from '../com/notifications/FeedItem'
|
||||
@@ -264,10 +265,13 @@ export const DebugModScreen = ({}: NativeStackScreenProps<
|
||||
}, [post, modOpts])
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<moderationOptsOverrideContext.Provider value={modOpts}>
|
||||
<ScrollView>
|
||||
<CenteredView style={[t.atoms.bg, a.px_lg, a.py_lg]}>
|
||||
<H1 style={[a.text_5xl, a.font_bold, a.pb_lg]}>Moderation states</H1>
|
||||
<H1 style={[a.text_5xl, a.font_bold, a.pb_lg]}>
|
||||
Moderation states
|
||||
</H1>
|
||||
|
||||
<Heading title="" subtitle="Scenario" />
|
||||
<ToggleButton.Group
|
||||
@@ -356,7 +360,8 @@ export const DebugModScreen = ({}: NativeStackScreenProps<
|
||||
type="checkbox"
|
||||
values={scenarioSwitches}
|
||||
onChange={setScenarioSwitches}>
|
||||
<View style={[a.gap_md, a.flex_row, a.flex_wrap, a.pt_md]}>
|
||||
<View
|
||||
style={[a.gap_md, a.flex_row, a.flex_wrap, a.pt_md]}>
|
||||
<Toggle.Item name="targetMe" label="Target is me">
|
||||
<Toggle.Checkbox />
|
||||
<Toggle.LabelText>Target is me</Toggle.LabelText>
|
||||
@@ -384,7 +389,12 @@ export const DebugModScreen = ({}: NativeStackScreenProps<
|
||||
false && (
|
||||
<View style={[a.mt_md]}>
|
||||
<Text
|
||||
style={[a.font_bold, a.text_xs, t.atoms.text, a.pb_sm]}>
|
||||
style={[
|
||||
a.font_bold,
|
||||
a.text_xs,
|
||||
t.atoms.text,
|
||||
a.pb_sm,
|
||||
]}>
|
||||
Preference
|
||||
</Text>
|
||||
<Toggle.Group
|
||||
@@ -473,7 +483,10 @@ export const DebugModScreen = ({}: NativeStackScreenProps<
|
||||
|
||||
<Heading title="" subtitle="Results" />
|
||||
|
||||
<ToggleButton.Group label="Results" values={view} onChange={setView}>
|
||||
<ToggleButton.Group
|
||||
label="Results"
|
||||
values={view}
|
||||
onChange={setView}>
|
||||
<ToggleButton.Button name="post" label="Post">
|
||||
<ToggleButton.ButtonText>Post</ToggleButton.ButtonText>
|
||||
</ToggleButton.Button>
|
||||
@@ -558,7 +571,10 @@ export const DebugModScreen = ({}: NativeStackScreenProps<
|
||||
label="Profile Moderation Data"
|
||||
data={profileModeration}
|
||||
/>
|
||||
<DataView label="Post Moderation Data" data={postModeration} />
|
||||
<DataView
|
||||
label="Post Moderation Data"
|
||||
data={postModeration}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
@@ -567,6 +583,7 @@ export const DebugModScreen = ({}: NativeStackScreenProps<
|
||||
</CenteredView>
|
||||
</ScrollView>
|
||||
</moderationOptsOverrideContext.Provider>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components
|
||||
import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline'
|
||||
import {ListMagnifyingGlass_Stroke2_Corner0_Rounded} from '#/components/icons/ListMagnifyingGlass'
|
||||
import {ListSparkle_Stroke2_Corner0_Rounded} from '#/components/icons/ListSparkle'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import * as ListCard from '#/components/ListCard'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Feeds'>
|
||||
@@ -545,7 +546,7 @@ export function FeedsScreen(_props: Props) {
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={[pal.view, styles.container]}>
|
||||
<Layout.Screen testID="FeedsScreen">
|
||||
{isMobile && (
|
||||
<ViewHeader
|
||||
title={_(msg`Feeds`)}
|
||||
@@ -582,7 +583,7 @@ export function FeedsScreen(_props: Props) {
|
||||
accessibilityHint=""
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -768,9 +769,6 @@ function FeedsAboutHeader() {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
list: {
|
||||
height: '100%',
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import {ActivityIndicator, StyleSheet, View} from 'react-native'
|
||||
import {ActivityIndicator, StyleSheet} from 'react-native'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
|
||||
import {PROD_DEFAULT_FEED} from '#/lib/constants'
|
||||
@@ -23,12 +23,13 @@ import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
|
||||
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
||||
import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
|
||||
import {FeedPage} from '#/view/com/feeds/FeedPage'
|
||||
import {HomeHeader} from '#/view/com/home/HomeHeader'
|
||||
import {Pager, PagerRef, RenderTabBarFnProps} from '#/view/com/pager/Pager'
|
||||
import {CustomFeedEmptyState} from '#/view/com/posts/CustomFeedEmptyState'
|
||||
import {FollowingEmptyState} from '#/view/com/posts/FollowingEmptyState'
|
||||
import {FollowingEndOfFeed} from '#/view/com/posts/FollowingEndOfFeed'
|
||||
import {NoFeedsPinned} from '#/screens/Home/NoFeedsPinned'
|
||||
import {HomeHeader} from '../com/home/HomeHeader'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home' | 'Start'>
|
||||
export function HomeScreen(props: Props) {
|
||||
@@ -70,17 +71,19 @@ export function HomeScreen(props: Props) {
|
||||
|
||||
if (preferences && pinnedFeedInfos && !isPinnedFeedsLoading) {
|
||||
return (
|
||||
<Layout.Screen testID="HomeScreen">
|
||||
<HomeScreenReady
|
||||
{...props}
|
||||
preferences={preferences}
|
||||
pinnedFeedInfos={pinnedFeedInfos}
|
||||
/>
|
||||
</Layout.Screen>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<View style={styles.loading}>
|
||||
<Layout.Screen style={styles.loading}>
|
||||
<ActivityIndicator size="large" />
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,10 @@ import {useModalControls} from '#/state/modals'
|
||||
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {Button} from '#/view/com/util/forms/Button'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import {Text} from '../com/util/text/Text'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'LanguageSettings'>
|
||||
|
||||
@@ -76,6 +77,7 @@ export function LanguageSettingsScreen(_props: Props) {
|
||||
}, [langPrefs.contentLanguages])
|
||||
|
||||
return (
|
||||
<Layout.Screen testID="PreferencesLanguagesScreen">
|
||||
<CenteredView
|
||||
style={[
|
||||
pal.view,
|
||||
@@ -279,8 +281,8 @@ export function LanguageSettingsScreen(_props: Props) {
|
||||
</Text>
|
||||
<Text style={[pal.text, s.pb10]}>
|
||||
<Trans>
|
||||
Select which languages you want your subscribed feeds to include.
|
||||
If none are selected, all languages will be shown.
|
||||
Select which languages you want your subscribed feeds to
|
||||
include. If none are selected, all languages will be shown.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
@@ -302,6 +304,7 @@ export function LanguageSettingsScreen(_props: Props) {
|
||||
</View>
|
||||
</View>
|
||||
</CenteredView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import {MyLists} from '#/view/com/lists/MyLists'
|
||||
import {Button} from '#/view/com/util/forms/Button'
|
||||
import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Lists'>
|
||||
export function ListsScreen({}: Props) {
|
||||
@@ -48,7 +49,7 @@ export function ListsScreen({}: Props) {
|
||||
}, [openModal, navigation])
|
||||
|
||||
return (
|
||||
<View style={s.hContentRegion} testID="listsScreen">
|
||||
<Layout.Screen testID="listsScreen">
|
||||
<SimpleViewHeader
|
||||
showBackButton={isMobile}
|
||||
style={[
|
||||
@@ -86,6 +87,6 @@ export function ListsScreen({}: Props) {
|
||||
</View>
|
||||
</SimpleViewHeader>
|
||||
<MyLists filter="curate" style={s.flexGrow1} />
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,16 +5,17 @@ import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {s} from '#/lib/styles'
|
||||
import {getEntries} from '#/logger/logDump'
|
||||
import {useTickEveryMinute} from '#/state/shell'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
||||
import {s} from 'lib/styles'
|
||||
import {Text} from '../com/util/text/Text'
|
||||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
import {ScrollView} from '../com/util/Views'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {ScrollView} from '#/view/com/util/Views'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
export function LogScreen({}: NativeStackScreenProps<
|
||||
CommonNavigatorParams,
|
||||
@@ -42,7 +43,7 @@ export function LogScreen({}: NativeStackScreenProps<
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[s.flex1]}>
|
||||
<Layout.Screen>
|
||||
<ViewHeader title="Log" />
|
||||
<ScrollView style={s.flex1}>
|
||||
{getEntries()
|
||||
@@ -91,7 +92,7 @@ export function LogScreen({}: NativeStackScreenProps<
|
||||
})}
|
||||
<View style={s.footerSpacer} />
|
||||
</ScrollView>
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,11 @@ import {logger} from '#/logger'
|
||||
import {useMyBlockedAccountsQuery} from '#/state/queries/my-blocked-accounts'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {ProfileCard} from '#/view/com/profile/ProfileCard'
|
||||
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import {ErrorScreen} from '../com/util/error/ErrorScreen'
|
||||
import {Text} from '../com/util/text/Text'
|
||||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
CommonNavigatorParams,
|
||||
@@ -95,6 +96,7 @@ export function ModerationBlockedAccounts({}: Props) {
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<Layout.Screen testID="blockedAccountsScreen">
|
||||
<CenteredView
|
||||
style={[
|
||||
styles.container,
|
||||
@@ -129,9 +131,9 @@ export function ModerationBlockedAccounts({}: Props) {
|
||||
<View style={[styles.empty, pal.viewLight]}>
|
||||
<Text type="lg" style={[pal.text, styles.emptyText]}>
|
||||
<Trans>
|
||||
You have not blocked any accounts yet. To block an account, go
|
||||
to their profile and select "Block account" from the menu on
|
||||
their account.
|
||||
You have not blocked any accounts yet. To block an account,
|
||||
go to their profile and select "Block account" from the menu
|
||||
on their account.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
@@ -165,6 +167,7 @@ export function ModerationBlockedAccounts({}: Props) {
|
||||
/>
|
||||
)}
|
||||
</CenteredView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import {MyLists} from '#/view/com/lists/MyLists'
|
||||
import {Button} from '#/view/com/util/forms/Button'
|
||||
import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ModerationModlists'>
|
||||
export function ModerationModlistsScreen({}: Props) {
|
||||
@@ -48,7 +49,7 @@ export function ModerationModlistsScreen({}: Props) {
|
||||
}, [openModal, navigation])
|
||||
|
||||
return (
|
||||
<View style={s.hContentRegion} testID="moderationModlistsScreen">
|
||||
<Layout.Screen testID="moderationModlistsScreen">
|
||||
<SimpleViewHeader
|
||||
showBackButton={isMobile}
|
||||
style={
|
||||
@@ -82,6 +83,6 @@ export function ModerationModlistsScreen({}: Props) {
|
||||
</View>
|
||||
</SimpleViewHeader>
|
||||
<MyLists filter="mod" style={s.flexGrow1} />
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,10 +20,11 @@ import {logger} from '#/logger'
|
||||
import {useMyMutedAccountsQuery} from '#/state/queries/my-muted-accounts'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {ProfileCard} from '#/view/com/profile/ProfileCard'
|
||||
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import {ErrorScreen} from '../com/util/error/ErrorScreen'
|
||||
import {Text} from '../com/util/text/Text'
|
||||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
CommonNavigatorParams,
|
||||
@@ -95,6 +96,7 @@ export function ModerationMutedAccounts({}: Props) {
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<Layout.Screen testID="mutedAccountsScreen">
|
||||
<CenteredView
|
||||
style={[
|
||||
styles.container,
|
||||
@@ -128,9 +130,9 @@ export function ModerationMutedAccounts({}: Props) {
|
||||
<View style={[styles.empty, pal.viewLight]}>
|
||||
<Text type="lg" style={[pal.text, styles.emptyText]}>
|
||||
<Trans>
|
||||
You have not muted any accounts yet. To mute an account, go to
|
||||
their profile and select "Mute account" from the menu on their
|
||||
account.
|
||||
You have not muted any accounts yet. To mute an account, go
|
||||
to their profile and select "Mute account" from the menu on
|
||||
their account.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
@@ -164,6 +166,7 @@ export function ModerationMutedAccounts({}: Props) {
|
||||
/>
|
||||
)}
|
||||
</CenteredView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import React from 'react'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {
|
||||
useNavigation,
|
||||
StackActions,
|
||||
useFocusEffect,
|
||||
useNavigation,
|
||||
} from '@react-navigation/native'
|
||||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
import {Text} from '../com/util/text/Text'
|
||||
import {Button} from 'view/com/util/forms/Button'
|
||||
import {NavigationProp} from 'lib/routes/types'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {s} from 'lib/styles'
|
||||
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {NavigationProp} from '#/lib/routes/types'
|
||||
import {s} from '#/lib/styles'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {Button} from '#/view/com/util/forms/Button'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
export const NotFoundScreen = () => {
|
||||
const pal = usePalette('default')
|
||||
@@ -38,7 +40,7 @@ export const NotFoundScreen = () => {
|
||||
}, [navigation, canGoBack])
|
||||
|
||||
return (
|
||||
<View testID="notFoundView" style={pal.view}>
|
||||
<Layout.Screen testID="notFoundView">
|
||||
<ViewHeader title={_(msg`Page Not Found`)} />
|
||||
<View style={styles.container}>
|
||||
<Text type="title-2xl" style={[pal.text, s.mb10]}>
|
||||
@@ -61,7 +63,7 @@ export const NotFoundScreen = () => {
|
||||
onPress={onPressHome}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import {CenteredView} from '#/view/com/util/Views'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button} from '#/components/Button'
|
||||
import {SettingsGear2_Stroke2_Corner0_Rounded as SettingsIcon} from '#/components/icons/SettingsGear2'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Link} from '#/components/Link'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {Text} from '#/components/Typography'
|
||||
@@ -192,10 +193,8 @@ export function NotificationsScreen({route: {params}}: Props) {
|
||||
}, [renderButton, isLoadingLatest])
|
||||
|
||||
return (
|
||||
<CenteredView
|
||||
testID="notificationsScreen"
|
||||
style={[s.hContentRegion, {paddingTop: 2}]}
|
||||
sideBorders={true}>
|
||||
<Layout.Screen testID="notificationsScreen">
|
||||
<CenteredView style={[a.flex_1, {paddingTop: 2}]} sideBorders={true}>
|
||||
<ViewHeader
|
||||
title={_(msg`Notifications`)}
|
||||
canGoBack={false}
|
||||
@@ -226,5 +225,6 @@ export function NotificationsScreen({route: {params}}: Props) {
|
||||
accessibilityHint=""
|
||||
/>
|
||||
</CenteredView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,8 +10,10 @@ import {useNotificationsSettingsMutation} from '#/state/queries/notifications/se
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {ScrollView} from '#/view/com/util/Views'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {Error} from '#/components/Error'
|
||||
import * as Toggle from '#/components/forms/Toggle'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
@@ -34,6 +36,7 @@ export function NotificationsSettingsScreen({}: Props) {
|
||||
: serverPriority
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<ScrollView stickyHeaderIndices={[0]}>
|
||||
<ViewHeader
|
||||
title={_(msg`Notification Settings`)}
|
||||
@@ -71,24 +74,16 @@ export function NotificationsSettingsScreen({}: Props) {
|
||||
</Toggle.Item>
|
||||
</View>
|
||||
</Toggle.Group>
|
||||
<View
|
||||
style={[
|
||||
a.mt_sm,
|
||||
a.px_xl,
|
||||
a.py_lg,
|
||||
a.rounded_md,
|
||||
t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
<Text style={[t.atoms.text_contrast_high, a.leading_snug]}>
|
||||
<Admonition type="warning" style={[a.mt_sm]}>
|
||||
<Trans>
|
||||
Experimental: When this preference is enabled, you'll only
|
||||
receive reply and quote notifications from users you follow.
|
||||
We'll continue to add more controls here over time.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
</Admonition>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {makeRecordUri} from '#/lib/strings/url-helpers'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
||||
import {makeRecordUri} from 'lib/strings/url-helpers'
|
||||
import {s} from 'lib/styles'
|
||||
import {PostThread as PostThreadComponent} from '../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'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'>
|
||||
export function PostThreadScreen({route}: Props) {
|
||||
@@ -22,10 +23,10 @@ export function PostThreadScreen({route}: Props) {
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={s.hContentRegion}>
|
||||
<View style={s.flex1}>
|
||||
<Layout.Screen testID="postThreadScreen">
|
||||
<View style={a.flex_1}>
|
||||
<PostThreadComponent uri={uri} />
|
||||
</View>
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,17 +10,17 @@ import {
|
||||
EmbedPlayerSource,
|
||||
externalEmbedLabels,
|
||||
} from '#/lib/strings/embed-player'
|
||||
import {s} from '#/lib/styles'
|
||||
import {
|
||||
useExternalEmbedsPrefs,
|
||||
useSetExternalEmbedPref,
|
||||
} from '#/state/preferences'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {ToggleButton} from '#/view/com/util/forms/ToggleButton'
|
||||
import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {ScrollView} from '#/view/com/util/Views'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {SimpleViewHeader} from '../com/util/SimpleViewHeader'
|
||||
import {Text} from '../com/util/text/Text'
|
||||
import {ScrollView} from '../com/util/Views'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
CommonNavigatorParams,
|
||||
@@ -38,7 +38,7 @@ export function PreferencesExternalEmbeds({}: Props) {
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={s.hContentRegion} testID="preferencesExternalEmbedsScreen">
|
||||
<Layout.Screen testID="preferencesExternalEmbedsScreen">
|
||||
<ScrollView
|
||||
// @ts-ignore web only -prf
|
||||
dataSet={{'stable-gutters': 1}}
|
||||
@@ -81,7 +81,7 @@ export function PreferencesExternalEmbeds({}: Props) {
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {ScrollView} from '#/view/com/util/Views'
|
||||
import {atoms as a} from '#/alf'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
type Props = NativeStackScreenProps<
|
||||
CommonNavigatorParams,
|
||||
@@ -35,7 +36,7 @@ export function PreferencesFollowingFeed({}: Props) {
|
||||
)
|
||||
|
||||
return (
|
||||
<View testID="preferencesHomeFeedScreen" style={s.hContentRegion}>
|
||||
<Layout.Screen testID="preferencesHomeFeedScreen">
|
||||
<ScrollView
|
||||
// @ts-ignore web only -sfn
|
||||
dataSet={{'stable-gutters': 1}}
|
||||
@@ -185,7 +186,7 @@ export function PreferencesFollowingFeed({}: Props) {
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {ScrollView} from '#/view/com/util/Views'
|
||||
import {atoms as a} from '#/alf'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PreferencesThreads'>
|
||||
export function PreferencesThreads({}: Props) {
|
||||
@@ -38,7 +39,7 @@ export function PreferencesThreads({}: Props) {
|
||||
)
|
||||
|
||||
return (
|
||||
<View testID="preferencesThreadsScreen" style={s.hContentRegion}>
|
||||
<Layout.Screen testID="preferencesThreadsScreen">
|
||||
<ScrollView
|
||||
// @ts-ignore web only -prf
|
||||
dataSet={{'stable-gutters': 1}}
|
||||
@@ -134,7 +135,7 @@ export function PreferencesThreads({}: Props) {
|
||||
<ActivityIndicator style={a.flex_1} />
|
||||
)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
import {Text} from 'view/com/util/text/Text'
|
||||
import {TextLink} from 'view/com/util/Link'
|
||||
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
|
||||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
import {ScrollView} from 'view/com/util/Views'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {s} from 'lib/styles'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {Trans, msg} from '@lingui/macro'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {s} from '#/lib/styles'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {TextLink} from '#/view/com/util/Link'
|
||||
import {Text} from '#/view/com/util/text/Text'
|
||||
import {ScrollView} from '#/view/com/util/Views'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PrivacyPolicy'>
|
||||
export const PrivacyPolicyScreen = (_props: Props) => {
|
||||
@@ -25,7 +27,7 @@ export const PrivacyPolicyScreen = (_props: Props) => {
|
||||
)
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Layout.Screen>
|
||||
<ViewHeader title={_(msg`Privacy Policy`)} />
|
||||
<ScrollView style={[s.hContentRegion, pal.view]}>
|
||||
<View style={[s.p20]}>
|
||||
@@ -42,6 +44,6 @@ export const PrivacyPolicyScreen = (_props: Props) => {
|
||||
</View>
|
||||
<View style={s.footerSpacer} />
|
||||
</ScrollView>
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header'
|
||||
import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed'
|
||||
import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels'
|
||||
import {web} from '#/alf'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ScreenHider} from '#/components/moderation/ScreenHider'
|
||||
import {ProfileStarterPacks} from '#/components/StarterPack/ProfileStarterPacks'
|
||||
import {navigate} from '#/Navigation'
|
||||
@@ -55,7 +56,15 @@ interface SectionRef {
|
||||
}
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'>
|
||||
export function ProfileScreen({route}: Props) {
|
||||
export function ProfileScreen(props: Props) {
|
||||
return (
|
||||
<Layout.Screen testID="profileScreen">
|
||||
<ProfileScreenInner {...props} />
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
function ProfileScreenInner({route}: Props) {
|
||||
const {_} = useLingui()
|
||||
const {currentAccount} = useSession()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
@@ -61,6 +61,7 @@ import {
|
||||
} from '#/components/icons/Heart2'
|
||||
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
|
||||
import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {InlineLinkText} from '#/components/Link'
|
||||
import * as Menu from '#/components/Menu'
|
||||
import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
|
||||
@@ -96,6 +97,7 @@ export function ProfileFeedScreen(props: Props) {
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Layout.Screen testID="profileFeedScreenError">
|
||||
<CenteredView>
|
||||
<View style={[pal.view, pal.border, styles.notFoundContainer]}>
|
||||
<Text type="title-lg" style={[pal.text, s.mb10]}>
|
||||
@@ -119,13 +121,18 @@ export function ProfileFeedScreen(props: Props) {
|
||||
</View>
|
||||
</View>
|
||||
</CenteredView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
return resolvedUri ? (
|
||||
<Layout.Screen>
|
||||
<ProfileFeedScreenIntermediate feedUri={resolvedUri.uri} />
|
||||
</Layout.Screen>
|
||||
) : (
|
||||
<Layout.Screen>
|
||||
<LoadingScreen />
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {makeRecordUri} from '#/lib/strings/url-helpers'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
||||
import {makeRecordUri} from 'lib/strings/url-helpers'
|
||||
import {PostLikedBy as PostLikedByComponent} from '../com/post-thread/PostLikedBy'
|
||||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
import {PostLikedBy as PostLikedByComponent} from '#/view/com/post-thread/PostLikedBy'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFeedLikedBy'>
|
||||
export const ProfileFeedLikedByScreen = ({route}: Props) => {
|
||||
@@ -24,9 +24,9 @@ export const ProfileFeedLikedByScreen = ({route}: Props) => {
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Layout.Screen testID="postLikedByScreen">
|
||||
<ViewHeader title={_(msg`Liked By`)} />
|
||||
<PostLikedByComponent uri={uri} />
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@ import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {CenteredView} from 'view/com/util/Views'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {ProfileFollowers as ProfileFollowersComponent} from '#/view/com/profile/ProfileFollowers'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ListHeaderDesktop} from '#/components/Lists'
|
||||
import {ProfileFollowers as ProfileFollowersComponent} from '../com/profile/ProfileFollowers'
|
||||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollowers'>
|
||||
export const ProfileFollowersScreen = ({route}: Props) => {
|
||||
@@ -25,10 +25,12 @@ export const ProfileFollowersScreen = ({route}: Props) => {
|
||||
)
|
||||
|
||||
return (
|
||||
<CenteredView style={a.util_screen_outer} sideBorders={true}>
|
||||
<Layout.Screen testID="profileFollowersScreen">
|
||||
<CenteredView sideBorders={true}>
|
||||
<ListHeaderDesktop title={_(msg`Followers`)} />
|
||||
<ViewHeader title={_(msg`Followers`)} showBorder={!isWeb} />
|
||||
<ProfileFollowersComponent name={name} />
|
||||
</CenteredView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@ import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {CenteredView} from 'view/com/util/Views'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {ProfileFollows as ProfileFollowsComponent} from '#/view/com/profile/ProfileFollows'
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {ListHeaderDesktop} from '#/components/Lists'
|
||||
import {ProfileFollows as ProfileFollowsComponent} from '../com/profile/ProfileFollows'
|
||||
import {ViewHeader} from '../com/util/ViewHeader'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollows'>
|
||||
export const ProfileFollowsScreen = ({route}: Props) => {
|
||||
@@ -25,10 +25,12 @@ export const ProfileFollowsScreen = ({route}: Props) => {
|
||||
)
|
||||
|
||||
return (
|
||||
<CenteredView style={a.util_screen_outer} sideBorders={true}>
|
||||
<Layout.Screen testID="profileFollowsScreen">
|
||||
<CenteredView sideBorders={true}>
|
||||
<ListHeaderDesktop title={_(msg`Following`)} />
|
||||
<ViewHeader title={_(msg`Following`)} showBorder={!isWeb} />
|
||||
<ProfileFollowsComponent name={name} />
|
||||
</CenteredView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ import {CenteredView} from '#/view/com/util/Views'
|
||||
import {ListHiddenScreen} from '#/screens/List/ListHiddenScreen'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import * as Hider from '#/components/moderation/Hider'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
|
||||
@@ -87,6 +88,14 @@ interface SectionRef {
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileList'>
|
||||
export function ProfileListScreen(props: Props) {
|
||||
return (
|
||||
<Layout.Screen testID="profileListScreen">
|
||||
<ProfileListScreenInner {...props} />
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
function ProfileListScreenInner(props: Props) {
|
||||
const {_} = useLingui()
|
||||
const {name: handleOrDid, rkey} = props.route.params
|
||||
const {data: resolvedUri, error: resolveError} = useResolveUriQuery(
|
||||
|
||||
+124
-113
@@ -1,22 +1,23 @@
|
||||
import React from 'react'
|
||||
import {ActivityIndicator, Pressable, StyleSheet, View} from 'react-native'
|
||||
import Animated, {LinearTransition} from 'react-native-reanimated'
|
||||
import {AppBskyActorDefs} from '@atproto/api'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useFocusEffect} from '@react-navigation/native'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||
import {CommonNavigatorParams} from '#/lib/routes/types'
|
||||
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
|
||||
import {colors, s} from '#/lib/styles'
|
||||
import {logger} from '#/logger'
|
||||
import {
|
||||
useOverwriteSavedFeedsMutation,
|
||||
usePreferencesQuery,
|
||||
useUpdateSavedFeedsMutation,
|
||||
} from '#/state/queries/preferences'
|
||||
import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
|
||||
import {useSetMinimalShellMode} from '#/state/shell'
|
||||
@@ -29,43 +30,41 @@ import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
||||
import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
|
||||
import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline'
|
||||
|
||||
const HITSLOP_TOP = {
|
||||
top: 20,
|
||||
left: 20,
|
||||
bottom: 5,
|
||||
right: 20,
|
||||
}
|
||||
const HITSLOP_BOTTOM = {
|
||||
top: 5,
|
||||
left: 20,
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
}
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Loader} from '#/components/Loader'
|
||||
|
||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'SavedFeeds'>
|
||||
export function SavedFeeds({}: Props) {
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
if (!preferences) {
|
||||
return <View />
|
||||
}
|
||||
return <SavedFeedsInner preferences={preferences} />
|
||||
}
|
||||
|
||||
function SavedFeedsInner({
|
||||
preferences,
|
||||
}: {
|
||||
preferences: UsePreferencesQueryResponse
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const {isMobile, isTabletOrDesktop} = useWebMediaQueries()
|
||||
const {isMobile, isTabletOrDesktop, isDesktop} = useWebMediaQueries()
|
||||
const setMinimalShellMode = useSetMinimalShellMode()
|
||||
const {data: preferences} = usePreferencesQuery()
|
||||
const {
|
||||
mutateAsync: overwriteSavedFeeds,
|
||||
variables: optimisticSavedFeedsResponse,
|
||||
reset: resetSaveFeedsMutationState,
|
||||
error: savedFeedsError,
|
||||
} = useOverwriteSavedFeedsMutation()
|
||||
const {mutateAsync: overwriteSavedFeeds, isPending: isOverwritePending} =
|
||||
useOverwriteSavedFeedsMutation()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
|
||||
/*
|
||||
* Use optimistic data if exists and no error, otherwise fallback to remote
|
||||
* data
|
||||
*/
|
||||
const currentFeeds =
|
||||
optimisticSavedFeedsResponse && !savedFeedsError
|
||||
? optimisticSavedFeedsResponse
|
||||
: preferences?.savedFeeds || []
|
||||
const [currentFeeds, setCurrentFeeds] = React.useState(
|
||||
() => preferences.savedFeeds || [],
|
||||
)
|
||||
const hasUnsavedChanges = currentFeeds !== preferences.savedFeeds
|
||||
const pinnedFeeds = currentFeeds.filter(f => f.pinned)
|
||||
const unpinnedFeeds = currentFeeds.filter(f => !f.pinned)
|
||||
const noSavedFeedsOfAnyType = pinnedFeeds.length + unpinnedFeeds.length === 0
|
||||
@@ -78,18 +77,49 @@ export function SavedFeeds({}: Props) {
|
||||
}, [setMinimalShellMode]),
|
||||
)
|
||||
|
||||
const onSaveChanges = React.useCallback(async () => {
|
||||
try {
|
||||
await overwriteSavedFeeds(currentFeeds)
|
||||
Toast.show(_(msg`Feeds updated!`))
|
||||
navigation.navigate('Feeds')
|
||||
} catch (e) {
|
||||
Toast.show(_(msg`There was an issue contacting the server`), 'xmark')
|
||||
logger.error('Failed to toggle pinned feed', {message: e})
|
||||
}
|
||||
}, [_, 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]}>
|
||||
<ButtonText style={[isDesktop && a.text_md]}>
|
||||
{isDesktop ? <Trans>Save changes</Trans> : <Trans>Save</Trans>}
|
||||
</ButtonText>
|
||||
{isOverwritePending && <ButtonIcon icon={Loader} />}
|
||||
</Button>
|
||||
)
|
||||
}, [_, isDesktop, onSaveChanges, hasUnsavedChanges, isOverwritePending])
|
||||
|
||||
return (
|
||||
<Layout.Screen>
|
||||
<CenteredView
|
||||
style={[
|
||||
s.hContentRegion,
|
||||
pal.border,
|
||||
isTabletOrDesktop && styles.desktopContainer,
|
||||
]}>
|
||||
<ViewHeader title={_(msg`Edit My Feeds`)} showOnDesktop showBorder />
|
||||
<ScrollView style={s.flex1} contentContainerStyle={[styles.noBorder]}>
|
||||
style={[a.util_screen_outer]}
|
||||
sideBorders={isTabletOrDesktop}>
|
||||
<ViewHeader
|
||||
title={_(msg`Edit My Feeds`)}
|
||||
showOnDesktop
|
||||
showBorder
|
||||
renderButton={renderHeaderBtn}
|
||||
/>
|
||||
<ScrollView style={[a.flex_1]} contentContainerStyle={[a.border_0]}>
|
||||
{noSavedFeedsOfAnyType && (
|
||||
<View
|
||||
style={[pal.border, {borderBottomWidth: StyleSheet.hairlineWidth}]}>
|
||||
<View style={[pal.border, a.border_b]}>
|
||||
<NoSavedFeedsOfAnyType />
|
||||
</View>
|
||||
)}
|
||||
@@ -119,9 +149,8 @@ export function SavedFeeds({}: Props) {
|
||||
key={f.id}
|
||||
feed={f}
|
||||
isPinned
|
||||
overwriteSavedFeeds={overwriteSavedFeeds}
|
||||
resetSaveFeedsMutationState={resetSaveFeedsMutationState}
|
||||
currentFeeds={currentFeeds}
|
||||
setCurrentFeeds={setCurrentFeeds}
|
||||
preferences={preferences}
|
||||
/>
|
||||
))
|
||||
@@ -131,8 +160,7 @@ export function SavedFeeds({}: Props) {
|
||||
)}
|
||||
|
||||
{noFollowingFeed && (
|
||||
<View
|
||||
style={[pal.border, {borderBottomWidth: StyleSheet.hairlineWidth}]}>
|
||||
<View style={[pal.border, a.border_b]}>
|
||||
<NoFollowingFeed />
|
||||
</View>
|
||||
)}
|
||||
@@ -161,9 +189,8 @@ export function SavedFeeds({}: Props) {
|
||||
key={f.id}
|
||||
feed={f}
|
||||
isPinned={false}
|
||||
overwriteSavedFeeds={overwriteSavedFeeds}
|
||||
resetSaveFeedsMutationState={resetSaveFeedsMutationState}
|
||||
currentFeeds={currentFeeds}
|
||||
setCurrentFeeds={setCurrentFeeds}
|
||||
preferences={preferences}
|
||||
/>
|
||||
))
|
||||
@@ -175,8 +202,8 @@ export function SavedFeeds({}: Props) {
|
||||
<View style={styles.footerText}>
|
||||
<Text type="sm" style={pal.textLight}>
|
||||
<Trans>
|
||||
Feeds are custom algorithms that users build with a little coding
|
||||
expertise.{' '}
|
||||
Feeds are custom algorithms that users build with a little
|
||||
coding expertise.{' '}
|
||||
<TextLink
|
||||
type="sm"
|
||||
style={pal.link}
|
||||
@@ -190,6 +217,7 @@ export function SavedFeeds({}: Props) {
|
||||
<View style={{height: 100}} />
|
||||
</ScrollView>
|
||||
</CenteredView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -197,44 +225,27 @@ function ListItem({
|
||||
feed,
|
||||
isPinned,
|
||||
currentFeeds,
|
||||
overwriteSavedFeeds,
|
||||
resetSaveFeedsMutationState,
|
||||
setCurrentFeeds,
|
||||
}: {
|
||||
feed: AppBskyActorDefs.SavedFeed
|
||||
isPinned: boolean
|
||||
currentFeeds: AppBskyActorDefs.SavedFeed[]
|
||||
overwriteSavedFeeds: ReturnType<
|
||||
typeof useOverwriteSavedFeedsMutation
|
||||
>['mutateAsync']
|
||||
resetSaveFeedsMutationState: ReturnType<
|
||||
typeof useOverwriteSavedFeedsMutation
|
||||
>['reset']
|
||||
setCurrentFeeds: React.Dispatch<AppBskyActorDefs.SavedFeed[]>
|
||||
preferences: UsePreferencesQueryResponse
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
const pal = usePalette('default')
|
||||
const playHaptic = useHaptics()
|
||||
const {isPending: isUpdatePending, mutateAsync: updateSavedFeeds} =
|
||||
useUpdateSavedFeedsMutation()
|
||||
const feedUri = feed.value
|
||||
|
||||
const onTogglePinned = React.useCallback(async () => {
|
||||
playHaptic()
|
||||
|
||||
try {
|
||||
resetSaveFeedsMutationState()
|
||||
|
||||
await updateSavedFeeds([
|
||||
{
|
||||
...feed,
|
||||
pinned: !feed.pinned,
|
||||
},
|
||||
])
|
||||
} catch (e) {
|
||||
Toast.show(_(msg`There was an issue contacting the server`), 'xmark')
|
||||
logger.error('Failed to toggle pinned feed', {message: e})
|
||||
}
|
||||
}, [_, playHaptic, feed, updateSavedFeeds, resetSaveFeedsMutationState])
|
||||
setCurrentFeeds(
|
||||
currentFeeds.map(f =>
|
||||
f.id === feed.id ? {...feed, pinned: !feed.pinned} : f,
|
||||
),
|
||||
)
|
||||
}, [playHaptic, feed, currentFeeds, setCurrentFeeds])
|
||||
|
||||
const onPressUp = React.useCallback(async () => {
|
||||
if (!isPinned) return
|
||||
@@ -250,13 +261,8 @@ function ListItem({
|
||||
nextFeeds[index],
|
||||
]
|
||||
|
||||
try {
|
||||
await overwriteSavedFeeds(nextFeeds)
|
||||
} catch (e) {
|
||||
Toast.show(_(msg`There was an issue contacting the server`), 'xmark')
|
||||
logger.error('Failed to set pinned feed order', {message: e})
|
||||
}
|
||||
}, [feed, isPinned, overwriteSavedFeeds, currentFeeds, _])
|
||||
setCurrentFeeds(nextFeeds)
|
||||
}, [feed, isPinned, setCurrentFeeds, currentFeeds])
|
||||
|
||||
const onPressDown = React.useCallback(async () => {
|
||||
if (!isPinned) return
|
||||
@@ -266,22 +272,25 @@ function ListItem({
|
||||
const index = ids.indexOf(feed.id)
|
||||
const nextIndex = index + 1
|
||||
|
||||
if (index === -1 || index >= nextFeeds.length - 1) return
|
||||
if (index === -1 || index >= nextFeeds.filter(f => f.pinned).length - 1)
|
||||
return
|
||||
;[nextFeeds[index], nextFeeds[nextIndex]] = [
|
||||
nextFeeds[nextIndex],
|
||||
nextFeeds[index],
|
||||
]
|
||||
|
||||
try {
|
||||
await overwriteSavedFeeds(nextFeeds)
|
||||
} catch (e) {
|
||||
Toast.show(_(msg`There was an issue contacting the server`), 'xmark')
|
||||
logger.error('Failed to set pinned feed order', {message: e})
|
||||
}
|
||||
}, [feed, isPinned, overwriteSavedFeeds, currentFeeds, _])
|
||||
setCurrentFeeds(nextFeeds)
|
||||
}, [feed, isPinned, setCurrentFeeds, currentFeeds])
|
||||
|
||||
const onPressRemove = React.useCallback(async () => {
|
||||
playHaptic()
|
||||
setCurrentFeeds(currentFeeds.filter(f => f.id !== feed.id))
|
||||
}, [playHaptic, feed, currentFeeds, setCurrentFeeds])
|
||||
|
||||
return (
|
||||
<View style={[styles.itemContainer, pal.border]}>
|
||||
<Animated.View
|
||||
style={[styles.itemContainer, pal.border]}
|
||||
layout={LinearTransition.duration(100)}>
|
||||
{feed.type === 'timeline' ? (
|
||||
<FollowingFeedCard />
|
||||
) : (
|
||||
@@ -290,25 +299,22 @@ function ListItem({
|
||||
feedUri={feedUri}
|
||||
style={[isPinned && {paddingRight: 8}]}
|
||||
showMinimalPlaceholder
|
||||
showSaveBtn={!isPinned}
|
||||
hideTopBorder={true}
|
||||
/>
|
||||
)}
|
||||
{isPinned ? (
|
||||
<>
|
||||
<Pressable
|
||||
disabled={isUpdatePending}
|
||||
accessibilityRole="button"
|
||||
onPress={onPressUp}
|
||||
hitSlop={HITSLOP_TOP}
|
||||
hitSlop={5}
|
||||
style={state => ({
|
||||
backgroundColor: pal.viewLight.backgroundColor,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 4,
|
||||
marginRight: 8,
|
||||
opacity:
|
||||
state.hovered || state.pressed || isUpdatePending ? 0.5 : 1,
|
||||
opacity: state.hovered || state.pressed ? 0.5 : 1,
|
||||
})}>
|
||||
<FontAwesomeIcon
|
||||
icon="arrow-up"
|
||||
@@ -317,18 +323,16 @@ function ListItem({
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
disabled={isUpdatePending}
|
||||
accessibilityRole="button"
|
||||
onPress={onPressDown}
|
||||
hitSlop={HITSLOP_BOTTOM}
|
||||
hitSlop={5}
|
||||
style={state => ({
|
||||
backgroundColor: pal.viewLight.backgroundColor,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 4,
|
||||
marginRight: 8,
|
||||
opacity:
|
||||
state.hovered || state.pressed || isUpdatePending ? 0.5 : 1,
|
||||
opacity: state.hovered || state.pressed ? 0.5 : 1,
|
||||
})}>
|
||||
<FontAwesomeIcon
|
||||
icon="arrow-down"
|
||||
@@ -337,20 +341,39 @@ function ListItem({
|
||||
/>
|
||||
</Pressable>
|
||||
</>
|
||||
) : null}
|
||||
) : (
|
||||
<Pressable
|
||||
testID={`feed-${feedUri}-toggleSave`}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Remove from my feeds`)}
|
||||
accessibilityHint=""
|
||||
onPress={onPressRemove}
|
||||
hitSlop={5}
|
||||
style={state => ({
|
||||
marginRight: 8,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 4,
|
||||
opacity: state.hovered || state.focused ? 0.5 : 1,
|
||||
})}>
|
||||
<FontAwesomeIcon
|
||||
icon={['far', 'trash-can']}
|
||||
size={19}
|
||||
color={pal.colors.icon}
|
||||
/>
|
||||
</Pressable>
|
||||
)}
|
||||
<View style={{paddingRight: 16}}>
|
||||
<Pressable
|
||||
disabled={isUpdatePending}
|
||||
accessibilityRole="button"
|
||||
hitSlop={10}
|
||||
hitSlop={5}
|
||||
onPress={onTogglePinned}
|
||||
style={state => ({
|
||||
backgroundColor: pal.viewLight.backgroundColor,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 4,
|
||||
opacity:
|
||||
state.hovered || state.focused || isUpdatePending ? 0.5 : 1,
|
||||
opacity: state.hovered || state.focused ? 0.5 : 1,
|
||||
})}>
|
||||
<FontAwesomeIcon
|
||||
icon="thumb-tack"
|
||||
@@ -359,7 +382,7 @@ function ListItem({
|
||||
/>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
</Animated.View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -409,12 +432,6 @@ function FollowingFeedCard() {
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
desktopContainer: {
|
||||
borderLeftWidth: 1,
|
||||
borderRightWidth: 1,
|
||||
// @ts-ignore only rendered on web
|
||||
minHeight: '100vh',
|
||||
},
|
||||
empty: {
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 20,
|
||||
@@ -438,10 +455,4 @@ const styles = StyleSheet.create({
|
||||
paddingTop: 22,
|
||||
paddingBottom: 100,
|
||||
},
|
||||
noBorder: {
|
||||
borderBottomWidth: 0,
|
||||
borderRightWidth: 0,
|
||||
borderLeftWidth: 0,
|
||||
borderTopWidth: 0,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -10,20 +10,20 @@ import {
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useGetPopularFeedsQuery} from '#/state/queries/feed'
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {useSuggestedFollowsQuery} from '#/state/queries/suggested-follows'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
|
||||
import {List} from '#/view/com/util/List'
|
||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {
|
||||
FeedFeedLoadingPlaceholder,
|
||||
ProfileCardFeedLoadingPlaceholder,
|
||||
} from 'view/com/util/LoadingPlaceholder'
|
||||
} from '#/view/com/util/LoadingPlaceholder'
|
||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {atoms as a, useTheme, ViewStyleProp} from '#/alf'
|
||||
import {Button} from '#/components/Button'
|
||||
import * as FeedCard from '#/components/FeedCard'
|
||||
@@ -564,6 +564,8 @@ export function Explore() {
|
||||
[t, moderationOpts],
|
||||
)
|
||||
|
||||
// note: actually not a screen, instead it's nested within
|
||||
// the search screen. so we don't need Layout.Screen
|
||||
return (
|
||||
<List
|
||||
data={items}
|
||||
|
||||
@@ -65,6 +65,7 @@ import * as FeedCard from '#/components/FeedCard'
|
||||
import {SearchInput} from '#/components/forms/SearchInput'
|
||||
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron'
|
||||
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
|
||||
import * as Layout from '#/components/Layout'
|
||||
|
||||
function Loader() {
|
||||
const pal = usePalette('default')
|
||||
@@ -436,19 +437,16 @@ function SearchLanguageDropdown({
|
||||
}
|
||||
|
||||
function useQueryManager({initialQuery}: {initialQuery: string}) {
|
||||
const {contentLanguages} = useLanguagePrefs()
|
||||
const {query, params: initialParams} = React.useMemo(() => {
|
||||
return parseSearchQuery(initialQuery || '')
|
||||
}, [initialQuery])
|
||||
const prevInitialQuery = React.useRef(initialQuery)
|
||||
const [lang, setLang] = React.useState(
|
||||
initialParams.lang || contentLanguages[0],
|
||||
)
|
||||
const [lang, setLang] = React.useState(initialParams.lang || '')
|
||||
|
||||
if (initialQuery !== prevInitialQuery.current) {
|
||||
// handle new queryParam change (from manual search entry)
|
||||
prevInitialQuery.current = initialQuery
|
||||
setLang(initialParams.lang || contentLanguages[0])
|
||||
setLang(initialParams.lang || '')
|
||||
}
|
||||
|
||||
const params = React.useMemo(
|
||||
@@ -855,7 +853,7 @@ export function SearchScreen(
|
||||
}, [setShowAutocomplete])
|
||||
|
||||
return (
|
||||
<View style={isWeb ? null : {flex: 1}}>
|
||||
<Layout.Screen testID="searchScreen">
|
||||
<CenteredView
|
||||
style={[
|
||||
a.p_md,
|
||||
@@ -960,7 +958,7 @@ export function SearchScreen(
|
||||
headerHeight={headerHeight}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ import {atoms as a, useTheme} from '#/alf'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings'
|
||||
import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Email2FAToggle} from './Email2FAToggle'
|
||||
import {ExportCarDialog} from './ExportCarDialog'
|
||||
|
||||
@@ -286,7 +287,7 @@ export function SettingsScreen({}: Props) {
|
||||
const {mutate: onPressDeleteChatDeclaration} = useDeleteActorDeclaration()
|
||||
|
||||
return (
|
||||
<View style={s.hContentRegion} testID="settingsScreen">
|
||||
<Layout.Screen testID="settingsScreen">
|
||||
<ExportCarDialog control={exportCarControl} />
|
||||
<BirthDateSettingsDialog control={birthdayControl} />
|
||||
|
||||
@@ -919,7 +920,7 @@ export function SettingsScreen({}: Props) {
|
||||
</View>
|
||||
<View style={s.footerSpacer} />
|
||||
</ScrollView>
|
||||
</View>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user