Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 12b41fd162 | |||
| a8fcc1d1a1 | |||
| 4c3c10d7f8 | |||
| 2d88463453 | |||
| 0f40013963 | |||
| ef5bc5243e | |||
| 240535fd8b | |||
| 0b4dc64c63 | |||
| a445489b53 | |||
| db7b875c52 | |||
| 432dc867f8 | |||
| 830b4bee9c | |||
| 7f3b5366e6 | |||
| 8311a7a3d4 | |||
| b75c9d2ef6 | |||
| f40b8ac80c | |||
| d6322477fe | |||
| 8e16427497 | |||
| eaba658419 | |||
| 0f7cd2b74d | |||
| 907b8d17f5 | |||
| e53b972957 | |||
| 157011efe3 | |||
| f7852d02ba | |||
| 7e5c522718 | |||
| 8c6384175c | |||
| 8547bb268c | |||
| 0aa76be70f | |||
| e1e148d42a | |||
| 242b400bb9 | |||
| dcd9b0a528 | |||
| c7e569b73b |
+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,
|
||||
|
||||
@@ -37,7 +37,7 @@ export function GifSelectDialog({
|
||||
onSelectGif: onSelectGifProp,
|
||||
}: {
|
||||
controlRef: React.RefObject<{open: () => void}>
|
||||
onClose: () => void
|
||||
onClose?: () => void
|
||||
onSelectGif: (gif: Gif) => void
|
||||
}) {
|
||||
const control = Dialog.useDialogControl()
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -36,7 +36,7 @@ export function Group({children, multiple, ...props}: GroupProps) {
|
||||
|
||||
export function Button({children, ...props}: ItemProps) {
|
||||
return (
|
||||
<Toggle.Item {...props} style={[a.flex_grow]}>
|
||||
<Toggle.Item {...props} style={[a.flex_grow, a.flex_1]}>
|
||||
<ButtonInner>{children}</ButtonInner>
|
||||
</Toggle.Item>
|
||||
)
|
||||
|
||||
+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,103 +12,105 @@ export function SharedPreferencesTesterScreen() {
|
||||
const [currentTestOutput, setCurrentTestOutput] = React.useState<string>('')
|
||||
|
||||
return (
|
||||
<ScrollView contentContainerStyle={{backgroundColor: 'red'}}>
|
||||
<View style={[a.flex_1]}>
|
||||
<View>
|
||||
<Text testID="testOutput">{currentTestOutput}</Text>
|
||||
<Layout.Screen>
|
||||
<ScrollView contentContainerStyle={{backgroundColor: 'red'}}>
|
||||
<View style={[a.flex_1]}>
|
||||
<View>
|
||||
<Text testID="testOutput">{currentTestOutput}</Text>
|
||||
</View>
|
||||
<View style={[a.flex_wrap]}>
|
||||
<Button
|
||||
label="btn"
|
||||
testID="setStringBtn"
|
||||
style={[a.self_center]}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="small"
|
||||
onPress={async () => {
|
||||
SharedPrefs.removeValue('testerString')
|
||||
SharedPrefs.setValue('testerString', 'Hello')
|
||||
const str = SharedPrefs.getString('testerString')
|
||||
console.log(JSON.stringify(str))
|
||||
setCurrentTestOutput(`${str}`)
|
||||
}}>
|
||||
<ButtonText>Set String</ButtonText>
|
||||
</Button>
|
||||
<Button
|
||||
label="btn"
|
||||
testID="removeStringBtn"
|
||||
style={[a.self_center]}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="small"
|
||||
onPress={async () => {
|
||||
SharedPrefs.removeValue('testerString')
|
||||
const str = SharedPrefs.getString('testerString')
|
||||
setCurrentTestOutput(`${str}`)
|
||||
}}>
|
||||
<ButtonText>Remove String</ButtonText>
|
||||
</Button>
|
||||
<Button
|
||||
label="btn"
|
||||
testID="setBoolBtn"
|
||||
style={[a.self_center]}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="small"
|
||||
onPress={async () => {
|
||||
SharedPrefs.removeValue('testerBool')
|
||||
SharedPrefs.setValue('testerBool', true)
|
||||
const bool = SharedPrefs.getBool('testerBool')
|
||||
setCurrentTestOutput(`${bool}`)
|
||||
}}>
|
||||
<ButtonText>Set Bool</ButtonText>
|
||||
</Button>
|
||||
<Button
|
||||
label="btn"
|
||||
testID="setNumberBtn"
|
||||
style={[a.self_center]}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="small"
|
||||
onPress={async () => {
|
||||
SharedPrefs.removeValue('testerNumber')
|
||||
SharedPrefs.setValue('testerNumber', 123)
|
||||
const num = SharedPrefs.getNumber('testerNumber')
|
||||
setCurrentTestOutput(`${num}`)
|
||||
}}>
|
||||
<ButtonText>Set Number</ButtonText>
|
||||
</Button>
|
||||
<Button
|
||||
label="btn"
|
||||
testID="addToSetBtn"
|
||||
style={[a.self_center]}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="small"
|
||||
onPress={async () => {
|
||||
SharedPrefs.removeFromSet('testerSet', 'Hello!')
|
||||
SharedPrefs.addToSet('testerSet', 'Hello!')
|
||||
const contains = SharedPrefs.setContains('testerSet', 'Hello!')
|
||||
setCurrentTestOutput(`${contains}`)
|
||||
}}>
|
||||
<ButtonText>Add to Set</ButtonText>
|
||||
</Button>
|
||||
<Button
|
||||
label="btn"
|
||||
testID="removeFromSetBtn"
|
||||
style={[a.self_center]}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="small"
|
||||
onPress={async () => {
|
||||
SharedPrefs.removeFromSet('testerSet', 'Hello!')
|
||||
const contains = SharedPrefs.setContains('testerSet', 'Hello!')
|
||||
setCurrentTestOutput(`${contains}`)
|
||||
}}>
|
||||
<ButtonText>Remove from Set</ButtonText>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
<View style={[a.flex_wrap]}>
|
||||
<Button
|
||||
label="btn"
|
||||
testID="setStringBtn"
|
||||
style={[a.self_center]}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="small"
|
||||
onPress={async () => {
|
||||
SharedPrefs.removeValue('testerString')
|
||||
SharedPrefs.setValue('testerString', 'Hello')
|
||||
const str = SharedPrefs.getString('testerString')
|
||||
console.log(JSON.stringify(str))
|
||||
setCurrentTestOutput(`${str}`)
|
||||
}}>
|
||||
<ButtonText>Set String</ButtonText>
|
||||
</Button>
|
||||
<Button
|
||||
label="btn"
|
||||
testID="removeStringBtn"
|
||||
style={[a.self_center]}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="small"
|
||||
onPress={async () => {
|
||||
SharedPrefs.removeValue('testerString')
|
||||
const str = SharedPrefs.getString('testerString')
|
||||
setCurrentTestOutput(`${str}`)
|
||||
}}>
|
||||
<ButtonText>Remove String</ButtonText>
|
||||
</Button>
|
||||
<Button
|
||||
label="btn"
|
||||
testID="setBoolBtn"
|
||||
style={[a.self_center]}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="small"
|
||||
onPress={async () => {
|
||||
SharedPrefs.removeValue('testerBool')
|
||||
SharedPrefs.setValue('testerBool', true)
|
||||
const bool = SharedPrefs.getBool('testerBool')
|
||||
setCurrentTestOutput(`${bool}`)
|
||||
}}>
|
||||
<ButtonText>Set Bool</ButtonText>
|
||||
</Button>
|
||||
<Button
|
||||
label="btn"
|
||||
testID="setNumberBtn"
|
||||
style={[a.self_center]}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="small"
|
||||
onPress={async () => {
|
||||
SharedPrefs.removeValue('testerNumber')
|
||||
SharedPrefs.setValue('testerNumber', 123)
|
||||
const num = SharedPrefs.getNumber('testerNumber')
|
||||
setCurrentTestOutput(`${num}`)
|
||||
}}>
|
||||
<ButtonText>Set Number</ButtonText>
|
||||
</Button>
|
||||
<Button
|
||||
label="btn"
|
||||
testID="addToSetBtn"
|
||||
style={[a.self_center]}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="small"
|
||||
onPress={async () => {
|
||||
SharedPrefs.removeFromSet('testerSet', 'Hello!')
|
||||
SharedPrefs.addToSet('testerSet', 'Hello!')
|
||||
const contains = SharedPrefs.setContains('testerSet', 'Hello!')
|
||||
setCurrentTestOutput(`${contains}`)
|
||||
}}>
|
||||
<ButtonText>Add to Set</ButtonText>
|
||||
</Button>
|
||||
<Button
|
||||
label="btn"
|
||||
testID="removeFromSetBtn"
|
||||
style={[a.self_center]}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="small"
|
||||
onPress={async () => {
|
||||
SharedPrefs.removeFromSet('testerSet', 'Hello!')
|
||||
const contains = SharedPrefs.setContains('testerSet', 'Hello!')
|
||||
setCurrentTestOutput(`${contains}`)
|
||||
}}>
|
||||
<ButtonText>Remove from Set</ButtonText>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</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 (
|
||||
<ConvoProvider key={convoId} convoId={convoId}>
|
||||
<Inner />
|
||||
</ConvoProvider>
|
||||
<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,100 +56,93 @@ export function MessagesSettingsScreen({}: Props) {
|
||||
)
|
||||
|
||||
return (
|
||||
<ScrollView stickyHeaderIndices={[0]}>
|
||||
<ViewHeader title={_(msg`Chat Settings`)} showOnDesktop showBorder />
|
||||
<View style={[a.p_lg, a.gap_md]}>
|
||||
<Text style={[a.text_lg, a.font_bold]}>
|
||||
<Trans>Allow new messages from</Trans>
|
||||
</Text>
|
||||
<Toggle.Group
|
||||
label={_(msg`Allow new messages from`)}
|
||||
type="radio"
|
||||
values={[
|
||||
(profile?.associated?.chat?.allowIncoming as AllowIncoming) ??
|
||||
'following',
|
||||
]}
|
||||
onChange={onSelectMessagesFrom}>
|
||||
<View>
|
||||
<Toggle.Item
|
||||
name="all"
|
||||
label={_(msg`Everyone`)}
|
||||
style={[a.justify_between, a.py_sm]}>
|
||||
<Toggle.LabelText>
|
||||
<Trans>Everyone</Trans>
|
||||
</Toggle.LabelText>
|
||||
<Toggle.Radio />
|
||||
</Toggle.Item>
|
||||
<Toggle.Item
|
||||
name="following"
|
||||
label={_(msg`Users I follow`)}
|
||||
style={[a.justify_between, a.py_sm]}>
|
||||
<Toggle.LabelText>
|
||||
<Trans>Users I follow</Trans>
|
||||
</Toggle.LabelText>
|
||||
<Toggle.Radio />
|
||||
</Toggle.Item>
|
||||
<Toggle.Item
|
||||
name="none"
|
||||
label={_(msg`No one`)}
|
||||
style={[a.justify_between, a.py_sm]}>
|
||||
<Toggle.LabelText>
|
||||
<Trans>No one</Trans>
|
||||
</Toggle.LabelText>
|
||||
<Toggle.Radio />
|
||||
</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]}>
|
||||
<Layout.Screen testID="messagesSettingsScreen">
|
||||
<ScrollView stickyHeaderIndices={[0]}>
|
||||
<ViewHeader title={_(msg`Chat Settings`)} showOnDesktop showBorder />
|
||||
<View style={[a.p_lg, a.gap_md]}>
|
||||
<Text style={[a.text_lg, a.font_bold]}>
|
||||
<Trans>Allow new messages from</Trans>
|
||||
</Text>
|
||||
<Toggle.Group
|
||||
label={_(msg`Allow new messages from`)}
|
||||
type="radio"
|
||||
values={[
|
||||
(profile?.associated?.chat?.allowIncoming as AllowIncoming) ??
|
||||
'following',
|
||||
]}
|
||||
onChange={onSelectMessagesFrom}>
|
||||
<View>
|
||||
<Toggle.Item
|
||||
name="all"
|
||||
label={_(msg`Everyone`)}
|
||||
style={[a.justify_between, a.py_sm]}>
|
||||
<Toggle.LabelText>
|
||||
<Trans>Everyone</Trans>
|
||||
</Toggle.LabelText>
|
||||
<Toggle.Radio />
|
||||
</Toggle.Item>
|
||||
<Toggle.Item
|
||||
name="following"
|
||||
label={_(msg`Users I follow`)}
|
||||
style={[a.justify_between, a.py_sm]}>
|
||||
<Toggle.LabelText>
|
||||
<Trans>Users I follow</Trans>
|
||||
</Toggle.LabelText>
|
||||
<Toggle.Radio />
|
||||
</Toggle.Item>
|
||||
<Toggle.Item
|
||||
name="none"
|
||||
label={_(msg`No one`)}
|
||||
style={[a.justify_between, a.py_sm]}>
|
||||
<Toggle.LabelText>
|
||||
<Trans>No one</Trans>
|
||||
</Toggle.LabelText>
|
||||
<Toggle.Radio />
|
||||
</Toggle.Item>
|
||||
</View>
|
||||
</Toggle.Group>
|
||||
<Admonition type="tip">
|
||||
<Trans>
|
||||
You can continue ongoing conversations regardless of which setting
|
||||
you choose.
|
||||
</Trans>
|
||||
</Text>
|
||||
</Admonition>
|
||||
{isNative && (
|
||||
<>
|
||||
<Divider style={a.my_md} />
|
||||
<Text style={[a.text_lg, a.font_bold]}>
|
||||
<Trans>Notification Sounds</Trans>
|
||||
</Text>
|
||||
<Toggle.Group
|
||||
label={_(msg`Notification sounds`)}
|
||||
type="radio"
|
||||
values={[preferences.playSoundChat ? 'enabled' : 'disabled']}
|
||||
onChange={onSelectSoundSetting}>
|
||||
<View>
|
||||
<Toggle.Item
|
||||
name="enabled"
|
||||
label={_(msg`Enabled`)}
|
||||
style={[a.justify_between, a.py_sm]}>
|
||||
<Toggle.LabelText>
|
||||
<Trans>Enabled</Trans>
|
||||
</Toggle.LabelText>
|
||||
<Toggle.Radio />
|
||||
</Toggle.Item>
|
||||
<Toggle.Item
|
||||
name="disabled"
|
||||
label={_(msg`Disabled`)}
|
||||
style={[a.justify_between, a.py_sm]}>
|
||||
<Toggle.LabelText>
|
||||
<Trans>Disabled</Trans>
|
||||
</Toggle.LabelText>
|
||||
<Toggle.Radio />
|
||||
</Toggle.Item>
|
||||
</View>
|
||||
</Toggle.Group>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
{isNative && (
|
||||
<>
|
||||
<Divider style={a.my_md} />
|
||||
<Text style={[a.text_lg, a.font_bold]}>
|
||||
<Trans>Notification Sounds</Trans>
|
||||
</Text>
|
||||
<Toggle.Group
|
||||
label={_(msg`Notification sounds`)}
|
||||
type="radio"
|
||||
values={[preferences.playSoundChat ? 'enabled' : 'disabled']}
|
||||
onChange={onSelectSoundSetting}>
|
||||
<View>
|
||||
<Toggle.Item
|
||||
name="enabled"
|
||||
label={_(msg`Enabled`)}
|
||||
style={[a.justify_between, a.py_sm]}>
|
||||
<Toggle.LabelText>
|
||||
<Trans>Enabled</Trans>
|
||||
</Toggle.LabelText>
|
||||
<Toggle.Radio />
|
||||
</Toggle.Item>
|
||||
<Toggle.Item
|
||||
name="disabled"
|
||||
label={_(msg`Disabled`)}
|
||||
style={[a.justify_between, a.py_sm]}>
|
||||
<Toggle.LabelText>
|
||||
<Trans>Disabled</Trans>
|
||||
</Toggle.LabelText>
|
||||
<Toggle.Radio />
|
||||
</Toggle.Item>
|
||||
</View>
|
||||
</Toggle.Group>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</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,31 +95,33 @@ export function ModerationScreen(
|
||||
const error = preferencesError
|
||||
|
||||
return (
|
||||
<CenteredView
|
||||
testID="moderationScreen"
|
||||
style={[
|
||||
t.atoms.border_contrast_low,
|
||||
t.atoms.bg,
|
||||
{minHeight: height},
|
||||
...(gtMobile ? [a.border_l, a.border_r] : []),
|
||||
]}>
|
||||
<ViewHeader title={_(msg`Moderation`)} showOnDesktop />
|
||||
<Layout.Screen testID="moderationScreen">
|
||||
<CenteredView
|
||||
testID="moderationScreen"
|
||||
style={[
|
||||
t.atoms.border_contrast_low,
|
||||
t.atoms.bg,
|
||||
{minHeight: height},
|
||||
...(gtMobile ? [a.border_l, a.border_r] : []),
|
||||
]}>
|
||||
<ViewHeader title={_(msg`Moderation`)} showOnDesktop />
|
||||
|
||||
{isLoading ? (
|
||||
<View style={[a.w_full, a.align_center, a.pt_2xl]}>
|
||||
<Loader size="xl" fill={t.atoms.text.color} />
|
||||
</View>
|
||||
) : error || !preferences ? (
|
||||
<ErrorState
|
||||
error={
|
||||
preferencesError?.toString() ||
|
||||
_(msg`Something went wrong, please try again.`)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<ModerationScreenInner preferences={preferences} />
|
||||
)}
|
||||
</CenteredView>
|
||||
{isLoading ? (
|
||||
<View style={[a.w_full, a.align_center, a.pt_2xl]}>
|
||||
<Loader size="xl" fill={t.atoms.text.color} />
|
||||
</View>
|
||||
) : error || !preferences ? (
|
||||
<ErrorState
|
||||
error={
|
||||
preferencesError?.toString() ||
|
||||
_(msg`Something went wrong, please try again.`)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<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}>
|
||||
<ListHeaderDesktop title={_(msg`Liked By`)} />
|
||||
<ViewHeader title={_(msg`Liked By`)} showBorder={!isWeb} />
|
||||
<PostLikedByComponent uri={uri} />
|
||||
</CenteredView>
|
||||
<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}>
|
||||
<ListHeaderDesktop title={_(msg`Quotes`)} />
|
||||
<ViewHeader title={_(msg`Quotes`)} showBorder={!isWeb} />
|
||||
<PostQuotesComponent uri={uri} />
|
||||
</CenteredView>
|
||||
<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}>
|
||||
<ListHeaderDesktop title={_(msg`Reposted By`)} />
|
||||
<ViewHeader title={_(msg`Reposted By`)} showBorder={!isWeb} />
|
||||
<PostRepostedByComponent uri={uri} />
|
||||
</CenteredView>
|
||||
<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,19 +92,21 @@ export const ProfileKnownFollowersScreen = ({route}: Props) => {
|
||||
|
||||
if (followers.length < 1) {
|
||||
return (
|
||||
<ListMaybePlaceholder
|
||||
isLoading={isDidLoading || isFollowersLoading}
|
||||
isError={isError}
|
||||
emptyType="results"
|
||||
emptyMessage={_(msg`You don't follow any users who follow @${name}.`)}
|
||||
errorMessage={cleanError(resolveError || error)}
|
||||
onRetry={isError ? refetch : undefined}
|
||||
/>
|
||||
<Layout.Screen>
|
||||
<ListMaybePlaceholder
|
||||
isLoading={isDidLoading || isFollowersLoading}
|
||||
isError={isError}
|
||||
emptyType="results"
|
||||
emptyMessage={_(msg`You don't follow any users who follow @${name}.`)}
|
||||
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 (
|
||||
<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>
|
||||
<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,33 +98,39 @@ export function Wizard({
|
||||
|
||||
if (!isReady) {
|
||||
return (
|
||||
<ListMaybePlaceholder
|
||||
isLoading={
|
||||
isLoadingStarterPack || isLoadingProfiles || isLoadingProfile
|
||||
}
|
||||
isError={isErrorStarterPack || isErrorProfiles || isErrorProfile}
|
||||
errorMessage={_(msg`That starter pack could not be found.`)}
|
||||
/>
|
||||
<Layout.Screen>
|
||||
<ListMaybePlaceholder
|
||||
isLoading={
|
||||
isLoadingStarterPack || isLoadingProfiles || isLoadingProfile
|
||||
}
|
||||
isError={isErrorStarterPack || isErrorProfiles || isErrorProfile}
|
||||
errorMessage={_(msg`That starter pack could not be found.`)}
|
||||
/>
|
||||
</Layout.Screen>
|
||||
)
|
||||
} else if (isEdit && starterPack?.creator.did !== currentAccount?.did) {
|
||||
return (
|
||||
<ListMaybePlaceholder
|
||||
isLoading={false}
|
||||
isError={true}
|
||||
errorMessage={_(msg`That starter pack could not be found.`)}
|
||||
/>
|
||||
<Layout.Screen>
|
||||
<ListMaybePlaceholder
|
||||
isLoading={false}
|
||||
isError={true}
|
||||
errorMessage={_(msg`That starter pack could not be found.`)}
|
||||
/>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Provider starterPack={starterPack} listItems={listItems}>
|
||||
<WizardInner
|
||||
currentStarterPack={starterPack}
|
||||
currentListItems={listItems}
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
/>
|
||||
</Provider>
|
||||
<Layout.Screen>
|
||||
<Provider starterPack={starterPack} listItems={listItems}>
|
||||
<WizardInner
|
||||
currentStarterPack={starterPack}
|
||||
currentListItems={listItems}
|
||||
profile={profile}
|
||||
moderationOpts={moderationOpts}
|
||||
/>
|
||||
</Provider>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -48,13 +48,6 @@ export interface DeleteAccountModal {
|
||||
name: 'delete-account'
|
||||
}
|
||||
|
||||
export interface SelfLabelModal {
|
||||
name: 'self-label'
|
||||
labels: string[]
|
||||
hasMedia: boolean
|
||||
onChange: (labels: string[]) => void
|
||||
}
|
||||
|
||||
export interface ChangeHandleModal {
|
||||
name: 'change-handle'
|
||||
onChanged: () => void
|
||||
@@ -127,7 +120,6 @@ export type Modal =
|
||||
|
||||
// Posts
|
||||
| CropImageModal
|
||||
| SelfLabelModal
|
||||
|
||||
// Bluesky access
|
||||
| WaitlistModal
|
||||
|
||||
@@ -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,
|
||||
@@ -497,10 +499,6 @@ export const ComposePost = ({
|
||||
openEmojiPicker?.(textInput.current?.getCursorPosition())
|
||||
}, [openEmojiPicker])
|
||||
|
||||
const focusTextInput = useCallback(() => {
|
||||
textInput.current?.focus()
|
||||
}, [])
|
||||
|
||||
const onSelectGif = useCallback((gif: Gif) => {
|
||||
dispatch({type: 'embed_add_gif', gif})
|
||||
}, [])
|
||||
@@ -806,11 +804,7 @@ export const ComposePost = ({
|
||||
disabled={!canSelectImages}
|
||||
onAdd={onImageAdd}
|
||||
/>
|
||||
<SelectGifBtn
|
||||
onClose={focusTextInput}
|
||||
onSelectGif={onSelectGif}
|
||||
disabled={hasMedia}
|
||||
/>
|
||||
<SelectGifBtn onSelectGif={onSelectGif} disabled={hasMedia} />
|
||||
{!isMobile ? (
|
||||
<Button
|
||||
onPress={onEmojiButtonPress}
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
onPress={onRemove}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Remove attachment`)}
|
||||
accessibilityHint={_(msg`Removes the attachment`)}
|
||||
onAccessibilityEscape={onRemove}>
|
||||
<FontAwesomeIcon size={18} icon="xmark" style={s.white} />
|
||||
</TouchableOpacity>
|
||||
<View style={[a.absolute, a.pt_sm, a.pr_sm, {top: 0, right: 0}]}>
|
||||
<Button
|
||||
label={_(msg`Remove attachment`)}
|
||||
onPress={onRemove}
|
||||
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>
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import React from 'react'
|
||||
import {Keyboard, StyleSheet} from 'react-native'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {Keyboard, LayoutAnimation, View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {ShieldExclamation} from 'lib/icons'
|
||||
import {isNative} from 'platform/detection'
|
||||
import {Button} from 'view/com/util/forms/Button'
|
||||
import {ShieldExclamation} from '#/lib/icons'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import * as ToggleButton from '#/components/forms/ToggleButton'
|
||||
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn']
|
||||
|
||||
export function LabelsBtn({
|
||||
labels,
|
||||
@@ -20,48 +22,176 @@ export function LabelsBtn({
|
||||
hasMedia: boolean
|
||||
onChange: (v: string[]) => void
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const control = Dialog.useDialogControl()
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {openModal} = useModalControls()
|
||||
|
||||
const removeAdultLabel = () => {
|
||||
const final = labels.filter(l => !ADULT_CONTENT_LABELS.includes(l))
|
||||
onChange(final)
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
}
|
||||
|
||||
const hasAdultSelection =
|
||||
labels.includes('sexual') ||
|
||||
labels.includes('nudity') ||
|
||||
labels.includes('porn')
|
||||
|
||||
if (!hasMedia && hasAdultSelection) {
|
||||
removeAdultLabel()
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
type="default-light"
|
||||
testID="labelsBtn"
|
||||
style={[styles.button, !hasMedia && styles.dimmed]}
|
||||
accessibilityLabel={_(msg`Content warnings`)}
|
||||
accessibilityHint=""
|
||||
onPress={() => {
|
||||
if (isNative) {
|
||||
if (Keyboard.isVisible()) {
|
||||
Keyboard.dismiss()
|
||||
}
|
||||
}
|
||||
openModal({name: 'self-label', labels, hasMedia, onChange})
|
||||
}}>
|
||||
<ShieldExclamation style={pal.link} size={24} />
|
||||
{labels.length > 0 ? (
|
||||
<FontAwesomeIcon
|
||||
icon="check"
|
||||
size={16}
|
||||
style={pal.link as FontAwesomeIconStyle}
|
||||
<>
|
||||
<Button
|
||||
testID="labelsBtn"
|
||||
style={!hasMedia && {opacity: 0.4}}
|
||||
label={_(msg`Content warnings`)}
|
||||
accessibilityHint={_(
|
||||
msg`Opens a dialog to add a content warning to your post`,
|
||||
)}
|
||||
onPress={() => {
|
||||
Keyboard.dismiss()
|
||||
control.open()
|
||||
}}>
|
||||
<ShieldExclamation style={{color: t.palette.primary_500}} size={24} />
|
||||
{labels.length > 0 ? (
|
||||
<Check size="sm" fill={t.palette.primary_500} />
|
||||
) : null}
|
||||
</Button>
|
||||
|
||||
<Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
|
||||
<Dialog.Handle />
|
||||
<DialogInner
|
||||
labels={labels}
|
||||
onChange={onChange}
|
||||
hasAdultSelection={hasAdultSelection}
|
||||
hasMedia={hasMedia}
|
||||
removeAdultLabel={removeAdultLabel}
|
||||
/>
|
||||
) : null}
|
||||
</Button>
|
||||
</Dialog.Outer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
button: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 6,
|
||||
},
|
||||
dimmed: {
|
||||
opacity: 0.4,
|
||||
},
|
||||
label: {
|
||||
maxWidth: 100,
|
||||
},
|
||||
})
|
||||
function DialogInner({
|
||||
labels,
|
||||
onChange,
|
||||
hasAdultSelection,
|
||||
hasMedia,
|
||||
removeAdultLabel,
|
||||
}: {
|
||||
labels: string[]
|
||||
onChange: (v: string[]) => void
|
||||
hasAdultSelection: boolean
|
||||
hasMedia: boolean
|
||||
removeAdultLabel: () => void
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const control = Dialog.useDialogContext()
|
||||
const t = useTheme()
|
||||
|
||||
return (
|
||||
<Dialog.ScrollableInner
|
||||
label={_(msg`Add a content warning`)}
|
||||
style={[{maxWidth: 500}, a.w_full]}>
|
||||
<View style={[a.flex_1, a.gap_md]}>
|
||||
<Text style={[a.text_2xl, a.font_bold]}>
|
||||
<Trans>Add a content warning</Trans>
|
||||
</Text>
|
||||
|
||||
{!hasMedia ? (
|
||||
<View>
|
||||
<Text style={t.atoms.text_contrast_medium}>
|
||||
<Trans>
|
||||
There are no self-labels that can be applied to this post.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<View style={[a.p_sm, t.atoms.border_contrast_high]}>
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.justify_between,
|
||||
a.pb_sm,
|
||||
]}>
|
||||
<Text style={[a.font_bold, a.text_lg]}>
|
||||
<Trans>Adult Content</Trans>
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
label={_(msg`Remove`)}
|
||||
variant="ghost"
|
||||
color="primary"
|
||||
size="tiny"
|
||||
onPress={removeAdultLabel}
|
||||
disabled={!hasAdultSelection}
|
||||
style={{opacity: hasAdultSelection ? 1 : 0}}
|
||||
aria-hidden={!hasAdultSelection}>
|
||||
<ButtonText>
|
||||
<Trans>Remove</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</View>
|
||||
<ToggleButton.Group
|
||||
label={_(msg`Adult Content labels`)}
|
||||
values={labels}
|
||||
onChange={values => {
|
||||
onChange(values)
|
||||
LayoutAnimation.configureNext(
|
||||
LayoutAnimation.Presets.easeInEaseOut,
|
||||
)
|
||||
}}>
|
||||
<ToggleButton.Button name="sexual" label={_(msg`Suggestive`)}>
|
||||
<ToggleButton.ButtonText>
|
||||
<Trans>Suggestive</Trans>
|
||||
</ToggleButton.ButtonText>
|
||||
</ToggleButton.Button>
|
||||
<ToggleButton.Button name="nudity" label={_(msg`Nudity`)}>
|
||||
<ToggleButton.ButtonText>
|
||||
<Trans>Nudity</Trans>
|
||||
</ToggleButton.ButtonText>
|
||||
</ToggleButton.Button>
|
||||
<ToggleButton.Button name="porn" label={_(msg`Porn`)}>
|
||||
<ToggleButton.ButtonText>
|
||||
<Trans>Porn</Trans>
|
||||
</ToggleButton.ButtonText>
|
||||
</ToggleButton.Button>
|
||||
</ToggleButton.Group>
|
||||
|
||||
<Text style={[a.mt_sm, t.atoms.text_contrast_medium]}>
|
||||
{labels.includes('sexual') ? (
|
||||
<Trans>Pictures meant for adults.</Trans>
|
||||
) : labels.includes('nudity') ? (
|
||||
<Trans>Artistic or non-erotic nudity.</Trans>
|
||||
) : labels.includes('porn') ? (
|
||||
<Trans>Sexual activity or erotic nudity.</Trans>
|
||||
) : (
|
||||
<Trans>
|
||||
If none are selected, this post is suitable for all
|
||||
audiences.
|
||||
</Trans>
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<Button
|
||||
label={_(msg`Done`)}
|
||||
onPress={() => control.close()}
|
||||
color="primary"
|
||||
size="large"
|
||||
variant="solid"
|
||||
style={a.mt_xl}>
|
||||
<ButtonText>
|
||||
<Trans>Done</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
</Dialog.ScrollableInner>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {GifSelectDialog} from '#/components/dialogs/GifSelect'
|
||||
import {GifSquare_Stroke2_Corner0_Rounded as GifIcon} from '#/components/icons/Gif'
|
||||
|
||||
type Props = {
|
||||
onClose: () => void
|
||||
onClose?: () => void
|
||||
onSelectGif: (gif: Gif) => void
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -20,7 +20,6 @@ import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguages
|
||||
import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings'
|
||||
import * as LinkWarningModal from './LinkWarning'
|
||||
import * as ListAddUserModal from './ListAddRemoveUsers'
|
||||
import * as SelfLabelModal from './SelfLabel'
|
||||
import * as UserAddRemoveListsModal from './UserAddRemoveLists'
|
||||
import * as VerifyEmailModal from './VerifyEmail'
|
||||
|
||||
@@ -70,9 +69,6 @@ export function ModalsContainer() {
|
||||
} else if (activeModal?.name === 'delete-account') {
|
||||
snapPoints = DeleteAccountModal.snapPoints
|
||||
element = <DeleteAccountModal.Component />
|
||||
} else if (activeModal?.name === 'self-label') {
|
||||
snapPoints = SelfLabelModal.snapPoints
|
||||
element = <SelfLabelModal.Component {...activeModal} />
|
||||
} else if (activeModal?.name === 'change-handle') {
|
||||
snapPoints = ChangeHandleModal.snapPoints
|
||||
element = <ChangeHandleModal.Component {...activeModal} />
|
||||
|
||||
@@ -20,7 +20,6 @@ import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguages
|
||||
import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings'
|
||||
import * as LinkWarningModal from './LinkWarning'
|
||||
import * as ListAddUserModal from './ListAddRemoveUsers'
|
||||
import * as SelfLabelModal from './SelfLabel'
|
||||
import * as UserAddRemoveLists from './UserAddRemoveLists'
|
||||
import * as VerifyEmailModal from './VerifyEmail'
|
||||
|
||||
@@ -75,8 +74,6 @@ function Modal({modal}: {modal: ModalIface}) {
|
||||
element = <CropImageModal.Component {...modal} />
|
||||
} else if (modal.name === 'delete-account') {
|
||||
element = <DeleteAccountModal.Component />
|
||||
} else if (modal.name === 'self-label') {
|
||||
element = <SelfLabelModal.Component {...modal} />
|
||||
} else if (modal.name === 'change-handle') {
|
||||
element = <ChangeHandleModal.Component {...modal} />
|
||||
} else if (modal.name === 'invite-codes') {
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
import React, {useState} from 'react'
|
||||
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {colors, s} from 'lib/styles'
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {ScrollView} from 'view/com/modals/util'
|
||||
import {Button} from '../util/forms/Button'
|
||||
import {SelectableBtn} from '../util/forms/SelectableBtn'
|
||||
import {Text} from '../util/text/Text'
|
||||
|
||||
const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn']
|
||||
|
||||
export const snapPoints = ['50%']
|
||||
|
||||
export function Component({
|
||||
labels,
|
||||
hasMedia,
|
||||
onChange,
|
||||
}: {
|
||||
labels: string[]
|
||||
hasMedia: boolean
|
||||
onChange: (labels: string[]) => void
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const {closeModal} = useModalControls()
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const [selected, setSelected] = useState(labels)
|
||||
const {_} = useLingui()
|
||||
|
||||
const toggleAdultLabel = (label: string) => {
|
||||
const hadLabel = selected.includes(label)
|
||||
const stripped = selected.filter(l => !ADULT_CONTENT_LABELS.includes(l))
|
||||
const final = !hadLabel ? stripped.concat([label]) : stripped
|
||||
setSelected(final)
|
||||
onChange(final)
|
||||
}
|
||||
|
||||
const removeAdultLabel = () => {
|
||||
const final = selected.filter(l => !ADULT_CONTENT_LABELS.includes(l))
|
||||
setSelected(final)
|
||||
onChange(final)
|
||||
}
|
||||
|
||||
const hasAdultSelection =
|
||||
selected.includes('sexual') ||
|
||||
selected.includes('nudity') ||
|
||||
selected.includes('porn')
|
||||
return (
|
||||
<View testID="selfLabelModal" style={[pal.view, styles.container]}>
|
||||
<View style={styles.titleSection}>
|
||||
<Text type="title-lg" style={[pal.text, styles.title]}>
|
||||
<Trans>Add a content warning</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<ScrollView>
|
||||
<View
|
||||
style={[
|
||||
styles.section,
|
||||
pal.border,
|
||||
{borderBottomWidth: 1, paddingHorizontal: isMobile ? 20 : 0},
|
||||
]}>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingBottom: 8,
|
||||
}}>
|
||||
<Text type="title" style={pal.text}>
|
||||
<Trans>Adult Content</Trans>
|
||||
</Text>
|
||||
{hasAdultSelection ? (
|
||||
<Button
|
||||
type="default-light"
|
||||
onPress={removeAdultLabel}
|
||||
style={{paddingTop: 0, paddingBottom: 0, paddingRight: 0}}>
|
||||
<Text type="md" style={pal.link}>
|
||||
<Trans>Remove</Trans>
|
||||
</Text>
|
||||
</Button>
|
||||
) : null}
|
||||
</View>
|
||||
{hasMedia ? (
|
||||
<>
|
||||
<View style={s.flexRow}>
|
||||
<SelectableBtn
|
||||
testID="sexualLabelBtn"
|
||||
selected={selected.includes('sexual')}
|
||||
left
|
||||
label={_(msg`Suggestive`)}
|
||||
onSelect={() => toggleAdultLabel('sexual')}
|
||||
accessibilityHint=""
|
||||
style={s.flex1}
|
||||
/>
|
||||
<SelectableBtn
|
||||
testID="nudityLabelBtn"
|
||||
selected={selected.includes('nudity')}
|
||||
label={_(msg`Nudity`)}
|
||||
onSelect={() => toggleAdultLabel('nudity')}
|
||||
accessibilityHint=""
|
||||
style={s.flex1}
|
||||
/>
|
||||
<SelectableBtn
|
||||
testID="pornLabelBtn"
|
||||
selected={selected.includes('porn')}
|
||||
label={_(msg`Porn`)}
|
||||
right
|
||||
onSelect={() => toggleAdultLabel('porn')}
|
||||
accessibilityHint=""
|
||||
style={s.flex1}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Text style={[pal.text, styles.adultExplainer]}>
|
||||
{selected.includes('sexual') ? (
|
||||
<Trans>Pictures meant for adults.</Trans>
|
||||
) : selected.includes('nudity') ? (
|
||||
<Trans>Artistic or non-erotic nudity.</Trans>
|
||||
) : selected.includes('porn') ? (
|
||||
<Trans>Sexual activity or erotic nudity.</Trans>
|
||||
) : (
|
||||
<Trans>If none are selected, suitable for all ages.</Trans>
|
||||
)}
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<View>
|
||||
<Text style={[pal.textLight]}>
|
||||
<Trans>
|
||||
<Text type="md-bold" style={[pal.textLight]}>
|
||||
Not Applicable.
|
||||
</Text>{' '}
|
||||
This warning is only available for posts with media attached.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
<View style={[styles.btnContainer, pal.borderDark]}>
|
||||
<TouchableOpacity
|
||||
testID="confirmBtn"
|
||||
onPress={() => {
|
||||
closeModal()
|
||||
}}
|
||||
style={styles.btn}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Confirm`)}
|
||||
accessibilityHint="">
|
||||
<Text style={[s.white, s.bold, s.f18]}>
|
||||
<Trans context="action">Done</Trans>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingBottom: isWeb ? 0 : 40,
|
||||
},
|
||||
titleSection: {
|
||||
paddingTop: isWeb ? 0 : 4,
|
||||
paddingBottom: isWeb ? 14 : 10,
|
||||
},
|
||||
title: {
|
||||
textAlign: 'center',
|
||||
fontWeight: '600',
|
||||
marginBottom: 5,
|
||||
},
|
||||
description: {
|
||||
textAlign: 'center',
|
||||
paddingHorizontal: 32,
|
||||
},
|
||||
section: {
|
||||
borderTopWidth: 1,
|
||||
paddingVertical: 20,
|
||||
},
|
||||
adultExplainer: {
|
||||
paddingLeft: 5,
|
||||
paddingTop: 10,
|
||||
},
|
||||
btn: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 32,
|
||||
padding: 14,
|
||||
backgroundColor: colors.blue3,
|
||||
},
|
||||
btnContainer: {
|
||||
paddingTop: 20,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
})
|
||||
@@ -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,122 +50,131 @@ export const ExternalLinkEmbed = ({
|
||||
return params
|
||||
}
|
||||
}, [link.uri, externalEmbedPrefs])
|
||||
const hasMedia = Boolean(imageUri || embedPlayerParams)
|
||||
|
||||
if (embedPlayerParams?.source === 'tenor') {
|
||||
const parsedAlt = parseAltFromGIFDescription(link.description)
|
||||
return (
|
||||
<GifEmbed
|
||||
params={embedPlayerParams}
|
||||
thumb={link.thumb}
|
||||
altText={parsedAlt.alt}
|
||||
isPreferredAltText={parsedAlt.isPreferred}
|
||||
hideAlt={hideAlt}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[a.flex_col, a.rounded_md, a.w_full]}>
|
||||
<LinkWrapper link={link} onOpen={onOpen} style={style}>
|
||||
{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],
|
||||
]}>
|
||||
<Text
|
||||
type="sm"
|
||||
numberOfLines={1}
|
||||
style={[pal.textLight, {marginVertical: 2}]}>
|
||||
{toNiceDomain(link.uri)}
|
||||
</Text>
|
||||
|
||||
{!embedPlayerParams?.isGif && !embedPlayerParams?.dimensions && (
|
||||
<Text emoji type="lg-bold" numberOfLines={3} style={[pal.text]}>
|
||||
{link.title || link.uri}
|
||||
</Text>
|
||||
)}
|
||||
{link.description ? (
|
||||
<Text
|
||||
emoji
|
||||
type="md"
|
||||
numberOfLines={link.thumb ? 2 : 4}
|
||||
style={[pal.text, a.mt_xs]}>
|
||||
{link.description}
|
||||
</Text>
|
||||
) : undefined}
|
||||
</View>
|
||||
</LinkWrapper>
|
||||
</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])
|
||||
|
||||
if (embedPlayerParams?.source === 'tenor') {
|
||||
const parsedAlt = parseAltFromGIFDescription(link.description)
|
||||
return (
|
||||
<View style={style}>
|
||||
<GifEmbed
|
||||
params={embedPlayerParams}
|
||||
thumb={link.thumb}
|
||||
altText={parsedAlt.alt}
|
||||
isPreferredAltText={parsedAlt.isPreferred}
|
||||
hideAlt={hideAlt}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
asAnchor
|
||||
anchorNoUnderline
|
||||
href={link.uri}
|
||||
style={[a.flex_1, a.rounded_sm, style]}
|
||||
onBeforePress={onOpen}
|
||||
label={link.title || _(msg`Open link to ${niceUrl}`)}
|
||||
to={link.uri}
|
||||
onPress={onOpen}
|
||||
onLongPress={onShareExternal}>
|
||||
{children}
|
||||
{({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 ? (
|
||||
<Image
|
||||
style={{
|
||||
aspectRatio: 1.91,
|
||||
}}
|
||||
source={{uri: imageUri}}
|
||||
accessibilityIgnoresInvertColors
|
||||
/>
|
||||
) : undefined}
|
||||
|
||||
{embedPlayerParams?.isGif ? (
|
||||
<ExternalGifEmbed link={link} params={embedPlayerParams} />
|
||||
) : embedPlayerParams ? (
|
||||
<ExternalPlayer link={link} params={embedPlayerParams} />
|
||||
) : undefined}
|
||||
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.pt_sm,
|
||||
{gap: 3},
|
||||
hasMedia && a.border_t,
|
||||
hovered
|
||||
? t.atoms.border_contrast_high
|
||||
: t.atoms.border_contrast_low,
|
||||
]}>
|
||||
<View style={[{gap: 3}, a.pb_xs, a.px_md]}>
|
||||
{!embedPlayerParams?.isGif && !embedPlayerParams?.dimensions && (
|
||||
<Text
|
||||
emoji
|
||||
numberOfLines={3}
|
||||
style={[a.text_md, a.font_bold, a.leading_snug]}>
|
||||
{link.title || link.uri}
|
||||
</Text>
|
||||
)}
|
||||
{link.description ? (
|
||||
<Text
|
||||
emoji
|
||||
numberOfLines={link.thumb ? 2 : 4}
|
||||
style={[a.text_sm, a.leading_snug]}>
|
||||
{link.description}
|
||||
</Text>
|
||||
) : undefined}
|
||||
</View>
|
||||
<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>
|
||||
</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.rounded_md,
|
||||
a.overflow_hidden,
|
||||
{aspectRatio: params.dimensions!.width / params.dimensions!.height},
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
+27
-20
@@ -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}>
|
||||
<DebugInner
|
||||
colorScheme={colorScheme}
|
||||
onToggleColorScheme={onToggleColorScheme}
|
||||
/>
|
||||
<Layout.Screen>
|
||||
<DebugInner
|
||||
colorScheme={colorScheme}
|
||||
onToggleColorScheme={onToggleColorScheme}
|
||||
/>
|
||||
</Layout.Screen>
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
|
||||
+293
-276
@@ -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,309 +265,325 @@ export const DebugModScreen = ({}: NativeStackScreenProps<
|
||||
}, [post, modOpts])
|
||||
|
||||
return (
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<Heading title="" subtitle="Scenario" />
|
||||
<ToggleButton.Group
|
||||
label="Scenario"
|
||||
values={scenario}
|
||||
onChange={setScenario}>
|
||||
<ToggleButton.Button name="label" label="Label">
|
||||
<ToggleButton.ButtonText>Label</ToggleButton.ButtonText>
|
||||
</ToggleButton.Button>
|
||||
<ToggleButton.Button name="block" label="Block">
|
||||
<ToggleButton.ButtonText>Block</ToggleButton.ButtonText>
|
||||
</ToggleButton.Button>
|
||||
<ToggleButton.Button name="mute" label="Mute">
|
||||
<ToggleButton.ButtonText>Mute</ToggleButton.ButtonText>
|
||||
</ToggleButton.Button>
|
||||
</ToggleButton.Group>
|
||||
<Heading title="" subtitle="Scenario" />
|
||||
<ToggleButton.Group
|
||||
label="Scenario"
|
||||
values={scenario}
|
||||
onChange={setScenario}>
|
||||
<ToggleButton.Button name="label" label="Label">
|
||||
<ToggleButton.ButtonText>Label</ToggleButton.ButtonText>
|
||||
</ToggleButton.Button>
|
||||
<ToggleButton.Button name="block" label="Block">
|
||||
<ToggleButton.ButtonText>Block</ToggleButton.ButtonText>
|
||||
</ToggleButton.Button>
|
||||
<ToggleButton.Button name="mute" label="Mute">
|
||||
<ToggleButton.ButtonText>Mute</ToggleButton.ButtonText>
|
||||
</ToggleButton.Button>
|
||||
</ToggleButton.Group>
|
||||
|
||||
{scenario[0] === 'label' && (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
a.border,
|
||||
a.rounded_sm,
|
||||
a.mt_lg,
|
||||
a.mb_lg,
|
||||
a.p_lg,
|
||||
t.atoms.border_contrast_medium,
|
||||
]}>
|
||||
<Toggle.Group
|
||||
label="Toggle"
|
||||
type="radio"
|
||||
values={label}
|
||||
onChange={setLabel}>
|
||||
<View style={[a.flex_row, a.gap_md, a.flex_wrap]}>
|
||||
{LABEL_VALUES.map(labelValue => {
|
||||
let targetFixed = target[0]
|
||||
if (
|
||||
targetFixed !== 'account' &&
|
||||
targetFixed !== 'profile'
|
||||
) {
|
||||
targetFixed = 'content'
|
||||
}
|
||||
const disabled =
|
||||
isSelfLabel &&
|
||||
LABELS[labelValue].flags.includes('no-self')
|
||||
return (
|
||||
<Toggle.Item
|
||||
key={labelValue}
|
||||
name={labelValue}
|
||||
label={labelStrings[labelValue].name}
|
||||
disabled={disabled}
|
||||
style={disabled ? {opacity: 0.5} : undefined}>
|
||||
<Toggle.Radio />
|
||||
<Toggle.LabelText>{labelValue}</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
)
|
||||
})}
|
||||
<Toggle.Item
|
||||
name="custom"
|
||||
label="Custom label"
|
||||
disabled={isSelfLabel}
|
||||
style={isSelfLabel ? {opacity: 0.5} : undefined}>
|
||||
<Toggle.Radio />
|
||||
<Toggle.LabelText>Custom label</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
</View>
|
||||
</Toggle.Group>
|
||||
|
||||
{label[0] === 'custom' ? (
|
||||
<CustomLabelForm
|
||||
def={customLabelDef}
|
||||
setDef={setCustomLabelDef}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<View style={{height: 10}} />
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
|
||||
<View style={{height: 10}} />
|
||||
|
||||
<SmallToggler label="Advanced">
|
||||
{scenario[0] === 'label' && (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
a.border,
|
||||
a.rounded_sm,
|
||||
a.mt_lg,
|
||||
a.mb_lg,
|
||||
a.p_lg,
|
||||
t.atoms.border_contrast_medium,
|
||||
]}>
|
||||
<Toggle.Group
|
||||
label="Toggle"
|
||||
type="checkbox"
|
||||
values={scenarioSwitches}
|
||||
onChange={setScenarioSwitches}>
|
||||
<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>
|
||||
</Toggle.Item>
|
||||
<Toggle.Item name="following" label="Following target">
|
||||
<Toggle.Checkbox />
|
||||
<Toggle.LabelText>Following target</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
<Toggle.Item name="selfLabel" label="Self label">
|
||||
<Toggle.Checkbox />
|
||||
<Toggle.LabelText>Self label</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
<Toggle.Item name="noAdult" label="Adult disabled">
|
||||
<Toggle.Checkbox />
|
||||
<Toggle.LabelText>Adult disabled</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
<Toggle.Item name="loggedOut" label="Logged out">
|
||||
<Toggle.Checkbox />
|
||||
<Toggle.LabelText>Logged out</Toggle.LabelText>
|
||||
type="radio"
|
||||
values={label}
|
||||
onChange={setLabel}>
|
||||
<View style={[a.flex_row, a.gap_md, a.flex_wrap]}>
|
||||
{LABEL_VALUES.map(labelValue => {
|
||||
let targetFixed = target[0]
|
||||
if (
|
||||
targetFixed !== 'account' &&
|
||||
targetFixed !== 'profile'
|
||||
) {
|
||||
targetFixed = 'content'
|
||||
}
|
||||
const disabled =
|
||||
isSelfLabel &&
|
||||
LABELS[labelValue].flags.includes('no-self')
|
||||
return (
|
||||
<Toggle.Item
|
||||
key={labelValue}
|
||||
name={labelValue}
|
||||
label={labelStrings[labelValue].name}
|
||||
disabled={disabled}
|
||||
style={disabled ? {opacity: 0.5} : undefined}>
|
||||
<Toggle.Radio />
|
||||
<Toggle.LabelText>{labelValue}</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
)
|
||||
})}
|
||||
<Toggle.Item
|
||||
name="custom"
|
||||
label="Custom label"
|
||||
disabled={isSelfLabel}
|
||||
style={isSelfLabel ? {opacity: 0.5} : undefined}>
|
||||
<Toggle.Radio />
|
||||
<Toggle.LabelText>Custom label</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
</View>
|
||||
</Toggle.Group>
|
||||
|
||||
{LABELS[label[0] as keyof typeof LABELS]?.configurable !==
|
||||
false && (
|
||||
<View style={[a.mt_md]}>
|
||||
<Text
|
||||
style={[a.font_bold, a.text_xs, t.atoms.text, a.pb_sm]}>
|
||||
Preference
|
||||
</Text>
|
||||
<Toggle.Group
|
||||
label="Preference"
|
||||
type="radio"
|
||||
values={visibility}
|
||||
onChange={setVisiblity}>
|
||||
<View
|
||||
{label[0] === 'custom' ? (
|
||||
<CustomLabelForm
|
||||
def={customLabelDef}
|
||||
setDef={setCustomLabelDef}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<View style={{height: 10}} />
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
|
||||
<View style={{height: 10}} />
|
||||
|
||||
<SmallToggler label="Advanced">
|
||||
<Toggle.Group
|
||||
label="Toggle"
|
||||
type="checkbox"
|
||||
values={scenarioSwitches}
|
||||
onChange={setScenarioSwitches}>
|
||||
<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>
|
||||
</Toggle.Item>
|
||||
<Toggle.Item name="following" label="Following target">
|
||||
<Toggle.Checkbox />
|
||||
<Toggle.LabelText>Following target</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
<Toggle.Item name="selfLabel" label="Self label">
|
||||
<Toggle.Checkbox />
|
||||
<Toggle.LabelText>Self label</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
<Toggle.Item name="noAdult" label="Adult disabled">
|
||||
<Toggle.Checkbox />
|
||||
<Toggle.LabelText>Adult disabled</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
<Toggle.Item name="loggedOut" label="Logged out">
|
||||
<Toggle.Checkbox />
|
||||
<Toggle.LabelText>Logged out</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
</View>
|
||||
</Toggle.Group>
|
||||
|
||||
{LABELS[label[0] as keyof typeof LABELS]?.configurable !==
|
||||
false && (
|
||||
<View style={[a.mt_md]}>
|
||||
<Text
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.gap_md,
|
||||
a.flex_wrap,
|
||||
a.align_center,
|
||||
a.font_bold,
|
||||
a.text_xs,
|
||||
t.atoms.text,
|
||||
a.pb_sm,
|
||||
]}>
|
||||
<Toggle.Item name="hide" label="Hide">
|
||||
Preference
|
||||
</Text>
|
||||
<Toggle.Group
|
||||
label="Preference"
|
||||
type="radio"
|
||||
values={visibility}
|
||||
onChange={setVisiblity}>
|
||||
<View
|
||||
style={[
|
||||
a.flex_row,
|
||||
a.gap_md,
|
||||
a.flex_wrap,
|
||||
a.align_center,
|
||||
]}>
|
||||
<Toggle.Item name="hide" label="Hide">
|
||||
<Toggle.Radio />
|
||||
<Toggle.LabelText>Hide</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
<Toggle.Item name="warn" label="Warn">
|
||||
<Toggle.Radio />
|
||||
<Toggle.LabelText>Warn</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
<Toggle.Item name="ignore" label="Ignore">
|
||||
<Toggle.Radio />
|
||||
<Toggle.LabelText>Ignore</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
</View>
|
||||
</Toggle.Group>
|
||||
</View>
|
||||
)}
|
||||
</SmallToggler>
|
||||
</View>
|
||||
|
||||
<View style={[a.flex_row, a.flex_wrap, a.gap_md]}>
|
||||
<View>
|
||||
<Text
|
||||
style={[
|
||||
a.font_bold,
|
||||
a.text_xs,
|
||||
t.atoms.text,
|
||||
a.pl_md,
|
||||
a.pb_xs,
|
||||
]}>
|
||||
Target
|
||||
</Text>
|
||||
<View
|
||||
style={[
|
||||
a.border,
|
||||
a.rounded_full,
|
||||
a.px_md,
|
||||
a.py_sm,
|
||||
t.atoms.border_contrast_medium,
|
||||
t.atoms.bg,
|
||||
]}>
|
||||
<Toggle.Group
|
||||
label="Target"
|
||||
type="radio"
|
||||
values={target}
|
||||
onChange={setTarget}>
|
||||
<View style={[a.flex_row, a.gap_md, a.flex_wrap]}>
|
||||
<Toggle.Item name="account" label="Account">
|
||||
<Toggle.Radio />
|
||||
<Toggle.LabelText>Hide</Toggle.LabelText>
|
||||
<Toggle.LabelText>Account</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
<Toggle.Item name="warn" label="Warn">
|
||||
<Toggle.Item name="profile" label="Profile">
|
||||
<Toggle.Radio />
|
||||
<Toggle.LabelText>Warn</Toggle.LabelText>
|
||||
<Toggle.LabelText>Profile</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
<Toggle.Item name="ignore" label="Ignore">
|
||||
<Toggle.Item name="post" label="Post">
|
||||
<Toggle.Radio />
|
||||
<Toggle.LabelText>Ignore</Toggle.LabelText>
|
||||
<Toggle.LabelText>Post</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
<Toggle.Item name="embed" label="Embed">
|
||||
<Toggle.Radio />
|
||||
<Toggle.LabelText>Embed</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
</View>
|
||||
</Toggle.Group>
|
||||
</View>
|
||||
)}
|
||||
</SmallToggler>
|
||||
</View>
|
||||
|
||||
<View style={[a.flex_row, a.flex_wrap, a.gap_md]}>
|
||||
<View>
|
||||
<Text
|
||||
style={[
|
||||
a.font_bold,
|
||||
a.text_xs,
|
||||
t.atoms.text,
|
||||
a.pl_md,
|
||||
a.pb_xs,
|
||||
]}>
|
||||
Target
|
||||
</Text>
|
||||
<View
|
||||
style={[
|
||||
a.border,
|
||||
a.rounded_full,
|
||||
a.px_md,
|
||||
a.py_sm,
|
||||
t.atoms.border_contrast_medium,
|
||||
t.atoms.bg,
|
||||
]}>
|
||||
<Toggle.Group
|
||||
label="Target"
|
||||
type="radio"
|
||||
values={target}
|
||||
onChange={setTarget}>
|
||||
<View style={[a.flex_row, a.gap_md, a.flex_wrap]}>
|
||||
<Toggle.Item name="account" label="Account">
|
||||
<Toggle.Radio />
|
||||
<Toggle.LabelText>Account</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
<Toggle.Item name="profile" label="Profile">
|
||||
<Toggle.Radio />
|
||||
<Toggle.LabelText>Profile</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
<Toggle.Item name="post" label="Post">
|
||||
<Toggle.Radio />
|
||||
<Toggle.LabelText>Post</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
<Toggle.Item name="embed" label="Embed">
|
||||
<Toggle.Radio />
|
||||
<Toggle.LabelText>Embed</Toggle.LabelText>
|
||||
</Toggle.Item>
|
||||
</View>
|
||||
</Toggle.Group>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Spacer />
|
||||
|
||||
<Heading title="" subtitle="Results" />
|
||||
|
||||
<ToggleButton.Group label="Results" values={view} onChange={setView}>
|
||||
<ToggleButton.Button name="post" label="Post">
|
||||
<ToggleButton.ButtonText>Post</ToggleButton.ButtonText>
|
||||
</ToggleButton.Button>
|
||||
<ToggleButton.Button name="notifications" label="Notifications">
|
||||
<ToggleButton.ButtonText>Notifications</ToggleButton.ButtonText>
|
||||
</ToggleButton.Button>
|
||||
<ToggleButton.Button name="account" label="Account">
|
||||
<ToggleButton.ButtonText>Account</ToggleButton.ButtonText>
|
||||
</ToggleButton.Button>
|
||||
<ToggleButton.Button name="data" label="Data">
|
||||
<ToggleButton.ButtonText>Data</ToggleButton.ButtonText>
|
||||
</ToggleButton.Button>
|
||||
</ToggleButton.Group>
|
||||
|
||||
<View
|
||||
style={[
|
||||
a.border,
|
||||
a.rounded_sm,
|
||||
a.mt_lg,
|
||||
a.p_md,
|
||||
t.atoms.border_contrast_medium,
|
||||
]}>
|
||||
{view[0] === 'post' && (
|
||||
<>
|
||||
<Heading title="Post" subtitle="in feed" />
|
||||
<MockPostFeedItem post={post} moderation={postModeration} />
|
||||
|
||||
<Heading title="Post" subtitle="viewed directly" />
|
||||
<MockPostThreadItem post={post} moderation={postModeration} />
|
||||
|
||||
<Heading title="Post" subtitle="reply in thread" />
|
||||
<MockPostThreadItem
|
||||
post={post}
|
||||
moderation={postModeration}
|
||||
reply
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{view[0] === 'notifications' && (
|
||||
<>
|
||||
<Heading title="Notification" subtitle="quote or reply" />
|
||||
<MockNotifItem notif={replyNotif} moderationOpts={modOpts} />
|
||||
<View style={{height: 20}} />
|
||||
<Heading title="Notification" subtitle="follow or like" />
|
||||
<MockNotifItem notif={followNotif} moderationOpts={modOpts} />
|
||||
</>
|
||||
)}
|
||||
<Spacer />
|
||||
|
||||
{view[0] === 'account' && (
|
||||
<>
|
||||
<Heading title="Account" subtitle="in listing" />
|
||||
<MockAccountCard
|
||||
profile={profile}
|
||||
moderation={profileModeration}
|
||||
/>
|
||||
<Heading title="" subtitle="Results" />
|
||||
|
||||
<Heading title="Account" subtitle="viewing directly" />
|
||||
<MockAccountScreen
|
||||
profile={profile}
|
||||
moderation={profileModeration}
|
||||
moderationOpts={modOpts}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<ToggleButton.Group
|
||||
label="Results"
|
||||
values={view}
|
||||
onChange={setView}>
|
||||
<ToggleButton.Button name="post" label="Post">
|
||||
<ToggleButton.ButtonText>Post</ToggleButton.ButtonText>
|
||||
</ToggleButton.Button>
|
||||
<ToggleButton.Button name="notifications" label="Notifications">
|
||||
<ToggleButton.ButtonText>Notifications</ToggleButton.ButtonText>
|
||||
</ToggleButton.Button>
|
||||
<ToggleButton.Button name="account" label="Account">
|
||||
<ToggleButton.ButtonText>Account</ToggleButton.ButtonText>
|
||||
</ToggleButton.Button>
|
||||
<ToggleButton.Button name="data" label="Data">
|
||||
<ToggleButton.ButtonText>Data</ToggleButton.ButtonText>
|
||||
</ToggleButton.Button>
|
||||
</ToggleButton.Group>
|
||||
|
||||
{view[0] === 'data' && (
|
||||
<>
|
||||
<ModerationUIView
|
||||
label="Profile Moderation UI"
|
||||
mod={profileModeration}
|
||||
/>
|
||||
<ModerationUIView
|
||||
label="Post Moderation UI"
|
||||
mod={postModeration}
|
||||
/>
|
||||
<DataView
|
||||
label={label[0]}
|
||||
data={LABELS[label[0] as keyof typeof LABELS]}
|
||||
/>
|
||||
<DataView
|
||||
label="Profile Moderation Data"
|
||||
data={profileModeration}
|
||||
/>
|
||||
<DataView label="Post Moderation Data" data={postModeration} />
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
a.border,
|
||||
a.rounded_sm,
|
||||
a.mt_lg,
|
||||
a.p_md,
|
||||
t.atoms.border_contrast_medium,
|
||||
]}>
|
||||
{view[0] === 'post' && (
|
||||
<>
|
||||
<Heading title="Post" subtitle="in feed" />
|
||||
<MockPostFeedItem post={post} moderation={postModeration} />
|
||||
|
||||
<View style={{height: 400}} />
|
||||
</CenteredView>
|
||||
</ScrollView>
|
||||
</moderationOptsOverrideContext.Provider>
|
||||
<Heading title="Post" subtitle="viewed directly" />
|
||||
<MockPostThreadItem post={post} moderation={postModeration} />
|
||||
|
||||
<Heading title="Post" subtitle="reply in thread" />
|
||||
<MockPostThreadItem
|
||||
post={post}
|
||||
moderation={postModeration}
|
||||
reply
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{view[0] === 'notifications' && (
|
||||
<>
|
||||
<Heading title="Notification" subtitle="quote or reply" />
|
||||
<MockNotifItem notif={replyNotif} moderationOpts={modOpts} />
|
||||
<View style={{height: 20}} />
|
||||
<Heading title="Notification" subtitle="follow or like" />
|
||||
<MockNotifItem notif={followNotif} moderationOpts={modOpts} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{view[0] === 'account' && (
|
||||
<>
|
||||
<Heading title="Account" subtitle="in listing" />
|
||||
<MockAccountCard
|
||||
profile={profile}
|
||||
moderation={profileModeration}
|
||||
/>
|
||||
|
||||
<Heading title="Account" subtitle="viewing directly" />
|
||||
<MockAccountScreen
|
||||
profile={profile}
|
||||
moderation={profileModeration}
|
||||
moderationOpts={modOpts}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{view[0] === 'data' && (
|
||||
<>
|
||||
<ModerationUIView
|
||||
label="Profile Moderation UI"
|
||||
mod={profileModeration}
|
||||
/>
|
||||
<ModerationUIView
|
||||
label="Post Moderation UI"
|
||||
mod={postModeration}
|
||||
/>
|
||||
<DataView
|
||||
label={label[0]}
|
||||
data={LABELS[label[0] as keyof typeof LABELS]}
|
||||
/>
|
||||
<DataView
|
||||
label="Profile Moderation Data"
|
||||
data={profileModeration}
|
||||
/>
|
||||
<DataView
|
||||
label="Post Moderation Data"
|
||||
data={postModeration}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={{height: 400}} />
|
||||
</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 (
|
||||
<HomeScreenReady
|
||||
{...props}
|
||||
preferences={preferences}
|
||||
pinnedFeedInfos={pinnedFeedInfos}
|
||||
/>
|
||||
<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,232 +77,234 @@ export function LanguageSettingsScreen(_props: Props) {
|
||||
}, [langPrefs.contentLanguages])
|
||||
|
||||
return (
|
||||
<CenteredView
|
||||
style={[
|
||||
pal.view,
|
||||
pal.border,
|
||||
styles.container,
|
||||
isTabletOrDesktop && styles.desktopContainer,
|
||||
]}>
|
||||
<ViewHeader title={_(msg`Language Settings`)} showOnDesktop />
|
||||
<Layout.Screen testID="PreferencesLanguagesScreen">
|
||||
<CenteredView
|
||||
style={[
|
||||
pal.view,
|
||||
pal.border,
|
||||
styles.container,
|
||||
isTabletOrDesktop && styles.desktopContainer,
|
||||
]}>
|
||||
<ViewHeader title={_(msg`Language Settings`)} showOnDesktop />
|
||||
|
||||
<View style={{paddingTop: 20, paddingHorizontal: 20}}>
|
||||
{/* APP LANGUAGE */}
|
||||
<View style={{paddingBottom: 20}}>
|
||||
<Text type="title-sm" style={[pal.text, s.pb5]}>
|
||||
<Trans>App Language</Trans>
|
||||
</Text>
|
||||
<Text style={[pal.text, s.pb10]}>
|
||||
<Trans>
|
||||
Select your app language for the default text to display in the
|
||||
app.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<View style={{position: 'relative'}}>
|
||||
<RNPickerSelect
|
||||
placeholder={{}}
|
||||
value={sanitizeAppLanguageSetting(langPrefs.appLanguage)}
|
||||
onValueChange={onChangeAppLanguage}
|
||||
items={APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({
|
||||
label: l.name,
|
||||
value: l.code2,
|
||||
key: l.code2,
|
||||
}))}
|
||||
style={{
|
||||
inputAndroid: {
|
||||
backgroundColor: pal.viewLight.backgroundColor,
|
||||
color: pal.text.color,
|
||||
fontSize: 14,
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: '600',
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 24,
|
||||
},
|
||||
inputIOS: {
|
||||
backgroundColor: pal.viewLight.backgroundColor,
|
||||
color: pal.text.color,
|
||||
fontSize: 14,
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: '600',
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 24,
|
||||
},
|
||||
|
||||
inputWeb: {
|
||||
cursor: 'pointer',
|
||||
// @ts-ignore web only
|
||||
'-moz-appearance': 'none',
|
||||
'-webkit-appearance': 'none',
|
||||
appearance: 'none',
|
||||
outline: 0,
|
||||
borderWidth: 0,
|
||||
backgroundColor: pal.viewLight.backgroundColor,
|
||||
color: pal.text.color,
|
||||
fontSize: 14,
|
||||
fontFamily: 'inherit',
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: '600',
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 24,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 1,
|
||||
right: 1,
|
||||
bottom: 1,
|
||||
width: 40,
|
||||
backgroundColor: pal.viewLight.backgroundColor,
|
||||
borderRadius: 24,
|
||||
pointerEvents: 'none',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<FontAwesomeIcon
|
||||
icon="chevron-down"
|
||||
style={pal.text as FontAwesomeIconStyle}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
height: 1,
|
||||
backgroundColor: pal.border.borderColor,
|
||||
marginBottom: 20,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* PRIMARY LANGUAGE */}
|
||||
<View style={{paddingBottom: 20}}>
|
||||
<Text type="title-sm" style={[pal.text, s.pb5]}>
|
||||
<Trans>Primary Language</Trans>
|
||||
</Text>
|
||||
<Text style={[pal.text, s.pb10]}>
|
||||
<Trans>
|
||||
Select your preferred language for translations in your feed.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<View style={{position: 'relative'}}>
|
||||
<RNPickerSelect
|
||||
placeholder={{}}
|
||||
value={langPrefs.primaryLanguage}
|
||||
onValueChange={onChangePrimaryLanguage}
|
||||
items={LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({
|
||||
label: l.name,
|
||||
value: l.code2,
|
||||
key: l.code2 + l.code3,
|
||||
}))}
|
||||
style={{
|
||||
inputAndroid: {
|
||||
backgroundColor: pal.viewLight.backgroundColor,
|
||||
color: pal.text.color,
|
||||
fontSize: 14,
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: '600',
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 24,
|
||||
},
|
||||
inputIOS: {
|
||||
backgroundColor: pal.viewLight.backgroundColor,
|
||||
color: pal.text.color,
|
||||
fontSize: 14,
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: '600',
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 24,
|
||||
},
|
||||
inputWeb: {
|
||||
cursor: 'pointer',
|
||||
// @ts-ignore web only
|
||||
'-moz-appearance': 'none',
|
||||
'-webkit-appearance': 'none',
|
||||
appearance: 'none',
|
||||
outline: 0,
|
||||
borderWidth: 0,
|
||||
backgroundColor: pal.viewLight.backgroundColor,
|
||||
color: pal.text.color,
|
||||
fontSize: 14,
|
||||
fontFamily: 'inherit',
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: '600',
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 24,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 1,
|
||||
right: 1,
|
||||
bottom: 1,
|
||||
width: 40,
|
||||
backgroundColor: pal.viewLight.backgroundColor,
|
||||
borderRadius: 24,
|
||||
pointerEvents: 'none',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<FontAwesomeIcon
|
||||
icon="chevron-down"
|
||||
style={pal.text as FontAwesomeIconStyle}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
height: 1,
|
||||
backgroundColor: pal.border.borderColor,
|
||||
marginBottom: 20,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* CONTENT LANGUAGES */}
|
||||
<View style={{paddingBottom: 20}}>
|
||||
<Text type="title-sm" style={[pal.text, s.pb5]}>
|
||||
<Trans>Content Languages</Trans>
|
||||
</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.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
type="default"
|
||||
onPress={onPressContentLanguages}
|
||||
style={styles.button}>
|
||||
<FontAwesomeIcon
|
||||
icon={myLanguages.length ? 'check' : 'plus'}
|
||||
style={pal.text as FontAwesomeIconStyle}
|
||||
/>
|
||||
<Text
|
||||
type="button"
|
||||
style={[pal.text, {flexShrink: 1, overflow: 'hidden'}]}
|
||||
numberOfLines={1}>
|
||||
{myLanguages.length ? myLanguages : _(msg`Select languages`)}
|
||||
<View style={{paddingTop: 20, paddingHorizontal: 20}}>
|
||||
{/* APP LANGUAGE */}
|
||||
<View style={{paddingBottom: 20}}>
|
||||
<Text type="title-sm" style={[pal.text, s.pb5]}>
|
||||
<Trans>App Language</Trans>
|
||||
</Text>
|
||||
</Button>
|
||||
<Text style={[pal.text, s.pb10]}>
|
||||
<Trans>
|
||||
Select your app language for the default text to display in the
|
||||
app.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<View style={{position: 'relative'}}>
|
||||
<RNPickerSelect
|
||||
placeholder={{}}
|
||||
value={sanitizeAppLanguageSetting(langPrefs.appLanguage)}
|
||||
onValueChange={onChangeAppLanguage}
|
||||
items={APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({
|
||||
label: l.name,
|
||||
value: l.code2,
|
||||
key: l.code2,
|
||||
}))}
|
||||
style={{
|
||||
inputAndroid: {
|
||||
backgroundColor: pal.viewLight.backgroundColor,
|
||||
color: pal.text.color,
|
||||
fontSize: 14,
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: '600',
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 24,
|
||||
},
|
||||
inputIOS: {
|
||||
backgroundColor: pal.viewLight.backgroundColor,
|
||||
color: pal.text.color,
|
||||
fontSize: 14,
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: '600',
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 24,
|
||||
},
|
||||
|
||||
inputWeb: {
|
||||
cursor: 'pointer',
|
||||
// @ts-ignore web only
|
||||
'-moz-appearance': 'none',
|
||||
'-webkit-appearance': 'none',
|
||||
appearance: 'none',
|
||||
outline: 0,
|
||||
borderWidth: 0,
|
||||
backgroundColor: pal.viewLight.backgroundColor,
|
||||
color: pal.text.color,
|
||||
fontSize: 14,
|
||||
fontFamily: 'inherit',
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: '600',
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 24,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 1,
|
||||
right: 1,
|
||||
bottom: 1,
|
||||
width: 40,
|
||||
backgroundColor: pal.viewLight.backgroundColor,
|
||||
borderRadius: 24,
|
||||
pointerEvents: 'none',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<FontAwesomeIcon
|
||||
icon="chevron-down"
|
||||
style={pal.text as FontAwesomeIconStyle}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
height: 1,
|
||||
backgroundColor: pal.border.borderColor,
|
||||
marginBottom: 20,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* PRIMARY LANGUAGE */}
|
||||
<View style={{paddingBottom: 20}}>
|
||||
<Text type="title-sm" style={[pal.text, s.pb5]}>
|
||||
<Trans>Primary Language</Trans>
|
||||
</Text>
|
||||
<Text style={[pal.text, s.pb10]}>
|
||||
<Trans>
|
||||
Select your preferred language for translations in your feed.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<View style={{position: 'relative'}}>
|
||||
<RNPickerSelect
|
||||
placeholder={{}}
|
||||
value={langPrefs.primaryLanguage}
|
||||
onValueChange={onChangePrimaryLanguage}
|
||||
items={LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({
|
||||
label: l.name,
|
||||
value: l.code2,
|
||||
key: l.code2 + l.code3,
|
||||
}))}
|
||||
style={{
|
||||
inputAndroid: {
|
||||
backgroundColor: pal.viewLight.backgroundColor,
|
||||
color: pal.text.color,
|
||||
fontSize: 14,
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: '600',
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 24,
|
||||
},
|
||||
inputIOS: {
|
||||
backgroundColor: pal.viewLight.backgroundColor,
|
||||
color: pal.text.color,
|
||||
fontSize: 14,
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: '600',
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 24,
|
||||
},
|
||||
inputWeb: {
|
||||
cursor: 'pointer',
|
||||
// @ts-ignore web only
|
||||
'-moz-appearance': 'none',
|
||||
'-webkit-appearance': 'none',
|
||||
appearance: 'none',
|
||||
outline: 0,
|
||||
borderWidth: 0,
|
||||
backgroundColor: pal.viewLight.backgroundColor,
|
||||
color: pal.text.color,
|
||||
fontSize: 14,
|
||||
fontFamily: 'inherit',
|
||||
letterSpacing: 0.5,
|
||||
fontWeight: '600',
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 24,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 1,
|
||||
right: 1,
|
||||
bottom: 1,
|
||||
width: 40,
|
||||
backgroundColor: pal.viewLight.backgroundColor,
|
||||
borderRadius: 24,
|
||||
pointerEvents: 'none',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<FontAwesomeIcon
|
||||
icon="chevron-down"
|
||||
style={pal.text as FontAwesomeIconStyle}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
height: 1,
|
||||
backgroundColor: pal.border.borderColor,
|
||||
marginBottom: 20,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* CONTENT LANGUAGES */}
|
||||
<View style={{paddingBottom: 20}}>
|
||||
<Text type="title-sm" style={[pal.text, s.pb5]}>
|
||||
<Trans>Content Languages</Trans>
|
||||
</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.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
type="default"
|
||||
onPress={onPressContentLanguages}
|
||||
style={styles.button}>
|
||||
<FontAwesomeIcon
|
||||
icon={myLanguages.length ? 'check' : 'plus'}
|
||||
style={pal.text as FontAwesomeIconStyle}
|
||||
/>
|
||||
<Text
|
||||
type="button"
|
||||
style={[pal.text, {flexShrink: 1, overflow: 'hidden'}]}
|
||||
numberOfLines={1}>
|
||||
{myLanguages.length ? myLanguages : _(msg`Select languages`)}
|
||||
</Text>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</CenteredView>
|
||||
</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,76 +96,78 @@ export function ModerationBlockedAccounts({}: Props) {
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<CenteredView
|
||||
style={[
|
||||
styles.container,
|
||||
isTabletOrDesktop && styles.containerDesktop,
|
||||
pal.view,
|
||||
pal.border,
|
||||
]}
|
||||
testID="blockedAccountsScreen">
|
||||
<ViewHeader title={_(msg`Blocked Accounts`)} showOnDesktop />
|
||||
<Text
|
||||
type="sm"
|
||||
<Layout.Screen testID="blockedAccountsScreen">
|
||||
<CenteredView
|
||||
style={[
|
||||
styles.description,
|
||||
pal.text,
|
||||
isTabletOrDesktop && styles.descriptionDesktop,
|
||||
]}>
|
||||
<Trans>
|
||||
Blocked accounts cannot reply in your threads, mention you, or
|
||||
otherwise interact with you. You will not see their content and they
|
||||
will be prevented from seeing yours.
|
||||
</Trans>
|
||||
</Text>
|
||||
{isEmpty ? (
|
||||
<View style={[pal.border, !isTabletOrDesktop && styles.flex1]}>
|
||||
{isError ? (
|
||||
<ErrorScreen
|
||||
title="Oops!"
|
||||
message={cleanError(error)}
|
||||
onPressTryAgain={refetch}
|
||||
/>
|
||||
) : (
|
||||
<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.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
style={[!isTabletOrDesktop && styles.flex1]}
|
||||
data={profiles}
|
||||
keyExtractor={(item: ActorDefs.ProfileView) => item.did}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={isPTRing}
|
||||
onRefresh={onRefresh}
|
||||
tintColor={pal.colors.text}
|
||||
titleColor={pal.colors.text}
|
||||
/>
|
||||
}
|
||||
onEndReached={onEndReached}
|
||||
renderItem={renderItem}
|
||||
initialNumToRender={15}
|
||||
// FIXME(dan)
|
||||
styles.container,
|
||||
isTabletOrDesktop && styles.containerDesktop,
|
||||
pal.view,
|
||||
pal.border,
|
||||
]}
|
||||
testID="blockedAccountsScreen">
|
||||
<ViewHeader title={_(msg`Blocked Accounts`)} showOnDesktop />
|
||||
<Text
|
||||
type="sm"
|
||||
style={[
|
||||
styles.description,
|
||||
pal.text,
|
||||
isTabletOrDesktop && styles.descriptionDesktop,
|
||||
]}>
|
||||
<Trans>
|
||||
Blocked accounts cannot reply in your threads, mention you, or
|
||||
otherwise interact with you. You will not see their content and they
|
||||
will be prevented from seeing yours.
|
||||
</Trans>
|
||||
</Text>
|
||||
{isEmpty ? (
|
||||
<View style={[pal.border, !isTabletOrDesktop && styles.flex1]}>
|
||||
{isError ? (
|
||||
<ErrorScreen
|
||||
title="Oops!"
|
||||
message={cleanError(error)}
|
||||
onPressTryAgain={refetch}
|
||||
/>
|
||||
) : (
|
||||
<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.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
style={[!isTabletOrDesktop && styles.flex1]}
|
||||
data={profiles}
|
||||
keyExtractor={(item: ActorDefs.ProfileView) => item.did}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={isPTRing}
|
||||
onRefresh={onRefresh}
|
||||
tintColor={pal.colors.text}
|
||||
titleColor={pal.colors.text}
|
||||
/>
|
||||
}
|
||||
onEndReached={onEndReached}
|
||||
renderItem={renderItem}
|
||||
initialNumToRender={15}
|
||||
// FIXME(dan)
|
||||
|
||||
ListFooterComponent={() => (
|
||||
<View style={styles.footer}>
|
||||
{(isFetching || isFetchingNextPage) && <ActivityIndicator />}
|
||||
</View>
|
||||
)}
|
||||
// @ts-ignore our .web version only -prf
|
||||
desktopFixedHeight
|
||||
/>
|
||||
)}
|
||||
</CenteredView>
|
||||
ListFooterComponent={() => (
|
||||
<View style={styles.footer}>
|
||||
{(isFetching || isFetchingNextPage) && <ActivityIndicator />}
|
||||
</View>
|
||||
)}
|
||||
// @ts-ignore our .web version only -prf
|
||||
desktopFixedHeight
|
||||
/>
|
||||
)}
|
||||
</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,75 +96,77 @@ export function ModerationMutedAccounts({}: Props) {
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<CenteredView
|
||||
style={[
|
||||
styles.container,
|
||||
isTabletOrDesktop && styles.containerDesktop,
|
||||
pal.view,
|
||||
pal.border,
|
||||
]}
|
||||
testID="mutedAccountsScreen">
|
||||
<ViewHeader title={_(msg`Muted Accounts`)} showOnDesktop />
|
||||
<Text
|
||||
type="sm"
|
||||
<Layout.Screen testID="mutedAccountsScreen">
|
||||
<CenteredView
|
||||
style={[
|
||||
styles.description,
|
||||
pal.text,
|
||||
isTabletOrDesktop && styles.descriptionDesktop,
|
||||
]}>
|
||||
<Trans>
|
||||
Muted accounts have their posts removed from your feed and from your
|
||||
notifications. Mutes are completely private.
|
||||
</Trans>
|
||||
</Text>
|
||||
{isEmpty ? (
|
||||
<View style={[pal.border, !isTabletOrDesktop && styles.flex1]}>
|
||||
{isError ? (
|
||||
<ErrorScreen
|
||||
title="Oops!"
|
||||
message={cleanError(error)}
|
||||
onPressTryAgain={refetch}
|
||||
/>
|
||||
) : (
|
||||
<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.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
style={[!isTabletOrDesktop && styles.flex1]}
|
||||
data={profiles}
|
||||
keyExtractor={item => item.did}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={isPTRing}
|
||||
onRefresh={onRefresh}
|
||||
tintColor={pal.colors.text}
|
||||
titleColor={pal.colors.text}
|
||||
/>
|
||||
}
|
||||
onEndReached={onEndReached}
|
||||
renderItem={renderItem}
|
||||
initialNumToRender={15}
|
||||
// FIXME(dan)
|
||||
styles.container,
|
||||
isTabletOrDesktop && styles.containerDesktop,
|
||||
pal.view,
|
||||
pal.border,
|
||||
]}
|
||||
testID="mutedAccountsScreen">
|
||||
<ViewHeader title={_(msg`Muted Accounts`)} showOnDesktop />
|
||||
<Text
|
||||
type="sm"
|
||||
style={[
|
||||
styles.description,
|
||||
pal.text,
|
||||
isTabletOrDesktop && styles.descriptionDesktop,
|
||||
]}>
|
||||
<Trans>
|
||||
Muted accounts have their posts removed from your feed and from your
|
||||
notifications. Mutes are completely private.
|
||||
</Trans>
|
||||
</Text>
|
||||
{isEmpty ? (
|
||||
<View style={[pal.border, !isTabletOrDesktop && styles.flex1]}>
|
||||
{isError ? (
|
||||
<ErrorScreen
|
||||
title="Oops!"
|
||||
message={cleanError(error)}
|
||||
onPressTryAgain={refetch}
|
||||
/>
|
||||
) : (
|
||||
<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.
|
||||
</Trans>
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
style={[!isTabletOrDesktop && styles.flex1]}
|
||||
data={profiles}
|
||||
keyExtractor={item => item.did}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={isPTRing}
|
||||
onRefresh={onRefresh}
|
||||
tintColor={pal.colors.text}
|
||||
titleColor={pal.colors.text}
|
||||
/>
|
||||
}
|
||||
onEndReached={onEndReached}
|
||||
renderItem={renderItem}
|
||||
initialNumToRender={15}
|
||||
// FIXME(dan)
|
||||
|
||||
ListFooterComponent={() => (
|
||||
<View style={styles.footer}>
|
||||
{(isFetching || isFetchingNextPage) && <ActivityIndicator />}
|
||||
</View>
|
||||
)}
|
||||
// @ts-ignore our .web version only -prf
|
||||
desktopFixedHeight
|
||||
/>
|
||||
)}
|
||||
</CenteredView>
|
||||
ListFooterComponent={() => (
|
||||
<View style={styles.footer}>
|
||||
{(isFetching || isFetchingNextPage) && <ActivityIndicator />}
|
||||
</View>
|
||||
)}
|
||||
// @ts-ignore our .web version only -prf
|
||||
desktopFixedHeight
|
||||
/>
|
||||
)}
|
||||
</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,39 +193,38 @@ export function NotificationsScreen({route: {params}}: Props) {
|
||||
}, [renderButton, isLoadingLatest])
|
||||
|
||||
return (
|
||||
<CenteredView
|
||||
testID="notificationsScreen"
|
||||
style={[s.hContentRegion, {paddingTop: 2}]}
|
||||
sideBorders={true}>
|
||||
<ViewHeader
|
||||
title={_(msg`Notifications`)}
|
||||
canGoBack={false}
|
||||
showBorder={true}
|
||||
renderButton={renderHeaderSpinner}
|
||||
/>
|
||||
<MainScrollProvider>
|
||||
<Feed
|
||||
onScrolledDownChange={setIsScrolledDown}
|
||||
scrollElRef={scrollElRef}
|
||||
ListHeaderComponent={ListHeaderComponent}
|
||||
overridePriorityNotifications={params?.show === 'all'}
|
||||
<Layout.Screen testID="notificationsScreen">
|
||||
<CenteredView style={[a.flex_1, {paddingTop: 2}]} sideBorders={true}>
|
||||
<ViewHeader
|
||||
title={_(msg`Notifications`)}
|
||||
canGoBack={false}
|
||||
showBorder={true}
|
||||
renderButton={renderHeaderSpinner}
|
||||
/>
|
||||
</MainScrollProvider>
|
||||
{(isScrolledDown || hasNew) && (
|
||||
<LoadLatestBtn
|
||||
onPress={onPressLoadLatest}
|
||||
label={_(msg`Load new notifications`)}
|
||||
showIndicator={hasNew}
|
||||
<MainScrollProvider>
|
||||
<Feed
|
||||
onScrolledDownChange={setIsScrolledDown}
|
||||
scrollElRef={scrollElRef}
|
||||
ListHeaderComponent={ListHeaderComponent}
|
||||
overridePriorityNotifications={params?.show === 'all'}
|
||||
/>
|
||||
</MainScrollProvider>
|
||||
{(isScrolledDown || hasNew) && (
|
||||
<LoadLatestBtn
|
||||
onPress={onPressLoadLatest}
|
||||
label={_(msg`Load new notifications`)}
|
||||
showIndicator={hasNew}
|
||||
/>
|
||||
)}
|
||||
<FAB
|
||||
testID="composeFAB"
|
||||
onPress={() => openComposer({})}
|
||||
icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`New post`)}
|
||||
accessibilityHint=""
|
||||
/>
|
||||
)}
|
||||
<FAB
|
||||
testID="composeFAB"
|
||||
onPress={() => openComposer({})}
|
||||
icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`New post`)}
|
||||
accessibilityHint=""
|
||||
/>
|
||||
</CenteredView>
|
||||
</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,61 +36,54 @@ export function NotificationsSettingsScreen({}: Props) {
|
||||
: serverPriority
|
||||
|
||||
return (
|
||||
<ScrollView stickyHeaderIndices={[0]}>
|
||||
<ViewHeader
|
||||
title={_(msg`Notification Settings`)}
|
||||
showOnDesktop
|
||||
showBorder
|
||||
/>
|
||||
{isQueryError ? (
|
||||
<Error
|
||||
title={_(msg`Oops!`)}
|
||||
message={_(msg`Something went wrong!`)}
|
||||
onRetry={refetch}
|
||||
sideBorders={false}
|
||||
<Layout.Screen>
|
||||
<ScrollView stickyHeaderIndices={[0]}>
|
||||
<ViewHeader
|
||||
title={_(msg`Notification Settings`)}
|
||||
showOnDesktop
|
||||
showBorder
|
||||
/>
|
||||
) : (
|
||||
<View style={[a.p_lg, a.gap_md]}>
|
||||
<Text style={[a.text_lg, a.font_bold]}>
|
||||
<FontAwesomeIcon icon="flask" style={t.atoms.text} />{' '}
|
||||
<Trans>Notification filters</Trans>
|
||||
</Text>
|
||||
<Toggle.Group
|
||||
label={_(msg`Priority notifications`)}
|
||||
type="checkbox"
|
||||
values={priority ? ['enabled'] : []}
|
||||
onChange={onChangePriority}
|
||||
disabled={typeof priority !== 'boolean' || isMutationPending}>
|
||||
<View>
|
||||
<Toggle.Item
|
||||
name="enabled"
|
||||
label={_(msg`Enable priority notifications`)}
|
||||
style={[a.justify_between, a.py_sm]}>
|
||||
<Toggle.LabelText>
|
||||
<Trans>Enable priority notifications</Trans>
|
||||
</Toggle.LabelText>
|
||||
{!data ? <Loader size="md" /> : <Toggle.Platform />}
|
||||
</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]}>
|
||||
{isQueryError ? (
|
||||
<Error
|
||||
title={_(msg`Oops!`)}
|
||||
message={_(msg`Something went wrong!`)}
|
||||
onRetry={refetch}
|
||||
sideBorders={false}
|
||||
/>
|
||||
) : (
|
||||
<View style={[a.p_lg, a.gap_md]}>
|
||||
<Text style={[a.text_lg, a.font_bold]}>
|
||||
<FontAwesomeIcon icon="flask" style={t.atoms.text} />{' '}
|
||||
<Trans>Notification filters</Trans>
|
||||
</Text>
|
||||
<Toggle.Group
|
||||
label={_(msg`Priority notifications`)}
|
||||
type="checkbox"
|
||||
values={priority ? ['enabled'] : []}
|
||||
onChange={onChangePriority}
|
||||
disabled={typeof priority !== 'boolean' || isMutationPending}>
|
||||
<View>
|
||||
<Toggle.Item
|
||||
name="enabled"
|
||||
label={_(msg`Enable priority notifications`)}
|
||||
style={[a.justify_between, a.py_sm]}>
|
||||
<Toggle.LabelText>
|
||||
<Trans>Enable priority notifications</Trans>
|
||||
</Toggle.LabelText>
|
||||
{!data ? <Loader size="md" /> : <Toggle.Platform />}
|
||||
</Toggle.Item>
|
||||
</View>
|
||||
</Toggle.Group>
|
||||
<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>
|
||||
</Admonition>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
)}
|
||||
</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,36 +97,42 @@ export function ProfileFeedScreen(props: Props) {
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<CenteredView>
|
||||
<View style={[pal.view, pal.border, styles.notFoundContainer]}>
|
||||
<Text type="title-lg" style={[pal.text, s.mb10]}>
|
||||
<Trans>Could not load feed</Trans>
|
||||
</Text>
|
||||
<Text type="md" style={[pal.text, s.mb20]}>
|
||||
{error.toString()}
|
||||
</Text>
|
||||
<Layout.Screen testID="profileFeedScreenError">
|
||||
<CenteredView>
|
||||
<View style={[pal.view, pal.border, styles.notFoundContainer]}>
|
||||
<Text type="title-lg" style={[pal.text, s.mb10]}>
|
||||
<Trans>Could not load feed</Trans>
|
||||
</Text>
|
||||
<Text type="md" style={[pal.text, s.mb20]}>
|
||||
{error.toString()}
|
||||
</Text>
|
||||
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<Button
|
||||
type="default"
|
||||
accessibilityLabel={_(msg`Go back`)}
|
||||
accessibilityHint={_(msg`Returns to previous page`)}
|
||||
onPress={onPressBack}
|
||||
style={{flexShrink: 1}}>
|
||||
<Text type="button" style={pal.text}>
|
||||
<Trans>Go Back</Trans>
|
||||
</Text>
|
||||
</Button>
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<Button
|
||||
type="default"
|
||||
accessibilityLabel={_(msg`Go back`)}
|
||||
accessibilityHint={_(msg`Returns to previous page`)}
|
||||
onPress={onPressBack}
|
||||
style={{flexShrink: 1}}>
|
||||
<Text type="button" style={pal.text}>
|
||||
<Trans>Go Back</Trans>
|
||||
</Text>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</CenteredView>
|
||||
</CenteredView>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
return resolvedUri ? (
|
||||
<ProfileFeedScreenIntermediate feedUri={resolvedUri.uri} />
|
||||
<Layout.Screen>
|
||||
<ProfileFeedScreenIntermediate feedUri={resolvedUri.uri} />
|
||||
</Layout.Screen>
|
||||
) : (
|
||||
<LoadingScreen />
|
||||
<Layout.Screen>
|
||||
<LoadingScreen />
|
||||
</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