Merge branch 'main' into ja-translation-14
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M9.576 2.534C7.578 1.299 5 2.737 5 5.086v13.828c0 2.35 2.578 3.787 4.576 2.552l11.194-6.914c1.899-1.172 1.899-3.932 0-5.104L9.576 2.534Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 239 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M5 5.086C5 2.736 7.578 1.3 9.576 2.534L20.77 9.448c1.899 1.172 1.899 3.932 0 5.104L9.576 21.466C7.578 22.701 5 21.263 5 18.914V5.086Zm3.525-.85A1 1 0 0 0 7 5.085v13.828a1 1 0 0 0 1.525.85l11.194-6.913a1 1 0 0 0 0-1.702L8.525 4.235Z" clip-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 374 B |
+3
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "bsky.app",
|
"name": "bsky.app",
|
||||||
"version": "1.89.0",
|
"version": "1.90.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -143,6 +143,7 @@
|
|||||||
"expo-web-browser": "~13.0.3",
|
"expo-web-browser": "~13.0.3",
|
||||||
"fast-text-encoding": "^1.0.6",
|
"fast-text-encoding": "^1.0.6",
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
|
"hls.js": "^1.5.11",
|
||||||
"js-sha256": "^0.9.0",
|
"js-sha256": "^0.9.0",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"lande": "^1.0.10",
|
"lande": "^1.0.10",
|
||||||
@@ -269,7 +270,7 @@
|
|||||||
"react-scripts": "^5.0.1",
|
"react-scripts": "^5.0.1",
|
||||||
"react-test-renderer": "18.2.0",
|
"react-test-renderer": "18.2.0",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.5.4",
|
||||||
"url-loader": "^4.1.1",
|
"url-loader": "^4.1.1",
|
||||||
"webpack": "^5.75.0",
|
"webpack": "^5.75.0",
|
||||||
"webpack-bundle-analyzer": "^4.10.1",
|
"webpack-bundle-analyzer": "^4.10.1",
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
--- a/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerViewExtension.kt
|
||||||
|
+++ b/node_modules/expo-video/android/src/main/java/expo/modules/video/PlayerViewExtension.kt
|
||||||
|
@@ -11,6 +11,7 @@ internal fun PlayerView.applyRequiresLinearPlayback(requireLinearPlayback: Boole
|
||||||
|
setShowPreviousButton(!requireLinearPlayback)
|
||||||
|
setShowNextButton(!requireLinearPlayback)
|
||||||
|
setTimeBarInteractive(requireLinearPlayback)
|
||||||
|
+ setShowSubtitleButton(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||||
|
@@ -27,7 +28,8 @@ internal fun PlayerView.setTimeBarInteractive(interactive: Boolean) {
|
||||||
|
|
||||||
|
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||||
|
internal fun PlayerView.setFullscreenButtonVisibility(visible: Boolean) {
|
||||||
|
- val fullscreenButton = findViewById<android.widget.ImageButton>(androidx.media3.ui.R.id.exo_fullscreen)
|
||||||
|
+ val fullscreenButton =
|
||||||
|
+ findViewById<android.widget.ImageButton>(androidx.media3.ui.R.id.exo_fullscreen)
|
||||||
|
fullscreenButton?.visibility = if (visible) {
|
||||||
|
android.view.View.VISIBLE
|
||||||
|
} else {
|
||||||
+41
-38
@@ -23,10 +23,12 @@ import {
|
|||||||
} from '#/lib/statsig/statsig'
|
} from '#/lib/statsig/statsig'
|
||||||
import {s} from '#/lib/styles'
|
import {s} from '#/lib/styles'
|
||||||
import {ThemeProvider} from '#/lib/ThemeContext'
|
import {ThemeProvider} from '#/lib/ThemeContext'
|
||||||
|
import I18nProvider from '#/locale/i18nProvider'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {Provider as A11yProvider} from '#/state/a11y'
|
import {Provider as A11yProvider} from '#/state/a11y'
|
||||||
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
|
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
|
||||||
import {Provider as DialogStateProvider} from '#/state/dialogs'
|
import {Provider as DialogStateProvider} from '#/state/dialogs'
|
||||||
|
import {listenSessionDropped} from '#/state/events'
|
||||||
import {Provider as InvitesStateProvider} from '#/state/invites'
|
import {Provider as InvitesStateProvider} from '#/state/invites'
|
||||||
import {Provider as LightboxStateProvider} from '#/state/lightbox'
|
import {Provider as LightboxStateProvider} from '#/state/lightbox'
|
||||||
import {MessagesProvider} from '#/state/messages'
|
import {MessagesProvider} from '#/state/messages'
|
||||||
@@ -49,6 +51,7 @@ import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
|
|||||||
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
|
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
|
||||||
import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
|
import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
|
||||||
import {TestCtrls} from '#/view/com/testing/TestCtrls'
|
import {TestCtrls} from '#/view/com/testing/TestCtrls'
|
||||||
|
import {ActiveVideoProvider} from '#/view/com/util/post-embeds/ActiveVideoContext'
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {Shell} from '#/view/shell'
|
import {Shell} from '#/view/shell'
|
||||||
import {ThemeProvider as Alf} from '#/alf'
|
import {ThemeProvider as Alf} from '#/alf'
|
||||||
@@ -58,8 +61,6 @@ import {Provider as PortalProvider} from '#/components/Portal'
|
|||||||
import {Splash} from '#/Splash'
|
import {Splash} from '#/Splash'
|
||||||
import {Provider as TourProvider} from '#/tours'
|
import {Provider as TourProvider} from '#/tours'
|
||||||
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
|
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
|
||||||
import I18nProvider from './locale/i18nProvider'
|
|
||||||
import {listenSessionDropped} from './state/events'
|
|
||||||
|
|
||||||
SplashScreen.preventAutoHideAsync()
|
SplashScreen.preventAutoHideAsync()
|
||||||
|
|
||||||
@@ -107,42 +108,44 @@ function InnerApp() {
|
|||||||
<Alf theme={theme}>
|
<Alf theme={theme}>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<Splash isReady={isReady && hasCheckedReferrer}>
|
<Splash isReady={isReady && hasCheckedReferrer}>
|
||||||
<RootSiblingParent>
|
<ActiveVideoProvider>
|
||||||
<React.Fragment
|
<RootSiblingParent>
|
||||||
// Resets the entire tree below when it changes:
|
<React.Fragment
|
||||||
key={currentAccount?.did}>
|
// Resets the entire tree below when it changes:
|
||||||
<QueryProvider currentDid={currentAccount?.did}>
|
key={currentAccount?.did}>
|
||||||
<StatsigProvider>
|
<QueryProvider currentDid={currentAccount?.did}>
|
||||||
<MessagesProvider>
|
<StatsigProvider>
|
||||||
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
<MessagesProvider>
|
||||||
<LabelDefsProvider>
|
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
||||||
<ModerationOptsProvider>
|
<LabelDefsProvider>
|
||||||
<LoggedOutViewProvider>
|
<ModerationOptsProvider>
|
||||||
<SelectedFeedProvider>
|
<LoggedOutViewProvider>
|
||||||
<UnreadNotifsProvider>
|
<SelectedFeedProvider>
|
||||||
<BackgroundNotificationPreferencesProvider>
|
<UnreadNotifsProvider>
|
||||||
<MutedThreadsProvider>
|
<BackgroundNotificationPreferencesProvider>
|
||||||
<TourProvider>
|
<MutedThreadsProvider>
|
||||||
<ProgressGuideProvider>
|
<TourProvider>
|
||||||
<GestureHandlerRootView
|
<ProgressGuideProvider>
|
||||||
style={s.h100pct}>
|
<GestureHandlerRootView
|
||||||
<TestCtrls />
|
style={s.h100pct}>
|
||||||
<Shell />
|
<TestCtrls />
|
||||||
</GestureHandlerRootView>
|
<Shell />
|
||||||
</ProgressGuideProvider>
|
</GestureHandlerRootView>
|
||||||
</TourProvider>
|
</ProgressGuideProvider>
|
||||||
</MutedThreadsProvider>
|
</TourProvider>
|
||||||
</BackgroundNotificationPreferencesProvider>
|
</MutedThreadsProvider>
|
||||||
</UnreadNotifsProvider>
|
</BackgroundNotificationPreferencesProvider>
|
||||||
</SelectedFeedProvider>
|
</UnreadNotifsProvider>
|
||||||
</LoggedOutViewProvider>
|
</SelectedFeedProvider>
|
||||||
</ModerationOptsProvider>
|
</LoggedOutViewProvider>
|
||||||
</LabelDefsProvider>
|
</ModerationOptsProvider>
|
||||||
</MessagesProvider>
|
</LabelDefsProvider>
|
||||||
</StatsigProvider>
|
</MessagesProvider>
|
||||||
</QueryProvider>
|
</StatsigProvider>
|
||||||
</React.Fragment>
|
</QueryProvider>
|
||||||
</RootSiblingParent>
|
</React.Fragment>
|
||||||
|
</RootSiblingParent>
|
||||||
|
</ActiveVideoProvider>
|
||||||
</Splash>
|
</Splash>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</Alf>
|
</Alf>
|
||||||
|
|||||||
+38
-35
@@ -12,10 +12,12 @@ import {useIntentHandler} from '#/lib/hooks/useIntentHandler'
|
|||||||
import {QueryProvider} from '#/lib/react-query'
|
import {QueryProvider} from '#/lib/react-query'
|
||||||
import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
|
import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
|
||||||
import {ThemeProvider} from '#/lib/ThemeContext'
|
import {ThemeProvider} from '#/lib/ThemeContext'
|
||||||
|
import I18nProvider from '#/locale/i18nProvider'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {Provider as A11yProvider} from '#/state/a11y'
|
import {Provider as A11yProvider} from '#/state/a11y'
|
||||||
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
|
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
|
||||||
import {Provider as DialogStateProvider} from '#/state/dialogs'
|
import {Provider as DialogStateProvider} from '#/state/dialogs'
|
||||||
|
import {listenSessionDropped} from '#/state/events'
|
||||||
import {Provider as InvitesStateProvider} from '#/state/invites'
|
import {Provider as InvitesStateProvider} from '#/state/invites'
|
||||||
import {Provider as LightboxStateProvider} from '#/state/lightbox'
|
import {Provider as LightboxStateProvider} from '#/state/lightbox'
|
||||||
import {MessagesProvider} from '#/state/messages'
|
import {MessagesProvider} from '#/state/messages'
|
||||||
@@ -37,6 +39,7 @@ import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out'
|
|||||||
import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
|
import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
|
||||||
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
|
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
|
||||||
import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
|
import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
|
||||||
|
import {ActiveVideoProvider} from '#/view/com/util/post-embeds/ActiveVideoContext'
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {ToastContainer} from '#/view/com/util/Toast.web'
|
import {ToastContainer} from '#/view/com/util/Toast.web'
|
||||||
import {Shell} from '#/view/shell/index'
|
import {Shell} from '#/view/shell/index'
|
||||||
@@ -46,8 +49,6 @@ import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
|
|||||||
import {Provider as PortalProvider} from '#/components/Portal'
|
import {Provider as PortalProvider} from '#/components/Portal'
|
||||||
import {Provider as TourProvider} from '#/tours'
|
import {Provider as TourProvider} from '#/tours'
|
||||||
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
|
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
|
||||||
import I18nProvider from './locale/i18nProvider'
|
|
||||||
import {listenSessionDropped} from './state/events'
|
|
||||||
|
|
||||||
function InnerApp() {
|
function InnerApp() {
|
||||||
const [isReady, setIsReady] = React.useState(false)
|
const [isReady, setIsReady] = React.useState(false)
|
||||||
@@ -92,39 +93,41 @@ function InnerApp() {
|
|||||||
<Alf theme={theme}>
|
<Alf theme={theme}>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<RootSiblingParent>
|
<RootSiblingParent>
|
||||||
<React.Fragment
|
<ActiveVideoProvider>
|
||||||
// Resets the entire tree below when it changes:
|
<React.Fragment
|
||||||
key={currentAccount?.did}>
|
// Resets the entire tree below when it changes:
|
||||||
<QueryProvider currentDid={currentAccount?.did}>
|
key={currentAccount?.did}>
|
||||||
<StatsigProvider>
|
<QueryProvider currentDid={currentAccount?.did}>
|
||||||
<MessagesProvider>
|
<StatsigProvider>
|
||||||
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
<MessagesProvider>
|
||||||
<LabelDefsProvider>
|
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
||||||
<ModerationOptsProvider>
|
<LabelDefsProvider>
|
||||||
<LoggedOutViewProvider>
|
<ModerationOptsProvider>
|
||||||
<SelectedFeedProvider>
|
<LoggedOutViewProvider>
|
||||||
<UnreadNotifsProvider>
|
<SelectedFeedProvider>
|
||||||
<BackgroundNotificationPreferencesProvider>
|
<UnreadNotifsProvider>
|
||||||
<MutedThreadsProvider>
|
<BackgroundNotificationPreferencesProvider>
|
||||||
<SafeAreaProvider>
|
<MutedThreadsProvider>
|
||||||
<TourProvider>
|
<SafeAreaProvider>
|
||||||
<ProgressGuideProvider>
|
<TourProvider>
|
||||||
<Shell />
|
<ProgressGuideProvider>
|
||||||
</ProgressGuideProvider>
|
<Shell />
|
||||||
</TourProvider>
|
</ProgressGuideProvider>
|
||||||
</SafeAreaProvider>
|
</TourProvider>
|
||||||
</MutedThreadsProvider>
|
</SafeAreaProvider>
|
||||||
</BackgroundNotificationPreferencesProvider>
|
</MutedThreadsProvider>
|
||||||
</UnreadNotifsProvider>
|
</BackgroundNotificationPreferencesProvider>
|
||||||
</SelectedFeedProvider>
|
</UnreadNotifsProvider>
|
||||||
</LoggedOutViewProvider>
|
</SelectedFeedProvider>
|
||||||
</ModerationOptsProvider>
|
</LoggedOutViewProvider>
|
||||||
</LabelDefsProvider>
|
</ModerationOptsProvider>
|
||||||
</MessagesProvider>
|
</LabelDefsProvider>
|
||||||
</StatsigProvider>
|
</MessagesProvider>
|
||||||
</QueryProvider>
|
</StatsigProvider>
|
||||||
</React.Fragment>
|
</QueryProvider>
|
||||||
<ToastContainer />
|
</React.Fragment>
|
||||||
|
<ToastContainer />
|
||||||
|
</ActiveVideoProvider>
|
||||||
</RootSiblingParent>
|
</RootSiblingParent>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</Alf>
|
</Alf>
|
||||||
|
|||||||
+5
-6
@@ -2,7 +2,6 @@ import {Platform, StyleSheet} from 'react-native'
|
|||||||
|
|
||||||
import * as tokens from '#/alf/tokens'
|
import * as tokens from '#/alf/tokens'
|
||||||
import {native, web} from '#/alf/util/platform'
|
import {native, web} from '#/alf/util/platform'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
|
|
||||||
export const atoms = {
|
export const atoms = {
|
||||||
/*
|
/*
|
||||||
@@ -284,19 +283,19 @@ export const atoms = {
|
|||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
},
|
},
|
||||||
border: {
|
border: {
|
||||||
borderWidth: hairlineWidth,
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
border_t: {
|
border_t: {
|
||||||
borderTopWidth: hairlineWidth,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
border_b: {
|
border_b: {
|
||||||
borderBottomWidth: hairlineWidth,
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
border_l: {
|
border_l: {
|
||||||
borderLeftWidth: hairlineWidth,
|
borderLeftWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
border_r: {
|
border_r: {
|
||||||
borderRightWidth: hairlineWidth,
|
borderRightWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -70,6 +70,9 @@ export type ButtonProps = Pick<
|
|||||||
AccessibilityProps &
|
AccessibilityProps &
|
||||||
VariantProps & {
|
VariantProps & {
|
||||||
testID?: string
|
testID?: string
|
||||||
|
/**
|
||||||
|
* For a11y, try to make this descriptive and clear
|
||||||
|
*/
|
||||||
label: string
|
label: string
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
hoverStyle?: StyleProp<ViewStyle>
|
hoverStyle?: StyleProp<ViewStyle>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ type Props = {
|
|||||||
export function Default(props: Props) {
|
export function Default(props: Props) {
|
||||||
const {view} = props
|
const {view} = props
|
||||||
return (
|
return (
|
||||||
<Link label={view.displayName} {...props}>
|
<Link {...props}>
|
||||||
<Outer>
|
<Outer>
|
||||||
<Header>
|
<Header>
|
||||||
<Avatar src={view.avatar} />
|
<Avatar src={view.avatar} />
|
||||||
@@ -58,7 +58,7 @@ export function Link({
|
|||||||
view,
|
view,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: Props & Omit<LinkProps, 'to'>) {
|
}: Props & Omit<LinkProps, 'to' | 'label'>) {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
const href = React.useMemo(() => {
|
const href = React.useMemo(() => {
|
||||||
@@ -70,7 +70,11 @@ export function Link({
|
|||||||
}, [view, queryClient])
|
}, [view, queryClient])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InternalLink to={href} style={[a.flex_col]} {...props}>
|
<InternalLink
|
||||||
|
label={view.displayName}
|
||||||
|
to={href}
|
||||||
|
style={[a.flex_col]}
|
||||||
|
{...props}>
|
||||||
{children}
|
{children}
|
||||||
</InternalLink>
|
</InternalLink>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ export function SuggestedFollows() {
|
|||||||
{profiles.slice(0, maxLength).map(profile => (
|
{profiles.slice(0, maxLength).map(profile => (
|
||||||
<ProfileCard.Link
|
<ProfileCard.Link
|
||||||
key={profile.did}
|
key={profile.did}
|
||||||
did={profile.handle}
|
profile={profile}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
logEvent('feed:interstitial:profileCard:press', {})
|
logEvent('feed:interstitial:profileCard:press', {})
|
||||||
}}
|
}}
|
||||||
@@ -266,7 +266,10 @@ export function SuggestedFollows() {
|
|||||||
a.pt_xs,
|
a.pt_xs,
|
||||||
a.gap_md,
|
a.gap_md,
|
||||||
]}>
|
]}>
|
||||||
<InlineLinkText to="/search" style={[t.atoms.text_contrast_medium]}>
|
<InlineLinkText
|
||||||
|
label={_(msg`Browse more suggestions`)}
|
||||||
|
to="/search"
|
||||||
|
style={[t.atoms.text_contrast_medium]}>
|
||||||
<Trans>Browse more suggestions</Trans>
|
<Trans>Browse more suggestions</Trans>
|
||||||
</InlineLinkText>
|
</InlineLinkText>
|
||||||
<Arrow size="sm" fill={t.atoms.text_contrast_medium.color} />
|
<Arrow size="sm" fill={t.atoms.text_contrast_medium.color} />
|
||||||
@@ -396,7 +399,10 @@ export function SuggestedFeeds() {
|
|||||||
a.pt_xs,
|
a.pt_xs,
|
||||||
a.gap_md,
|
a.gap_md,
|
||||||
]}>
|
]}>
|
||||||
<InlineLinkText to="/search" style={[t.atoms.text_contrast_medium]}>
|
<InlineLinkText
|
||||||
|
label={_(msg`Browse more suggestions`)}
|
||||||
|
to="/search"
|
||||||
|
style={[t.atoms.text_contrast_medium]}>
|
||||||
<Trans>Browse more suggestions</Trans>
|
<Trans>Browse more suggestions</Trans>
|
||||||
</InlineLinkText>
|
</InlineLinkText>
|
||||||
<Arrow size="sm" fill={t.atoms.text_contrast_medium.color} />
|
<Arrow size="sm" fill={t.atoms.text_contrast_medium.color} />
|
||||||
|
|||||||
@@ -40,11 +40,6 @@ type BaseLinkProps = Pick<
|
|||||||
> & {
|
> & {
|
||||||
testID?: string
|
testID?: string
|
||||||
|
|
||||||
/**
|
|
||||||
* Label for a11y. Defaults to the href.
|
|
||||||
*/
|
|
||||||
label?: string
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The React Navigation `StackAction` to perform when the link is pressed.
|
* The React Navigation `StackAction` to perform when the link is pressed.
|
||||||
*/
|
*/
|
||||||
@@ -197,7 +192,7 @@ export function useLink({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type LinkProps = Omit<BaseLinkProps, 'disableMismatchWarning'> &
|
export type LinkProps = Omit<BaseLinkProps, 'disableMismatchWarning'> &
|
||||||
Omit<ButtonProps, 'onPress' | 'disabled' | 'label'>
|
Omit<ButtonProps, 'onPress' | 'disabled'>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A interactive element that renders as a `<a>` tag on the web. On mobile it
|
* A interactive element that renders as a `<a>` tag on the web. On mobile it
|
||||||
@@ -224,7 +219,6 @@ export function Link({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
label={href}
|
|
||||||
{...rest}
|
{...rest}
|
||||||
style={[a.justify_start, flatten(rest.style)]}
|
style={[a.justify_start, flatten(rest.style)]}
|
||||||
role="link"
|
role="link"
|
||||||
@@ -249,7 +243,8 @@ export function Link({
|
|||||||
|
|
||||||
export type InlineLinkProps = React.PropsWithChildren<
|
export type InlineLinkProps = React.PropsWithChildren<
|
||||||
BaseLinkProps & TextStyleProp & Pick<TextProps, 'selectable'>
|
BaseLinkProps & TextStyleProp & Pick<TextProps, 'selectable'>
|
||||||
>
|
> &
|
||||||
|
Pick<ButtonProps, 'label'>
|
||||||
|
|
||||||
export function InlineLinkText({
|
export function InlineLinkText({
|
||||||
children,
|
children,
|
||||||
@@ -291,7 +286,7 @@ export function InlineLinkText({
|
|||||||
<Text
|
<Text
|
||||||
selectable={selectable}
|
selectable={selectable}
|
||||||
accessibilityHint=""
|
accessibilityHint=""
|
||||||
accessibilityLabel={label || href}
|
accessibilityLabel={label}
|
||||||
{...rest}
|
{...rest}
|
||||||
style={[
|
style={[
|
||||||
{color: t.palette.primary_500},
|
{color: t.palette.primary_500},
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ type Props = {
|
|||||||
export function Default(props: Props) {
|
export function Default(props: Props) {
|
||||||
const {view, showPinButton} = props
|
const {view, showPinButton} = props
|
||||||
return (
|
return (
|
||||||
<Link label={view.name} {...props}>
|
<Link {...props}>
|
||||||
<Outer>
|
<Outer>
|
||||||
<Header>
|
<Header>
|
||||||
<Avatar src={view.avatar} />
|
<Avatar src={view.avatar} />
|
||||||
@@ -67,7 +67,7 @@ export function Link({
|
|||||||
view,
|
view,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: Props & Omit<LinkProps, 'to'>) {
|
}: Props & Omit<LinkProps, 'to' | 'label'>) {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
const href = React.useMemo(() => {
|
const href = React.useMemo(() => {
|
||||||
@@ -79,7 +79,7 @@ export function Link({
|
|||||||
}, [view, queryClient])
|
}, [view, queryClient])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InternalLink to={href} {...props}>
|
<InternalLink label={view.name} to={href} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</InternalLink>
|
</InternalLink>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function Default({
|
|||||||
logContext?: 'ProfileCard' | 'StarterPackProfilesList'
|
logContext?: 'ProfileCard' | 'StarterPackProfilesList'
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Link did={profile.did}>
|
<Link profile={profile}>
|
||||||
<Card
|
<Card
|
||||||
profile={profile}
|
profile={profile}
|
||||||
moderationOpts={moderationOpts}
|
moderationOpts={moderationOpts}
|
||||||
@@ -96,16 +96,24 @@ export function Header({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Link({
|
export function Link({
|
||||||
did,
|
profile,
|
||||||
children,
|
children,
|
||||||
style,
|
style,
|
||||||
...rest
|
...rest
|
||||||
}: {did: string} & Omit<LinkProps, 'to'>) {
|
}: {
|
||||||
|
profile: AppBskyActorDefs.ProfileViewDetailed
|
||||||
|
} & Omit<LinkProps, 'to' | 'label'>) {
|
||||||
|
const {_} = useLingui()
|
||||||
return (
|
return (
|
||||||
<InternalLink
|
<InternalLink
|
||||||
|
label={_(
|
||||||
|
msg`View ${
|
||||||
|
profile.displayName || sanitizeHandle(profile.handle)
|
||||||
|
}'s profile`,
|
||||||
|
)}
|
||||||
to={{
|
to={{
|
||||||
screen: 'Profile',
|
screen: 'Profile',
|
||||||
params: {name: did},
|
params: {name: profile.did},
|
||||||
}}
|
}}
|
||||||
style={[a.flex_col, style]}
|
style={[a.flex_col, style]}
|
||||||
{...rest}>
|
{...rest}>
|
||||||
|
|||||||
@@ -74,7 +74,9 @@ function DialogContent({
|
|||||||
|
|
||||||
const onPressAudience = (setting: ThreadgateSetting) => {
|
const onPressAudience = (setting: ThreadgateSetting) => {
|
||||||
// remove nobody
|
// remove nobody
|
||||||
let newSelected = draft.filter(v => v.type !== 'nobody')
|
let newSelected: ThreadgateSetting[] = draft.filter(
|
||||||
|
v => v.type !== 'nobody',
|
||||||
|
)
|
||||||
// toggle
|
// toggle
|
||||||
const i = newSelected.findIndex(v => isEqual(v, setting))
|
const i = newSelected.findIndex(v => isEqual(v, setting))
|
||||||
if (i === -1) {
|
if (i === -1) {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export function BlockedByListDialog({
|
|||||||
<React.Fragment key={block.source.list.uri}>
|
<React.Fragment key={block.source.list.uri}>
|
||||||
{i === 0 ? null : ', '}
|
{i === 0 ? null : ', '}
|
||||||
<InlineLinkText
|
<InlineLinkText
|
||||||
|
label={block.source.list.name}
|
||||||
to={listUriToHref(block.source.list.uri)}
|
to={listUriToHref(block.source.list.uri)}
|
||||||
style={[a.text_md, a.leading_snug]}>
|
style={[a.text_md, a.leading_snug]}>
|
||||||
{block.source.list.name}
|
{block.source.list.name}
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ function HeaderReady({
|
|||||||
userBlock?: ModerationCause
|
userBlock?: ModerationCause
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const convoState = useConvo()
|
const convoState = useConvo()
|
||||||
const profile = useProfileShadow(profileUnshadowed)
|
const profile = useProfileShadow(profileUnshadowed)
|
||||||
@@ -156,6 +157,7 @@ function HeaderReady({
|
|||||||
<View style={[a.flex_1]}>
|
<View style={[a.flex_1]}>
|
||||||
<View style={[a.w_full, a.flex_row, a.align_center, a.justify_between]}>
|
<View style={[a.w_full, a.flex_row, a.align_center, a.justify_between]}>
|
||||||
<Link
|
<Link
|
||||||
|
label={_(msg`View ${displayName}'s profile`)}
|
||||||
style={[a.flex_row, a.align_start, a.gap_md, a.flex_1, a.pr_md]}
|
style={[a.flex_row, a.align_start, a.gap_md, a.flex_1, a.pr_md]}
|
||||||
to={makeProfileLink(profile)}>
|
to={makeProfileLink(profile)}>
|
||||||
<View style={[a.pt_2xs]}>
|
<View style={[a.pt_2xs]}>
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import {createSinglePathSVG} from './TEMPLATE'
|
||||||
|
|
||||||
|
export const Play_Stroke2_Corner2_Rounded = createSinglePathSVG({
|
||||||
|
path: 'M5 5.086C5 2.736 7.578 1.3 9.576 2.534L20.77 9.448c1.899 1.172 1.899 3.932 0 5.104L9.576 21.466C7.578 22.701 5 21.263 5 18.914V5.086Zm3.525-.85A1 1 0 0 0 7 5.085v13.828a1 1 0 0 0 1.525.85l11.194-6.913a1 1 0 0 0 0-1.702L8.525 4.235Z',
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Play_Filled_Corner2_Rounded = createSinglePathSVG({
|
||||||
|
path: 'M9.576 2.534C7.578 1.299 5 2.737 5 5.086v13.828c0 2.35 2.578 3.787 4.576 2.552l11.194-6.914c1.899-1.172 1.899-3.932 0-5.104L9.576 2.534Z',
|
||||||
|
})
|
||||||
@@ -174,7 +174,7 @@ export function LabelerLabelPreference({
|
|||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
labelerDid?: string
|
labelerDid?: string
|
||||||
}) {
|
}) {
|
||||||
const {i18n} = useLingui()
|
const {_, i18n} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {gtPhone} = useBreakpoints()
|
const {gtPhone} = useBreakpoints()
|
||||||
|
|
||||||
@@ -243,7 +243,10 @@ export function LabelerLabelPreference({
|
|||||||
) : isGlobalLabel ? (
|
) : isGlobalLabel ? (
|
||||||
<Trans>
|
<Trans>
|
||||||
Configured in{' '}
|
Configured in{' '}
|
||||||
<InlineLinkText to="/moderation" style={a.text_sm}>
|
<InlineLinkText
|
||||||
|
label={_(msg`moderation settings`)}
|
||||||
|
to="/moderation"
|
||||||
|
style={a.text_sm}>
|
||||||
moderation settings
|
moderation settings
|
||||||
</InlineLinkText>
|
</InlineLinkText>
|
||||||
.
|
.
|
||||||
|
|||||||
@@ -128,6 +128,9 @@ function Label({
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {labeler, strings} = useLabelInfo(label)
|
const {labeler, strings} = useLabelInfo(label)
|
||||||
|
const sourceName = labeler
|
||||||
|
? sanitizeHandle(labeler.creator.handle, '@')
|
||||||
|
: label.src
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
@@ -169,13 +172,12 @@ function Label({
|
|||||||
<Trans>
|
<Trans>
|
||||||
Source:{' '}
|
Source:{' '}
|
||||||
<InlineLinkText
|
<InlineLinkText
|
||||||
|
label={sourceName}
|
||||||
to={makeProfileLink(
|
to={makeProfileLink(
|
||||||
labeler ? labeler.creator : {did: label.src, handle: ''},
|
labeler ? labeler.creator : {did: label.src, handle: ''},
|
||||||
)}
|
)}
|
||||||
onPress={() => control.close()}>
|
onPress={() => control.close()}>
|
||||||
{labeler
|
{sourceName}
|
||||||
? sanitizeHandle(labeler.creator.handle, '@')
|
|
||||||
: label.src}
|
|
||||||
</InlineLinkText>
|
</InlineLinkText>
|
||||||
</Trans>
|
</Trans>
|
||||||
)}
|
)}
|
||||||
@@ -203,6 +205,9 @@ function AppealForm({
|
|||||||
const isAccountReport = 'did' in subject
|
const isAccountReport = 'did' in subject
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
const gate = useGate()
|
const gate = useGate()
|
||||||
|
const sourceName = labeler
|
||||||
|
? sanitizeHandle(labeler.creator.handle, '@')
|
||||||
|
: label.src
|
||||||
|
|
||||||
const {mutate, isPending} = useMutation({
|
const {mutate, isPending} = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
@@ -260,12 +265,13 @@ function AppealForm({
|
|||||||
<Trans>
|
<Trans>
|
||||||
This appeal will be sent to{' '}
|
This appeal will be sent to{' '}
|
||||||
<InlineLinkText
|
<InlineLinkText
|
||||||
|
label={sourceName}
|
||||||
to={makeProfileLink(
|
to={makeProfileLink(
|
||||||
labeler ? labeler.creator : {did: label.src, handle: ''},
|
labeler ? labeler.creator : {did: label.src, handle: ''},
|
||||||
)}
|
)}
|
||||||
onPress={() => control.close()}
|
onPress={() => control.close()}
|
||||||
style={[a.text_md, a.leading_snug]}>
|
style={[a.text_md, a.leading_snug]}>
|
||||||
{labeler ? sanitizeHandle(labeler.creator.handle, '@') : label.src}
|
{sourceName}
|
||||||
</InlineLinkText>
|
</InlineLinkText>
|
||||||
.
|
.
|
||||||
</Trans>
|
</Trans>
|
||||||
|
|||||||
@@ -54,7 +54,10 @@ function ModerationDetailsDialogInner({
|
|||||||
description = (
|
description = (
|
||||||
<Trans>
|
<Trans>
|
||||||
This user is included in the{' '}
|
This user is included in the{' '}
|
||||||
<InlineLinkText to={listUriToHref(list.uri)} style={[a.text_sm]}>
|
<InlineLinkText
|
||||||
|
label={list.name}
|
||||||
|
to={listUriToHref(list.uri)}
|
||||||
|
style={[a.text_sm]}>
|
||||||
{list.name}
|
{list.name}
|
||||||
</InlineLinkText>{' '}
|
</InlineLinkText>{' '}
|
||||||
list which you have blocked.
|
list which you have blocked.
|
||||||
@@ -83,7 +86,10 @@ function ModerationDetailsDialogInner({
|
|||||||
description = (
|
description = (
|
||||||
<Trans>
|
<Trans>
|
||||||
This user is included in the{' '}
|
This user is included in the{' '}
|
||||||
<InlineLinkText to={listUriToHref(list.uri)} style={[a.text_sm]}>
|
<InlineLinkText
|
||||||
|
label={list.name}
|
||||||
|
to={listUriToHref(list.uri)}
|
||||||
|
style={[a.text_sm]}>
|
||||||
{list.name}
|
{list.name}
|
||||||
</InlineLinkText>{' '}
|
</InlineLinkText>{' '}
|
||||||
list which you have muted.
|
list which you have muted.
|
||||||
@@ -127,10 +133,11 @@ function ModerationDetailsDialogInner({
|
|||||||
<Trans>
|
<Trans>
|
||||||
This label was applied by{' '}
|
This label was applied by{' '}
|
||||||
<InlineLinkText
|
<InlineLinkText
|
||||||
|
label={desc.source || _(msg`an unknown labeler`)}
|
||||||
to={makeProfileLink({did: modcause.label.src, handle: ''})}
|
to={makeProfileLink({did: modcause.label.src, handle: ''})}
|
||||||
onPress={() => control.close()}
|
onPress={() => control.close()}
|
||||||
style={a.text_md}>
|
style={a.text_md}>
|
||||||
{desc.source}
|
{desc.source || _(msg`an unknown labeler`)}
|
||||||
</InlineLinkText>
|
</InlineLinkText>
|
||||||
.
|
.
|
||||||
</Trans>
|
</Trans>
|
||||||
|
|||||||
+70
-61
@@ -14,29 +14,33 @@ export type FeedTunerFn = (
|
|||||||
slices: FeedViewPostsSlice[],
|
slices: FeedViewPostsSlice[],
|
||||||
) => FeedViewPostsSlice[]
|
) => FeedViewPostsSlice[]
|
||||||
|
|
||||||
|
type FeedSliceItem = {
|
||||||
|
post: AppBskyFeedDefs.PostView
|
||||||
|
reply?: AppBskyFeedDefs.ReplyRef
|
||||||
|
}
|
||||||
|
|
||||||
|
function toSliceItem(feedViewPost: FeedViewPost): FeedSliceItem {
|
||||||
|
return {
|
||||||
|
post: feedViewPost.post,
|
||||||
|
reply: feedViewPost.reply,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class FeedViewPostsSlice {
|
export class FeedViewPostsSlice {
|
||||||
_reactKey: string
|
_reactKey: string
|
||||||
isFlattenedReply = false
|
_feedPost: FeedViewPost
|
||||||
|
items: FeedSliceItem[]
|
||||||
|
|
||||||
constructor(public items: FeedViewPost[]) {
|
constructor(feedPost: FeedViewPost) {
|
||||||
const item = items[0]
|
this._feedPost = feedPost
|
||||||
this._reactKey = `slice-${item.post.uri}-${
|
this._reactKey = `slice-${feedPost.post.uri}-${
|
||||||
item.reason?.indexedAt || item.post.indexedAt
|
feedPost.reason?.indexedAt || feedPost.post.indexedAt
|
||||||
}`
|
}`
|
||||||
|
this.items = [toSliceItem(feedPost)]
|
||||||
}
|
}
|
||||||
|
|
||||||
get uri() {
|
get uri() {
|
||||||
if (this.isFlattenedReply) {
|
return this._feedPost.post.uri
|
||||||
return this.items[1].post.uri
|
|
||||||
}
|
|
||||||
return this.items[0].post.uri
|
|
||||||
}
|
|
||||||
|
|
||||||
get ts() {
|
|
||||||
if (this.items[0].reason?.indexedAt) {
|
|
||||||
return this.items[0].reason.indexedAt as string
|
|
||||||
}
|
|
||||||
return this.items[0].post.indexedAt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get isThread() {
|
get isThread() {
|
||||||
@@ -48,31 +52,42 @@ export class FeedViewPostsSlice {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
get isFullThread() {
|
get isQuotePost() {
|
||||||
return this.isThread && !this.items[0].reply
|
const embed = this._feedPost.post.embed
|
||||||
}
|
return (
|
||||||
|
AppBskyEmbedRecord.isView(embed) ||
|
||||||
get rootItem() {
|
AppBskyEmbedRecordWithMedia.isView(embed)
|
||||||
if (this.isFlattenedReply) {
|
)
|
||||||
return this.items[1]
|
|
||||||
}
|
|
||||||
return this.items[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get isReply() {
|
get isReply() {
|
||||||
return (
|
return (
|
||||||
AppBskyFeedPost.isRecord(this.rootItem.post.record) &&
|
AppBskyFeedPost.isRecord(this._feedPost.post.record) &&
|
||||||
!!this.rootItem.post.record.reply
|
!!this._feedPost.post.record.reply
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
get source(): ReasonFeedSource | undefined {
|
get reason() {
|
||||||
return this.items.find(item => '__source' in item && !!item.__source)
|
return '__source' in this._feedPost
|
||||||
?.__source as ReasonFeedSource
|
? (this._feedPost.__source as ReasonFeedSource)
|
||||||
|
: this._feedPost.reason
|
||||||
}
|
}
|
||||||
|
|
||||||
get feedContext() {
|
get feedContext() {
|
||||||
return this.items.find(item => item.feedContext)?.feedContext
|
return this._feedPost.feedContext
|
||||||
|
}
|
||||||
|
|
||||||
|
get isRepost() {
|
||||||
|
const reason = this._feedPost.reason
|
||||||
|
return AppBskyFeedDefs.isReasonRepost(reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
get includesThreadRoot() {
|
||||||
|
return !this.items[0].reply
|
||||||
|
}
|
||||||
|
|
||||||
|
get likeCount() {
|
||||||
|
return this._feedPost.post.likeCount ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
containsUri(uri: string) {
|
containsUri(uri: string) {
|
||||||
@@ -97,24 +112,24 @@ export class FeedViewPostsSlice {
|
|||||||
if (this.items[0].reply) {
|
if (this.items[0].reply) {
|
||||||
const reply = this.items[0].reply
|
const reply = this.items[0].reply
|
||||||
if (AppBskyFeedDefs.isPostView(reply.parent)) {
|
if (AppBskyFeedDefs.isPostView(reply.parent)) {
|
||||||
this.isFlattenedReply = true
|
|
||||||
this.items.splice(0, 0, {post: reply.parent})
|
this.items.splice(0, 0, {post: reply.parent})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isFollowingAllAuthors(userDid: string) {
|
isFollowingAllAuthors(userDid: string) {
|
||||||
const item = this.rootItem
|
const feedPost = this._feedPost
|
||||||
if (item.post.author.did === userDid) {
|
if (feedPost.post.author.did === userDid) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (AppBskyFeedDefs.isPostView(item.reply?.parent)) {
|
if (AppBskyFeedDefs.isPostView(feedPost.reply?.parent)) {
|
||||||
const parent = item.reply?.parent
|
const parent = feedPost.reply?.parent
|
||||||
if (parent?.author.did === userDid) {
|
if (parent?.author.did === userDid) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
parent?.author.viewer?.following && item.post.author.viewer?.following
|
parent?.author.viewer?.following &&
|
||||||
|
feedPost.post.author.viewer?.following
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -127,7 +142,7 @@ export class NoopFeedTuner {
|
|||||||
feed: FeedViewPost[],
|
feed: FeedViewPost[],
|
||||||
_opts?: {dryRun: boolean; maintainOrder: boolean},
|
_opts?: {dryRun: boolean; maintainOrder: boolean},
|
||||||
): FeedViewPostsSlice[] {
|
): FeedViewPostsSlice[] {
|
||||||
return feed.map(item => new FeedViewPostsSlice([item]))
|
return feed.map(item => new FeedViewPostsSlice(item))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +180,7 @@ export class FeedTuner {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (maintainOrder) {
|
if (maintainOrder) {
|
||||||
slices = feed.map(item => new FeedViewPostsSlice([item]))
|
slices = feed.map(item => new FeedViewPostsSlice(item))
|
||||||
} else {
|
} else {
|
||||||
// arrange the posts into thread slices
|
// arrange the posts into thread slices
|
||||||
for (let i = feed.length - 1; i >= 0; i--) {
|
for (let i = feed.length - 1; i >= 0; i--) {
|
||||||
@@ -192,7 +207,7 @@ export class FeedTuner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slices.unshift(new FeedViewPostsSlice([item]))
|
slices.unshift(new FeedViewPostsSlice(item))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +230,7 @@ export class FeedTuner {
|
|||||||
|
|
||||||
// turn non-threads with reply parents into threads
|
// turn non-threads with reply parents into threads
|
||||||
for (const slice of slices) {
|
for (const slice of slices) {
|
||||||
if (!slice.isThread && !slice.items[0].reason && slice.items[0].reply) {
|
if (!slice.isThread && !slice.reason && slice.items[0].reply) {
|
||||||
const reply = slice.items[0].reply
|
const reply = slice.items[0].reply
|
||||||
if (
|
if (
|
||||||
AppBskyFeedDefs.isPostView(reply.parent) &&
|
AppBskyFeedDefs.isPostView(reply.parent) &&
|
||||||
@@ -256,8 +271,7 @@ export class FeedTuner {
|
|||||||
|
|
||||||
static removeReposts(tuner: FeedTuner, slices: FeedViewPostsSlice[]) {
|
static removeReposts(tuner: FeedTuner, slices: FeedViewPostsSlice[]) {
|
||||||
for (let i = slices.length - 1; i >= 0; i--) {
|
for (let i = slices.length - 1; i >= 0; i--) {
|
||||||
const reason = slices[i].rootItem.reason
|
if (slices[i].isRepost) {
|
||||||
if (AppBskyFeedDefs.isReasonRepost(reason)) {
|
|
||||||
slices.splice(i, 1)
|
slices.splice(i, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -266,11 +280,7 @@ export class FeedTuner {
|
|||||||
|
|
||||||
static removeQuotePosts(tuner: FeedTuner, slices: FeedViewPostsSlice[]) {
|
static removeQuotePosts(tuner: FeedTuner, slices: FeedViewPostsSlice[]) {
|
||||||
for (let i = slices.length - 1; i >= 0; i--) {
|
for (let i = slices.length - 1; i >= 0; i--) {
|
||||||
const embed = slices[i].rootItem.post.embed
|
if (slices[i].isQuotePost) {
|
||||||
if (
|
|
||||||
AppBskyEmbedRecord.isView(embed) ||
|
|
||||||
AppBskyEmbedRecordWithMedia.isView(embed)
|
|
||||||
) {
|
|
||||||
slices.splice(i, 1)
|
slices.splice(i, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,19 +325,18 @@ export class FeedTuner {
|
|||||||
// remove any replies without at least minLikes likes
|
// remove any replies without at least minLikes likes
|
||||||
for (let i = slices.length - 1; i >= 0; i--) {
|
for (let i = slices.length - 1; i >= 0; i--) {
|
||||||
const slice = slices[i]
|
const slice = slices[i]
|
||||||
if (slice.isFullThread || !slice.isReply) {
|
if (slice.isReply) {
|
||||||
continue
|
if (slice.isThread && slice.includesThreadRoot) {
|
||||||
}
|
continue
|
||||||
|
}
|
||||||
const item = slice.rootItem
|
if (slice.isRepost) {
|
||||||
const isRepost = Boolean(item.reason)
|
continue
|
||||||
if (isRepost) {
|
}
|
||||||
continue
|
if (slice.likeCount < minLikes) {
|
||||||
}
|
slices.splice(i, 1)
|
||||||
if ((item.post.likeCount || 0) < minLikes) {
|
} else if (followedOnly && !slice.isFollowingAllAuthors(userDid)) {
|
||||||
slices.splice(i, 1)
|
slices.splice(i, 1)
|
||||||
} else if (followedOnly && !slice.isFollowingAllAuthors(userDid)) {
|
}
|
||||||
slices.splice(i, 1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return slices
|
return slices
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ class MergeFeedSource_Following extends MergeFeedSource {
|
|||||||
dryRun: false,
|
dryRun: false,
|
||||||
maintainOrder: true,
|
maintainOrder: true,
|
||||||
})
|
})
|
||||||
res.data.feed = slices.map(slice => slice.rootItem)
|
res.data.feed = slices.map(slice => slice._feedPost)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,7 +240,10 @@ export function ModerationScreenInner({
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Link testID="moderationlistsBtn" to="/moderation/modlists">
|
<Link
|
||||||
|
label={_(msg`View your moderation lists`)}
|
||||||
|
testID="moderationlistsBtn"
|
||||||
|
to="/moderation/modlists">
|
||||||
{state => (
|
{state => (
|
||||||
<SubItem
|
<SubItem
|
||||||
title={_(msg`Moderation lists`)}
|
title={_(msg`Moderation lists`)}
|
||||||
@@ -252,7 +255,10 @@ export function ModerationScreenInner({
|
|||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Link testID="mutedAccountsBtn" to="/moderation/muted-accounts">
|
<Link
|
||||||
|
label={_(msg`View your muted accounts`)}
|
||||||
|
testID="mutedAccountsBtn"
|
||||||
|
to="/moderation/muted-accounts">
|
||||||
{state => (
|
{state => (
|
||||||
<SubItem
|
<SubItem
|
||||||
title={_(msg`Muted accounts`)}
|
title={_(msg`Muted accounts`)}
|
||||||
@@ -264,7 +270,10 @@ export function ModerationScreenInner({
|
|||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Link testID="blockedAccountsBtn" to="/moderation/blocked-accounts">
|
<Link
|
||||||
|
label={_(msg`View your blocked accounts`)}
|
||||||
|
testID="blockedAccountsBtn"
|
||||||
|
to="/moderation/blocked-accounts">
|
||||||
{state => (
|
{state => (
|
||||||
<SubItem
|
<SubItem
|
||||||
title={_(msg`Blocked accounts`)}
|
title={_(msg`Blocked accounts`)}
|
||||||
@@ -356,6 +365,7 @@ export function ModerationScreenInner({
|
|||||||
<Trans>
|
<Trans>
|
||||||
Adult content can only be enabled via the Web at{' '}
|
Adult content can only be enabled via the Web at{' '}
|
||||||
<InlineLinkText
|
<InlineLinkText
|
||||||
|
label={_(msg`The Bluesky web application`)}
|
||||||
to=""
|
to=""
|
||||||
onPress={evt => {
|
onPress={evt => {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
@@ -569,7 +579,9 @@ function PwiOptOut() {
|
|||||||
</Trans>
|
</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<InlineLinkText to="https://blueskyweb.zendesk.com/hc/en-us/articles/15835264007693-Data-Privacy">
|
<InlineLinkText
|
||||||
|
label={_(msg`Learn more about what is public on Bluesky.`)}
|
||||||
|
to="https://blueskyweb.zendesk.com/hc/en-us/articles/15835264007693-Data-Privacy">
|
||||||
<Trans>Learn more about what is public on Bluesky.</Trans>
|
<Trans>Learn more about what is public on Bluesky.</Trans>
|
||||||
</InlineLinkText>
|
</InlineLinkText>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -45,14 +45,20 @@ export const Policies = ({
|
|||||||
const els = []
|
const els = []
|
||||||
if (tos) {
|
if (tos) {
|
||||||
els.push(
|
els.push(
|
||||||
<InlineLinkText key="tos" to={tos}>
|
<InlineLinkText
|
||||||
|
label={_(msg`Read the Bluesky Terms of Service`)}
|
||||||
|
key="tos"
|
||||||
|
to={tos}>
|
||||||
{_(msg`Terms of Service`)}
|
{_(msg`Terms of Service`)}
|
||||||
</InlineLinkText>,
|
</InlineLinkText>,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (pp) {
|
if (pp) {
|
||||||
els.push(
|
els.push(
|
||||||
<InlineLinkText key="pp" to={pp}>
|
<InlineLinkText
|
||||||
|
label={_(msg`Read the Bluesky Privacy Policy`)}
|
||||||
|
key="pp"
|
||||||
|
to={pp}>
|
||||||
{_(msg`Privacy Policy`)}
|
{_(msg`Privacy Policy`)}
|
||||||
</InlineLinkText>,
|
</InlineLinkText>,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -166,6 +166,7 @@ export function Signup({onPressBack}: {onPressBack: () => void}) {
|
|||||||
<Text style={[t.atoms.text, !gtMobile && a.text_md]}>
|
<Text style={[t.atoms.text, !gtMobile && a.text_md]}>
|
||||||
<Trans>Having trouble?</Trans>{' '}
|
<Trans>Having trouble?</Trans>{' '}
|
||||||
<InlineLinkText
|
<InlineLinkText
|
||||||
|
label={_(msg`Contact support`)}
|
||||||
to={FEEDBACK_FORM_URL({email: state.email})}
|
to={FEEDBACK_FORM_URL({email: state.email})}
|
||||||
style={[!gtMobile && a.text_md]}>
|
style={[!gtMobile && a.text_md]}>
|
||||||
<Trans>Contact support</Trans>
|
<Trans>Contact support</Trans>
|
||||||
|
|||||||
@@ -78,7 +78,10 @@ export interface FeedPostSliceItem {
|
|||||||
uri: string
|
uri: string
|
||||||
post: AppBskyFeedDefs.PostView
|
post: AppBskyFeedDefs.PostView
|
||||||
record: AppBskyFeedPost.Record
|
record: AppBskyFeedPost.Record
|
||||||
reason?: AppBskyFeedDefs.ReasonRepost | ReasonFeedSource
|
reason?:
|
||||||
|
| AppBskyFeedDefs.ReasonRepost
|
||||||
|
| ReasonFeedSource
|
||||||
|
| {[k: string]: unknown; $type: string}
|
||||||
feedContext: string | undefined
|
feedContext: string | undefined
|
||||||
moderation: ModerationDecision
|
moderation: ModerationDecision
|
||||||
parentAuthor?: AppBskyActorDefs.ProfileViewBasic
|
parentAuthor?: AppBskyActorDefs.ProfileViewBasic
|
||||||
@@ -311,7 +314,7 @@ export function usePostFeedQuery(
|
|||||||
if (isDiscover) {
|
if (isDiscover) {
|
||||||
userActionHistory.seen(
|
userActionHistory.seen(
|
||||||
slice.items.map(item => ({
|
slice.items.map(item => ({
|
||||||
feedContext: item.feedContext,
|
feedContext: slice.feedContext,
|
||||||
likeCount: item.post.likeCount ?? 0,
|
likeCount: item.post.likeCount ?? 0,
|
||||||
repostCount: item.post.repostCount ?? 0,
|
repostCount: item.post.repostCount ?? 0,
|
||||||
replyCount: item.post.replyCount ?? 0,
|
replyCount: item.post.replyCount ?? 0,
|
||||||
@@ -323,10 +326,10 @@ export function usePostFeedQuery(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const feedPostSlice: FeedPostSlice = {
|
||||||
_reactKey: slice._reactKey,
|
_reactKey: slice._reactKey,
|
||||||
_isFeedPostSlice: true,
|
_isFeedPostSlice: true,
|
||||||
rootUri: slice.rootItem.post.uri,
|
rootUri: slice.uri,
|
||||||
isThread:
|
isThread:
|
||||||
slice.items.length > 1 &&
|
slice.items.length > 1 &&
|
||||||
slice.items.every(
|
slice.items.every(
|
||||||
@@ -341,35 +344,42 @@ export function usePostFeedQuery(
|
|||||||
AppBskyFeedPost.validateRecord(item.post.record)
|
AppBskyFeedPost.validateRecord(item.post.record)
|
||||||
.success
|
.success
|
||||||
) {
|
) {
|
||||||
const parentAuthor =
|
const parent = item.reply?.parent
|
||||||
item.reply?.parent?.author ??
|
let parentAuthor:
|
||||||
slice.items[i + 1]?.reply?.grandparentAuthor
|
| AppBskyActorDefs.ProfileViewBasic
|
||||||
|
| undefined
|
||||||
|
if (AppBskyFeedDefs.isPostView(parent)) {
|
||||||
|
parentAuthor = parent.author
|
||||||
|
}
|
||||||
|
if (!parentAuthor) {
|
||||||
|
parentAuthor =
|
||||||
|
slice.items[i + 1]?.reply?.grandparentAuthor
|
||||||
|
}
|
||||||
const replyRef = item.reply
|
const replyRef = item.reply
|
||||||
const isParentBlocked = AppBskyFeedDefs.isBlockedPost(
|
const isParentBlocked = AppBskyFeedDefs.isBlockedPost(
|
||||||
replyRef?.parent,
|
replyRef?.parent,
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
const feedPostSliceItem: FeedPostSliceItem = {
|
||||||
_reactKey: `${slice._reactKey}-${i}-${item.post.uri}`,
|
_reactKey: `${slice._reactKey}-${i}-${item.post.uri}`,
|
||||||
uri: item.post.uri,
|
uri: item.post.uri,
|
||||||
post: item.post,
|
post: item.post,
|
||||||
record: item.post.record,
|
record: item.post.record,
|
||||||
reason:
|
reason: slice.reason,
|
||||||
i === 0 && slice.source
|
feedContext: slice.feedContext,
|
||||||
? slice.source
|
|
||||||
: item.reason,
|
|
||||||
feedContext: item.feedContext || slice.feedContext,
|
|
||||||
moderation: moderations[i],
|
moderation: moderations[i],
|
||||||
parentAuthor,
|
parentAuthor,
|
||||||
isParentBlocked,
|
isParentBlocked,
|
||||||
}
|
}
|
||||||
|
return feedPostSliceItem
|
||||||
}
|
}
|
||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
.filter(Boolean) as FeedPostSliceItem[],
|
.filter(n => !!n),
|
||||||
}
|
}
|
||||||
|
return feedPostSlice
|
||||||
})
|
})
|
||||||
.filter(Boolean) as FeedPostSlice[],
|
.filter(n => !!n),
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export type ThreadgateSetting =
|
|||||||
| {type: 'nobody'}
|
| {type: 'nobody'}
|
||||||
| {type: 'mention'}
|
| {type: 'mention'}
|
||||||
| {type: 'following'}
|
| {type: 'following'}
|
||||||
| {type: 'list'; list: string}
|
| {type: 'list'; list: unknown}
|
||||||
|
|
||||||
export function threadgateViewToSettings(
|
export function threadgateViewToSettings(
|
||||||
threadgate: AppBskyFeedDefs.ThreadgateView | undefined,
|
threadgate: AppBskyFeedDefs.ThreadgateView | undefined,
|
||||||
@@ -21,18 +21,18 @@ export function threadgateViewToSettings(
|
|||||||
if (!record.allow?.length) {
|
if (!record.allow?.length) {
|
||||||
return [{type: 'nobody'}]
|
return [{type: 'nobody'}]
|
||||||
}
|
}
|
||||||
return record.allow
|
const settings: ThreadgateSetting[] = record.allow
|
||||||
.map(allow => {
|
.map(allow => {
|
||||||
|
let setting: ThreadgateSetting | undefined
|
||||||
if (allow.$type === 'app.bsky.feed.threadgate#mentionRule') {
|
if (allow.$type === 'app.bsky.feed.threadgate#mentionRule') {
|
||||||
return {type: 'mention'}
|
setting = {type: 'mention'}
|
||||||
|
} else if (allow.$type === 'app.bsky.feed.threadgate#followingRule') {
|
||||||
|
setting = {type: 'following'}
|
||||||
|
} else if (allow.$type === 'app.bsky.feed.threadgate#listRule') {
|
||||||
|
setting = {type: 'list', list: allow.list}
|
||||||
}
|
}
|
||||||
if (allow.$type === 'app.bsky.feed.threadgate#followingRule') {
|
return setting
|
||||||
return {type: 'following'}
|
|
||||||
}
|
|
||||||
if (allow.$type === 'app.bsky.feed.threadgate#listRule') {
|
|
||||||
return {type: 'list', list: allow.list}
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
})
|
})
|
||||||
.filter(Boolean) as ThreadgateSetting[]
|
.filter(n => !!n)
|
||||||
|
return settings
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ export const SplashScreen = ({
|
|||||||
|
|
||||||
function Footer() {
|
function Footer() {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
|
const {_} = useLingui()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
@@ -147,13 +148,19 @@ function Footer() {
|
|||||||
a.flex_1,
|
a.flex_1,
|
||||||
t.atoms.border_contrast_medium,
|
t.atoms.border_contrast_medium,
|
||||||
]}>
|
]}>
|
||||||
<InlineLinkText to="https://bsky.social">
|
<InlineLinkText
|
||||||
|
label={_(msg`Learn more about Bluesky`)}
|
||||||
|
to="https://bsky.social">
|
||||||
<Trans>Business</Trans>
|
<Trans>Business</Trans>
|
||||||
</InlineLinkText>
|
</InlineLinkText>
|
||||||
<InlineLinkText to="https://bsky.social/about/blog">
|
<InlineLinkText
|
||||||
|
label={_(msg`Read the Bluesky blog`)}
|
||||||
|
to="https://bsky.social/about/blog">
|
||||||
<Trans>Blog</Trans>
|
<Trans>Blog</Trans>
|
||||||
</InlineLinkText>
|
</InlineLinkText>
|
||||||
<InlineLinkText to="https://bsky.social/about/join">
|
<InlineLinkText
|
||||||
|
label={_(msg`See jobs at Bluesky`)}
|
||||||
|
to="https://bsky.social/about/join">
|
||||||
<Trans>Jobs</Trans>
|
<Trans>Jobs</Trans>
|
||||||
</InlineLinkText>
|
</InlineLinkText>
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,6 @@ import {SelectVideoBtn} from './videos/SelectVideoBtn'
|
|||||||
import {useVideoState} from './videos/state'
|
import {useVideoState} from './videos/state'
|
||||||
import {VideoPreview} from './videos/VideoPreview'
|
import {VideoPreview} from './videos/VideoPreview'
|
||||||
import {VideoTranscodeProgress} from './videos/VideoTranscodeProgress'
|
import {VideoTranscodeProgress} from './videos/VideoTranscodeProgress'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
|
|
||||||
type CancelRef = {
|
type CancelRef = {
|
||||||
onPressCancel: () => void
|
onPressCancel: () => void
|
||||||
@@ -763,7 +762,7 @@ function useAnimatedBorders() {
|
|||||||
|
|
||||||
const topBarAnimatedStyle = useAnimatedStyle(() => {
|
const topBarAnimatedStyle = useAnimatedStyle(() => {
|
||||||
return {
|
return {
|
||||||
borderBottomWidth: hairlineWidth,
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
borderColor: interpolateColor(
|
borderColor: interpolateColor(
|
||||||
hasScrolledTop.value,
|
hasScrolledTop.value,
|
||||||
[0, 1],
|
[0, 1],
|
||||||
@@ -773,7 +772,7 @@ function useAnimatedBorders() {
|
|||||||
})
|
})
|
||||||
const bottomBarAnimatedStyle = useAnimatedStyle(() => {
|
const bottomBarAnimatedStyle = useAnimatedStyle(() => {
|
||||||
return {
|
return {
|
||||||
borderTopWidth: hairlineWidth,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
borderColor: interpolateColor(
|
borderColor: interpolateColor(
|
||||||
hasScrolledBottom.value,
|
hasScrolledBottom.value,
|
||||||
[0, 1],
|
[0, 1],
|
||||||
@@ -855,7 +854,7 @@ const styles = StyleSheet.create({
|
|||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
errorIcon: {
|
errorIcon: {
|
||||||
borderWidth: hairlineWidth,
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
borderColor: colors.red4,
|
borderColor: colors.red4,
|
||||||
color: colors.red4,
|
color: colors.red4,
|
||||||
borderRadius: 30,
|
borderRadius: 30,
|
||||||
@@ -891,6 +890,6 @@ const styles = StyleSheet.create({
|
|||||||
paddingLeft: 7,
|
paddingLeft: 7,
|
||||||
paddingRight: 16,
|
paddingRight: 16,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderTopWidth: hairlineWidth,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {usePalette} from 'lib/hooks/usePalette'
|
|||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import {UserAvatar} from '../util/UserAvatar'
|
import {UserAvatar} from '../util/UserAvatar'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
|
|
||||||
export function ComposePrompt({onPressCompose}: {onPressCompose: () => void}) {
|
export function ComposePrompt({onPressCompose}: {onPressCompose: () => void}) {
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
@@ -49,7 +48,7 @@ const styles = StyleSheet.create({
|
|||||||
paddingBottom: 10,
|
paddingBottom: 10,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderTopWidth: hairlineWidth,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
labelMobile: {
|
labelMobile: {
|
||||||
paddingLeft: 12,
|
paddingLeft: 12,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {msg, Plural, Trans} from '@lingui/macro'
|
|||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
|
import {shouldClickOpenNewTab} from '#/platform/urls'
|
||||||
import {FeedSourceInfo, useFeedSourceInfoQuery} from '#/state/queries/feed'
|
import {FeedSourceInfo, useFeedSourceInfoQuery} from '#/state/queries/feed'
|
||||||
import {
|
import {
|
||||||
useAddSavedFeedsMutation,
|
useAddSavedFeedsMutation,
|
||||||
@@ -32,8 +33,6 @@ import * as Prompt from '#/components/Prompt'
|
|||||||
import {RichText} from '#/components/RichText'
|
import {RichText} from '#/components/RichText'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import {UserAvatar} from '../util/UserAvatar'
|
import {UserAvatar} from '../util/UserAvatar'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
import {shouldClickOpenNewTab} from '#/platform/urls'
|
|
||||||
|
|
||||||
export function FeedSourceCard({
|
export function FeedSourceCard({
|
||||||
feedUri,
|
feedUri,
|
||||||
@@ -209,7 +208,7 @@ export function FeedSourceCardLoaded({
|
|||||||
styles.container,
|
styles.container,
|
||||||
pal.border,
|
pal.border,
|
||||||
style,
|
style,
|
||||||
{borderTopWidth: hideTopBorder ? 0 : hairlineWidth},
|
{borderTopWidth: hideTopBorder ? 0 : StyleSheet.hairlineWidth},
|
||||||
]}
|
]}
|
||||||
onPress={e => {
|
onPress={e => {
|
||||||
const shouldOpenInNewTab = shouldClickOpenNewTab(e)
|
const shouldOpenInNewTab = shouldClickOpenNewTab(e)
|
||||||
@@ -330,7 +329,7 @@ const styles = StyleSheet.create({
|
|||||||
gap: 14,
|
gap: 14,
|
||||||
},
|
},
|
||||||
border: {
|
border: {
|
||||||
borderTopWidth: hairlineWidth,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
headerContainer: {
|
headerContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export function HomeHeaderLayoutMobile({
|
|||||||
]}>
|
]}>
|
||||||
{IS_DEV && (
|
{IS_DEV && (
|
||||||
<>
|
<>
|
||||||
<Link to="/sys/debug">
|
<Link label="View storybook" to="/sys/debug">
|
||||||
<ColorPalette size="md" />
|
<ColorPalette size="md" />
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {RichText as RichTextCom} from '#/components/RichText'
|
|||||||
import {Link} from '../util/Link'
|
import {Link} from '../util/Link'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import {UserAvatar} from '../util/UserAvatar'
|
import {UserAvatar} from '../util/UserAvatar'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
|
|
||||||
export const ListCard = ({
|
export const ListCard = ({
|
||||||
testID,
|
testID,
|
||||||
@@ -134,7 +133,7 @@ export const ListCard = ({
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
outer: {
|
outer: {
|
||||||
borderTopWidth: hairlineWidth,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
paddingHorizontal: 6,
|
paddingHorizontal: 6,
|
||||||
},
|
},
|
||||||
outerNoBorder: {
|
outerNoBorder: {
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import {Button} from '../util/forms/Button'
|
|||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import * as Toast from '../util/Toast'
|
import * as Toast from '../util/Toast'
|
||||||
import {UserAvatar} from '../util/UserAvatar'
|
import {UserAvatar} from '../util/UserAvatar'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
|
|
||||||
export const snapPoints = ['fullscreen']
|
export const snapPoints = ['fullscreen']
|
||||||
|
|
||||||
@@ -63,7 +62,7 @@ export function Component({
|
|||||||
return [pal.border, {height: screenHeight / 1.5}]
|
return [pal.border, {height: screenHeight / 1.5}]
|
||||||
}
|
}
|
||||||
|
|
||||||
return [pal.border, {flex: 1, borderTopWidth: hairlineWidth}]
|
return [pal.border, {flex: 1, borderTopWidth: StyleSheet.hairlineWidth}]
|
||||||
}, [pal.border, screenHeight])
|
}, [pal.border, screenHeight])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -188,7 +187,7 @@ function ListItem({
|
|||||||
style={[
|
style={[
|
||||||
styles.listItem,
|
styles.listItem,
|
||||||
pal.border,
|
pal.border,
|
||||||
index !== 0 && {borderTopWidth: hairlineWidth},
|
index !== 0 && {borderTopWidth: StyleSheet.hairlineWidth},
|
||||||
]}>
|
]}>
|
||||||
<View style={styles.listItemAvi}>
|
<View style={styles.listItemAvi}>
|
||||||
<UserAvatar size={40} avatar={list.avatar} type="list" />
|
<UserAvatar size={40} avatar={list.avatar} type="list" />
|
||||||
@@ -248,7 +247,7 @@ const styles = StyleSheet.create({
|
|||||||
gap: 10,
|
gap: 10,
|
||||||
paddingTop: 10,
|
paddingTop: 10,
|
||||||
paddingBottom: isAndroid ? 10 : 0,
|
paddingBottom: isAndroid ? 10 : 0,
|
||||||
borderTopWidth: hairlineWidth,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
footerBtn: {
|
footerBtn: {
|
||||||
paddingHorizontal: 24,
|
paddingHorizontal: 24,
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import {NotificationFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceho
|
|||||||
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
|
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
import {CenteredView} from '#/view/com/util/Views'
|
||||||
import {FeedItem} from './FeedItem'
|
import {FeedItem} from './FeedItem'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
|
|
||||||
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
||||||
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
|
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
|
||||||
@@ -137,7 +136,7 @@ export function Feed({
|
|||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
pal.border,
|
pal.border,
|
||||||
!isTabletOrMobile && {borderTopWidth: hairlineWidth},
|
!isTabletOrMobile && {borderTopWidth: StyleSheet.hairlineWidth},
|
||||||
]}>
|
]}>
|
||||||
<NotificationFeedLoadingPlaceholder />
|
<NotificationFeedLoadingPlaceholder />
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -20,20 +20,28 @@ import {
|
|||||||
import {AtUri} from '@atproto/api'
|
import {AtUri} from '@atproto/api'
|
||||||
import {msg, plural, Trans} from '@lingui/macro'
|
import {msg, plural, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {useNavigation} from '@react-navigation/native'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {useGate} from '#/lib/statsig/statsig'
|
import {useGate} from '#/lib/statsig/statsig'
|
||||||
|
import {parseTenorGif} from '#/lib/strings/embed-player'
|
||||||
|
import {logger} from '#/logger'
|
||||||
import {FeedNotification} from '#/state/queries/notifications/feed'
|
import {FeedNotification} from '#/state/queries/notifications/feed'
|
||||||
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
|
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {makeProfileLink} from 'lib/routes/links'
|
import {makeProfileLink} from 'lib/routes/links'
|
||||||
|
import {NavigationProp} from 'lib/routes/types'
|
||||||
|
import {forceLTR} from 'lib/strings/bidi'
|
||||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||||
import {sanitizeHandle} from 'lib/strings/handles'
|
import {sanitizeHandle} from 'lib/strings/handles'
|
||||||
import {niceDate} from 'lib/strings/time'
|
import {niceDate} from 'lib/strings/time'
|
||||||
import {colors, s} from 'lib/styles'
|
import {colors, s} from 'lib/styles'
|
||||||
import {isWeb} from 'platform/detection'
|
import {isWeb} from 'platform/detection'
|
||||||
|
import {DM_SERVICE_HEADERS} from 'state/queries/messages/const'
|
||||||
import {precacheProfile} from 'state/queries/profile'
|
import {precacheProfile} from 'state/queries/profile'
|
||||||
|
import {useAgent} from 'state/session'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import {
|
import {
|
||||||
ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon,
|
ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon,
|
||||||
ChevronTop_Stroke2_Corner0_Rounded as ChevronUpIcon,
|
ChevronTop_Stroke2_Corner0_Rounded as ChevronUpIcon,
|
||||||
@@ -41,8 +49,10 @@ import {
|
|||||||
import {Heart2_Filled_Stroke2_Corner0_Rounded as HeartIconFilled} from '#/components/icons/Heart2'
|
import {Heart2_Filled_Stroke2_Corner0_Rounded as HeartIconFilled} from '#/components/icons/Heart2'
|
||||||
import {PersonPlus_Filled_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person'
|
import {PersonPlus_Filled_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person'
|
||||||
import {Repost_Stroke2_Corner2_Rounded as RepostIcon} from '#/components/icons/Repost'
|
import {Repost_Stroke2_Corner2_Rounded as RepostIcon} from '#/components/icons/Repost'
|
||||||
|
import {StarterPack} from '#/components/icons/StarterPack'
|
||||||
import {Link as NewLink} from '#/components/Link'
|
import {Link as NewLink} from '#/components/Link'
|
||||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||||
|
import {Notification as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
|
||||||
import {FeedSourceCard} from '../feeds/FeedSourceCard'
|
import {FeedSourceCard} from '../feeds/FeedSourceCard'
|
||||||
import {Post} from '../post/Post'
|
import {Post} from '../post/Post'
|
||||||
import {ImageHorzList} from '../util/images/ImageHorzList'
|
import {ImageHorzList} from '../util/images/ImageHorzList'
|
||||||
@@ -52,19 +62,6 @@ import {Text} from '../util/text/Text'
|
|||||||
import {TimeElapsed} from '../util/TimeElapsed'
|
import {TimeElapsed} from '../util/TimeElapsed'
|
||||||
import {PreviewableUserAvatar, UserAvatar} from '../util/UserAvatar'
|
import {PreviewableUserAvatar, UserAvatar} from '../util/UserAvatar'
|
||||||
|
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
import {useNavigation} from '@react-navigation/native'
|
|
||||||
|
|
||||||
import {parseTenorGif} from '#/lib/strings/embed-player'
|
|
||||||
import {logger} from '#/logger'
|
|
||||||
import {NavigationProp} from 'lib/routes/types'
|
|
||||||
import {forceLTR} from 'lib/strings/bidi'
|
|
||||||
import {DM_SERVICE_HEADERS} from 'state/queries/messages/const'
|
|
||||||
import {useAgent} from 'state/session'
|
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
|
||||||
import {StarterPack} from '#/components/icons/StarterPack'
|
|
||||||
import {Notification as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
|
|
||||||
|
|
||||||
const MAX_AUTHORS = 5
|
const MAX_AUTHORS = 5
|
||||||
|
|
||||||
const EXPANDED_AUTHOR_EL_HEIGHT = 35
|
const EXPANDED_AUTHOR_EL_HEIGHT = 35
|
||||||
@@ -171,7 +168,6 @@ let FeedItem = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let isFollowBack = false
|
|
||||||
let action = ''
|
let action = ''
|
||||||
let icon = (
|
let icon = (
|
||||||
<HeartIconFilled
|
<HeartIconFilled
|
||||||
@@ -192,7 +188,6 @@ let FeedItem = ({
|
|||||||
item.notification.author.viewer?.following &&
|
item.notification.author.viewer?.following &&
|
||||||
gate('ungroup_follow_backs')
|
gate('ungroup_follow_backs')
|
||||||
) {
|
) {
|
||||||
isFollowBack = true
|
|
||||||
action = _(msg`followed you back`)
|
action = _(msg`followed you back`)
|
||||||
} else {
|
} else {
|
||||||
action = _(msg`followed you`)
|
action = _(msg`followed you`)
|
||||||
@@ -211,7 +206,22 @@ let FeedItem = ({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
let formattedCount = authors.length > 1 ? formatCount(authors.length - 1) : ''
|
const formattedCount =
|
||||||
|
authors.length > 1 ? formatCount(authors.length - 1) : ''
|
||||||
|
const firstAuthorName = sanitizeDisplayName(
|
||||||
|
authors[0].profile.displayName || authors[0].profile.handle,
|
||||||
|
)
|
||||||
|
const niceTimestamp = niceDate(item.notification.indexedAt)
|
||||||
|
const a11yLabelUsers =
|
||||||
|
authors.length > 1
|
||||||
|
? _(msg` and `) +
|
||||||
|
plural(authors.length - 1, {
|
||||||
|
one: `${formattedCount} other`,
|
||||||
|
other: `${formattedCount} others`,
|
||||||
|
})
|
||||||
|
: ''
|
||||||
|
const a11yLabel = `${firstAuthorName}${a11yLabelUsers} ${action} ${niceTimestamp}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
testID={`feedItem-by-${item.notification.author.handle}`}
|
testID={`feedItem-by-${item.notification.author.handle}`}
|
||||||
@@ -224,10 +234,12 @@ let FeedItem = ({
|
|||||||
backgroundColor: pal.colors.unreadNotifBg,
|
backgroundColor: pal.colors.unreadNotifBg,
|
||||||
borderColor: pal.colors.unreadNotifBorder,
|
borderColor: pal.colors.unreadNotifBorder,
|
||||||
},
|
},
|
||||||
{borderTopWidth: hideTopBorder ? 0 : hairlineWidth},
|
{borderTopWidth: hideTopBorder ? 0 : StyleSheet.hairlineWidth},
|
||||||
]}
|
]}
|
||||||
href={itemHref}
|
href={itemHref}
|
||||||
noFeedback
|
noFeedback
|
||||||
|
accessibilityHint=""
|
||||||
|
accessibilityLabel={a11yLabel}
|
||||||
accessible={!isAuthorsExpanded}
|
accessible={!isAuthorsExpanded}
|
||||||
accessibilityActions={
|
accessibilityActions={
|
||||||
authors.length > 1
|
authors.length > 1
|
||||||
@@ -272,19 +284,18 @@ let FeedItem = ({
|
|||||||
visible={!isAuthorsExpanded}
|
visible={!isAuthorsExpanded}
|
||||||
authors={authors}
|
authors={authors}
|
||||||
onToggleAuthorsExpanded={onToggleAuthorsExpanded}
|
onToggleAuthorsExpanded={onToggleAuthorsExpanded}
|
||||||
showDmButton={item.type === 'starterpack-joined' || isFollowBack}
|
showDmButton={item.type === 'starterpack-joined'}
|
||||||
/>
|
/>
|
||||||
<ExpandedAuthorsList visible={isAuthorsExpanded} authors={authors} />
|
<ExpandedAuthorsList visible={isAuthorsExpanded} authors={authors} />
|
||||||
<Text style={[styles.meta, a.self_start]}>
|
<Text
|
||||||
|
style={[styles.meta, a.self_start]}
|
||||||
|
accessibilityHint=""
|
||||||
|
accessibilityLabel={a11yLabel}>
|
||||||
<TextLink
|
<TextLink
|
||||||
key={authors[0].href}
|
key={authors[0].href}
|
||||||
style={[pal.text, s.bold]}
|
style={[pal.text, s.bold]}
|
||||||
href={authors[0].href}
|
href={authors[0].href}
|
||||||
text={forceLTR(
|
text={forceLTR(firstAuthorName)}
|
||||||
sanitizeDisplayName(
|
|
||||||
authors[0].profile.displayName || authors[0].profile.handle,
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
disableMismatchWarning
|
disableMismatchWarning
|
||||||
/>
|
/>
|
||||||
{authors.length > 1 ? (
|
{authors.length > 1 ? (
|
||||||
@@ -306,7 +317,7 @@ let FeedItem = ({
|
|||||||
{({timeElapsed}) => (
|
{({timeElapsed}) => (
|
||||||
<Text
|
<Text
|
||||||
style={[pal.textLight, styles.pointer]}
|
style={[pal.textLight, styles.pointer]}
|
||||||
title={niceDate(item.notification.indexedAt)}>
|
title={niceTimestamp}>
|
||||||
{' ' + timeElapsed}
|
{' ' + timeElapsed}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
@@ -458,7 +469,6 @@ function CondensedAuthorsList({
|
|||||||
profile={authors[0].profile}
|
profile={authors[0].profile}
|
||||||
moderation={authors[0].moderation.ui('avatar')}
|
moderation={authors[0].moderation.ui('avatar')}
|
||||||
type={authors[0].profile.associated?.labeler ? 'labeler' : 'user'}
|
type={authors[0].profile.associated?.labeler ? 'labeler' : 'user'}
|
||||||
accessible={false}
|
|
||||||
/>
|
/>
|
||||||
{showDmButton ? <SayHelloBtn profile={authors[0].profile} /> : null}
|
{showDmButton ? <SayHelloBtn profile={authors[0].profile} /> : null}
|
||||||
</View>
|
</View>
|
||||||
@@ -476,7 +486,6 @@ function CondensedAuthorsList({
|
|||||||
profile={author.profile}
|
profile={author.profile}
|
||||||
moderation={author.moderation.ui('avatar')}
|
moderation={author.moderation.ui('avatar')}
|
||||||
type={author.profile.associated?.labeler ? 'labeler' : 'user'}
|
type={author.profile.associated?.labeler ? 'labeler' : 'user'}
|
||||||
accessible={false}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|||||||
import {PressableWithHover} from '../util/PressableWithHover'
|
import {PressableWithHover} from '../util/PressableWithHover'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import {DraggableScrollView} from './DraggableScrollView'
|
import {DraggableScrollView} from './DraggableScrollView'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
|
|
||||||
export interface TabBarProps {
|
export interface TabBarProps {
|
||||||
testID?: string
|
testID?: string
|
||||||
@@ -208,6 +207,6 @@ const mobileStyles = StyleSheet.create({
|
|||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
top: '100%',
|
top: '100%',
|
||||||
borderBottomWidth: hairlineWidth,
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ import {PostEmbeds} from '../util/post-embeds'
|
|||||||
import {PostMeta} from '../util/PostMeta'
|
import {PostMeta} from '../util/PostMeta'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import {PreviewableUserAvatar} from '../util/UserAvatar'
|
import {PreviewableUserAvatar} from '../util/UserAvatar'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
|
|
||||||
export function PostThreadItem({
|
export function PostThreadItem({
|
||||||
post,
|
post,
|
||||||
@@ -604,7 +603,7 @@ function PostOuterWrapper({
|
|||||||
{
|
{
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
paddingHorizontal: isMobile ? 10 : 6,
|
paddingHorizontal: isMobile ? 10 : 6,
|
||||||
borderTopWidth: depth === 1 ? hairlineWidth : 0,
|
borderTopWidth: depth === 1 ? StyleSheet.hairlineWidth : 0,
|
||||||
},
|
},
|
||||||
]}>
|
]}>
|
||||||
{Array.from(Array(depth - 1)).map((_, n: number) => (
|
{Array.from(Array(depth - 1)).map((_, n: number) => (
|
||||||
@@ -699,7 +698,7 @@ function getThreadAuthor(
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
outer: {
|
outer: {
|
||||||
borderTopWidth: hairlineWidth,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
paddingLeft: 8,
|
paddingLeft: 8,
|
||||||
},
|
},
|
||||||
outerHighlighted: {
|
outerHighlighted: {
|
||||||
@@ -709,7 +708,7 @@ const styles = StyleSheet.create({
|
|||||||
paddingRight: 8,
|
paddingRight: 8,
|
||||||
},
|
},
|
||||||
outerHighlightedRoot: {
|
outerHighlightedRoot: {
|
||||||
borderTopWidth: hairlineWidth,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
paddingTop: 16,
|
paddingTop: 16,
|
||||||
},
|
},
|
||||||
noTopBorder: {
|
noTopBorder: {
|
||||||
@@ -761,8 +760,8 @@ const styles = StyleSheet.create({
|
|||||||
expandedInfo: {
|
expandedInfo: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
padding: 10,
|
padding: 10,
|
||||||
borderTopWidth: hairlineWidth,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
borderBottomWidth: hairlineWidth,
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
marginTop: 5,
|
marginTop: 5,
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import {PostMeta} from '../util/PostMeta'
|
|||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import {PreviewableUserAvatar} from '../util/UserAvatar'
|
import {PreviewableUserAvatar} from '../util/UserAvatar'
|
||||||
import {UserInfoText} from '../util/UserInfoText'
|
import {UserInfoText} from '../util/UserInfoText'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
|
|
||||||
export function Post({
|
export function Post({
|
||||||
post,
|
post,
|
||||||
@@ -155,7 +154,7 @@ function PostInner({
|
|||||||
style={[
|
style={[
|
||||||
styles.outer,
|
styles.outer,
|
||||||
pal.border,
|
pal.border,
|
||||||
!hideTopBorder && {borderTopWidth: hairlineWidth},
|
!hideTopBorder && {borderTopWidth: StyleSheet.hairlineWidth},
|
||||||
style,
|
style,
|
||||||
]}
|
]}
|
||||||
onBeforePress={onBeforePress}>
|
onBeforePress={onBeforePress}>
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ import {msg, Trans} from '@lingui/macro'
|
|||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {useGate} from '#/lib/statsig/statsig'
|
||||||
import {POST_TOMBSTONE, Shadow, usePostShadow} from '#/state/cache/post-shadow'
|
import {POST_TOMBSTONE, Shadow, usePostShadow} from '#/state/cache/post-shadow'
|
||||||
import {useFeedFeedbackContext} from '#/state/feed-feedback'
|
import {useFeedFeedbackContext} from '#/state/feed-feedback'
|
||||||
|
import {useSession} from '#/state/session'
|
||||||
import {useComposerControls} from '#/state/shell/composer'
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
import {isReasonFeedSource, ReasonFeedSource} from 'lib/api/feed/types'
|
import {isReasonFeedSource, ReasonFeedSource} from 'lib/api/feed/types'
|
||||||
import {MAX_POST_LINES} from 'lib/constants'
|
import {MAX_POST_LINES} from 'lib/constants'
|
||||||
@@ -29,6 +31,7 @@ import {countLines} from 'lib/strings/helpers'
|
|||||||
import {s} from 'lib/styles'
|
import {s} from 'lib/styles'
|
||||||
import {precacheProfile} from 'state/queries/profile'
|
import {precacheProfile} from 'state/queries/profile'
|
||||||
import {atoms as a} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
|
import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost'
|
||||||
import {ContentHider} from '#/components/moderation/ContentHider'
|
import {ContentHider} from '#/components/moderation/ContentHider'
|
||||||
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
|
||||||
import {RichText} from '#/components/RichText'
|
import {RichText} from '#/components/RichText'
|
||||||
@@ -38,17 +41,19 @@ import {FeedNameText} from '../util/FeedInfoText'
|
|||||||
import {Link, TextLink, TextLinkOnWebOnly} from '../util/Link'
|
import {Link, TextLink, TextLinkOnWebOnly} from '../util/Link'
|
||||||
import {PostCtrls} from '../util/post-ctrls/PostCtrls'
|
import {PostCtrls} from '../util/post-ctrls/PostCtrls'
|
||||||
import {PostEmbeds} from '../util/post-embeds'
|
import {PostEmbeds} from '../util/post-embeds'
|
||||||
|
import {VideoEmbed} from '../util/post-embeds/VideoEmbed'
|
||||||
import {PostMeta} from '../util/PostMeta'
|
import {PostMeta} from '../util/PostMeta'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import {PreviewableUserAvatar} from '../util/UserAvatar'
|
import {PreviewableUserAvatar} from '../util/UserAvatar'
|
||||||
import {AviFollowButton} from './AviFollowButton'
|
import {AviFollowButton} from './AviFollowButton'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
import {useSession} from '#/state/session'
|
|
||||||
import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost'
|
|
||||||
|
|
||||||
interface FeedItemProps {
|
interface FeedItemProps {
|
||||||
record: AppBskyFeedPost.Record
|
record: AppBskyFeedPost.Record
|
||||||
reason: AppBskyFeedDefs.ReasonRepost | ReasonFeedSource | undefined
|
reason:
|
||||||
|
| AppBskyFeedDefs.ReasonRepost
|
||||||
|
| ReasonFeedSource
|
||||||
|
| {[k: string]: unknown; $type: string}
|
||||||
|
| undefined
|
||||||
moderation: ModerationDecision
|
moderation: ModerationDecision
|
||||||
parentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined
|
parentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||||
showReplyTo: boolean
|
showReplyTo: boolean
|
||||||
@@ -132,6 +137,8 @@ let FeedItemInner = ({
|
|||||||
const {openComposer} = useComposerControls()
|
const {openComposer} = useComposerControls()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
const gate = useGate()
|
||||||
|
|
||||||
const href = useMemo(() => {
|
const href = useMemo(() => {
|
||||||
const urip = new AtUri(post.uri)
|
const urip = new AtUri(post.uri)
|
||||||
return makeProfileLink(post.author, 'post', urip.rkey)
|
return makeProfileLink(post.author, 'post', urip.rkey)
|
||||||
@@ -197,7 +204,8 @@ let FeedItemInner = ({
|
|||||||
isThreadLastChild || (!isThreadChild && !isThreadParent)
|
isThreadLastChild || (!isThreadChild && !isThreadParent)
|
||||||
? 8
|
? 8
|
||||||
: undefined,
|
: undefined,
|
||||||
borderTopWidth: hideTopBorder || isThreadChild ? 0 : hairlineWidth,
|
borderTopWidth:
|
||||||
|
hideTopBorder || isThreadChild ? 0 : StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -337,9 +345,11 @@ let FeedItemInner = ({
|
|||||||
postHref={href}
|
postHref={href}
|
||||||
onOpenAuthor={onOpenAuthor}
|
onOpenAuthor={onOpenAuthor}
|
||||||
/>
|
/>
|
||||||
{!isThreadChild && showReplyTo && parentAuthor && (
|
{!isThreadChild &&
|
||||||
<ReplyToLabel blocked={isParentBlocked} profile={parentAuthor} />
|
showReplyTo &&
|
||||||
)}
|
(parentAuthor || isParentBlocked) && (
|
||||||
|
<ReplyToLabel blocked={isParentBlocked} profile={parentAuthor} />
|
||||||
|
)}
|
||||||
<LabelsOnMyPost post={post} />
|
<LabelsOnMyPost post={post} />
|
||||||
<PostContent
|
<PostContent
|
||||||
moderation={moderation}
|
moderation={moderation}
|
||||||
@@ -348,6 +358,9 @@ let FeedItemInner = ({
|
|||||||
postAuthor={post.author}
|
postAuthor={post.author}
|
||||||
onOpenEmbed={onOpenEmbed}
|
onOpenEmbed={onOpenEmbed}
|
||||||
/>
|
/>
|
||||||
|
{__DEV__ && gate('videos') && (
|
||||||
|
<VideoEmbed source="https://lumi.jazco.dev/watch/did:plc:q6gjnaw2blty4crticxkmujt/Qmc8w93UpTa2adJHg4ZhnDPrBs1EsbzrekzPcqF5SwusuZ/playlist.m3u8" />
|
||||||
|
)}
|
||||||
<PostCtrls
|
<PostCtrls
|
||||||
post={post}
|
post={post}
|
||||||
record={record}
|
record={record}
|
||||||
@@ -431,12 +444,46 @@ function ReplyToLabel({
|
|||||||
profile,
|
profile,
|
||||||
blocked,
|
blocked,
|
||||||
}: {
|
}: {
|
||||||
profile: AppBskyActorDefs.ProfileViewBasic
|
profile: AppBskyActorDefs.ProfileViewBasic | undefined
|
||||||
blocked?: boolean
|
blocked?: boolean
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {currentAccount} = useSession()
|
const {currentAccount} = useSession()
|
||||||
const isMe = profile.did === currentAccount?.did
|
|
||||||
|
let label
|
||||||
|
if (blocked) {
|
||||||
|
label = <Trans context="description">Reply to a blocked post</Trans>
|
||||||
|
} else if (profile != null) {
|
||||||
|
const isMe = profile.did === currentAccount?.did
|
||||||
|
if (isMe) {
|
||||||
|
label = <Trans context="description">Reply to you</Trans>
|
||||||
|
} else {
|
||||||
|
label = (
|
||||||
|
<Trans context="description">
|
||||||
|
Reply to{' '}
|
||||||
|
<ProfileHoverCard inline did={profile.did}>
|
||||||
|
<TextLinkOnWebOnly
|
||||||
|
type="md"
|
||||||
|
style={pal.textLight}
|
||||||
|
lineHeight={1.2}
|
||||||
|
numberOfLines={1}
|
||||||
|
href={makeProfileLink(profile)}
|
||||||
|
text={
|
||||||
|
profile.displayName
|
||||||
|
? sanitizeDisplayName(profile.displayName)
|
||||||
|
: sanitizeHandle(profile.handle)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ProfileHoverCard>
|
||||||
|
</Trans>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!label) {
|
||||||
|
// Should not happen.
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[s.flexRow, s.mb2, s.alignCenter]}>
|
<View style={[s.flexRow, s.mb2, s.alignCenter]}>
|
||||||
@@ -450,29 +497,7 @@ function ReplyToLabel({
|
|||||||
style={[pal.textLight, s.mr2]}
|
style={[pal.textLight, s.mr2]}
|
||||||
lineHeight={1.2}
|
lineHeight={1.2}
|
||||||
numberOfLines={1}>
|
numberOfLines={1}>
|
||||||
{isMe ? (
|
{label}
|
||||||
<Trans context="description">Reply to you</Trans>
|
|
||||||
) : blocked ? (
|
|
||||||
<Trans context="description">Reply to a blocked post</Trans>
|
|
||||||
) : (
|
|
||||||
<Trans context="description">
|
|
||||||
Reply to{' '}
|
|
||||||
<ProfileHoverCard inline did={profile.did}>
|
|
||||||
<TextLinkOnWebOnly
|
|
||||||
type="md"
|
|
||||||
style={pal.textLight}
|
|
||||||
lineHeight={1.2}
|
|
||||||
numberOfLines={1}
|
|
||||||
href={makeProfileLink(profile)}
|
|
||||||
text={
|
|
||||||
profile.displayName
|
|
||||||
? sanitizeDisplayName(profile.displayName)
|
|
||||||
: sanitizeHandle(profile.handle)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ProfileHoverCard>
|
|
||||||
</Trans>
|
|
||||||
)}
|
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ export function FeedShutdownMsg({feedUri}: {feedUri: string}) {
|
|||||||
<Trans>
|
<Trans>
|
||||||
This feed is no longer online. We are showing{' '}
|
This feed is no longer online. We are showing{' '}
|
||||||
<InlineLinkText
|
<InlineLinkText
|
||||||
|
label={_(msg`The Discover feed`)}
|
||||||
to="/profile/bsky.app/feed/whats-hot"
|
to="/profile/bsky.app/feed/whats-hot"
|
||||||
style={[a.text_md]}>
|
style={[a.text_md]}>
|
||||||
Discover
|
Discover
|
||||||
|
|||||||
@@ -23,12 +23,11 @@ import {
|
|||||||
KnownFollowers,
|
KnownFollowers,
|
||||||
shouldShowKnownFollowers,
|
shouldShowKnownFollowers,
|
||||||
} from '#/components/KnownFollowers'
|
} from '#/components/KnownFollowers'
|
||||||
|
import * as Pills from '#/components/Pills'
|
||||||
import {Link} from '../util/Link'
|
import {Link} from '../util/Link'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import {PreviewableUserAvatar} from '../util/UserAvatar'
|
import {PreviewableUserAvatar} from '../util/UserAvatar'
|
||||||
import {FollowButton} from './FollowButton'
|
import {FollowButton} from './FollowButton'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
import * as Pills from '#/components/Pills'
|
|
||||||
|
|
||||||
export function ProfileCard({
|
export function ProfileCard({
|
||||||
testID,
|
testID,
|
||||||
@@ -217,7 +216,7 @@ export function ProfileCardWithFollowBtn({
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
outer: {
|
outer: {
|
||||||
borderTopWidth: hairlineWidth,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
paddingHorizontal: 6,
|
paddingHorizontal: 6,
|
||||||
paddingVertical: 4,
|
paddingVertical: 4,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,15 +15,13 @@ import {makeProfileLink} from 'lib/routes/links'
|
|||||||
import {NavigationProp} from 'lib/routes/types'
|
import {NavigationProp} from 'lib/routes/types'
|
||||||
import {sanitizeHandle} from 'lib/strings/handles'
|
import {sanitizeHandle} from 'lib/strings/handles'
|
||||||
import {isNative} from 'platform/detection'
|
import {isNative} from 'platform/detection'
|
||||||
|
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
|
||||||
|
import {StarterPack} from '#/components/icons/StarterPack'
|
||||||
import {TextLink} from '../util/Link'
|
import {TextLink} from '../util/Link'
|
||||||
import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
|
import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import {UserAvatar, UserAvatarType} from '../util/UserAvatar'
|
import {UserAvatar, UserAvatarType} from '../util/UserAvatar'
|
||||||
import {CenteredView} from '../util/Views'
|
import {CenteredView} from '../util/Views'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
|
|
||||||
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
|
|
||||||
import {StarterPack} from '#/components/icons/StarterPack'
|
|
||||||
|
|
||||||
export function ProfileSubpageHeader({
|
export function ProfileSubpageHeader({
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -84,7 +82,7 @@ export function ProfileSubpageHeader({
|
|||||||
{
|
{
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderBottomWidth: hairlineWidth,
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
paddingTop: isNative ? 0 : 8,
|
paddingTop: isNative ? 0 : 8,
|
||||||
paddingBottom: 8,
|
paddingBottom: 8,
|
||||||
paddingHorizontal: isMobile ? 12 : 14,
|
paddingHorizontal: isMobile ? 12 : 14,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
Heart2_Stroke2_Corner0_Rounded as HeartIconOutline,
|
Heart2_Stroke2_Corner0_Rounded as HeartIconOutline,
|
||||||
} from '#/components/icons/Heart2'
|
} from '#/components/icons/Heart2'
|
||||||
import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost'
|
import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
|
|
||||||
export function LoadingPlaceholder({
|
export function LoadingPlaceholder({
|
||||||
width,
|
width,
|
||||||
@@ -236,7 +235,7 @@ export function FeedLoadingPlaceholder({
|
|||||||
{
|
{
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
paddingVertical: 18,
|
paddingVertical: 18,
|
||||||
borderTopWidth: showTopBorder ? hairlineWidth : 0,
|
borderTopWidth: showTopBorder ? StyleSheet.hairlineWidth : 0,
|
||||||
},
|
},
|
||||||
pal.border,
|
pal.border,
|
||||||
style,
|
style,
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ interface PreviewableUserAvatarProps extends BaseUserAvatarProps {
|
|||||||
profile: AppBskyActorDefs.ProfileViewBasic
|
profile: AppBskyActorDefs.ProfileViewBasic
|
||||||
disableHoverCard?: boolean
|
disableHoverCard?: boolean
|
||||||
onBeforePress?: () => void
|
onBeforePress?: () => void
|
||||||
accessible?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const BLUR_AMOUNT = isWeb ? 5 : 100
|
const BLUR_AMOUNT = isWeb ? 5 : 100
|
||||||
@@ -412,7 +411,6 @@ let PreviewableUserAvatar = ({
|
|||||||
profile,
|
profile,
|
||||||
disableHoverCard,
|
disableHoverCard,
|
||||||
onBeforePress,
|
onBeforePress,
|
||||||
accessible = true,
|
|
||||||
...rest
|
...rest
|
||||||
}: PreviewableUserAvatarProps): React.ReactNode => {
|
}: PreviewableUserAvatarProps): React.ReactNode => {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
@@ -426,12 +424,8 @@ let PreviewableUserAvatar = ({
|
|||||||
return (
|
return (
|
||||||
<ProfileHoverCard did={profile.did} disable={disableHoverCard}>
|
<ProfileHoverCard did={profile.did} disable={disableHoverCard}>
|
||||||
<Link
|
<Link
|
||||||
label={
|
label={_(msg`${profile.displayName || profile.handle}'s avatar`)}
|
||||||
accessible
|
accessibilityHint={_(msg`Opens this profile`)}
|
||||||
? _(msg`${profile.displayName || profile.handle}'s avatar`)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
accessibilityHint={accessible ? _(msg`Opens this profile`) : undefined}
|
|
||||||
to={makeProfileLink({
|
to={makeProfileLink({
|
||||||
did: profile.did,
|
did: profile.did,
|
||||||
handle: profile.handle,
|
handle: profile.handle,
|
||||||
|
|||||||
@@ -13,10 +13,9 @@ import {usePalette} from 'lib/hooks/usePalette'
|
|||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {NavigationProp} from 'lib/routes/types'
|
import {NavigationProp} from 'lib/routes/types'
|
||||||
import {useTheme} from '#/alf'
|
import {useTheme} from '#/alf'
|
||||||
|
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
|
||||||
import {Text} from './text/Text'
|
import {Text} from './text/Text'
|
||||||
import {CenteredView} from './Views'
|
import {CenteredView} from './Views'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
|
|
||||||
|
|
||||||
const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20}
|
const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20}
|
||||||
|
|
||||||
@@ -154,7 +153,7 @@ function DesktopWebHeader({
|
|||||||
styles.desktopHeader,
|
styles.desktopHeader,
|
||||||
pal.border,
|
pal.border,
|
||||||
{
|
{
|
||||||
borderBottomWidth: showBorder ? hairlineWidth : 0,
|
borderBottomWidth: showBorder ? StyleSheet.hairlineWidth : 0,
|
||||||
},
|
},
|
||||||
{display: 'flex', flexDirection: 'column'},
|
{display: 'flex', flexDirection: 'column'},
|
||||||
]}>
|
]}>
|
||||||
@@ -243,7 +242,7 @@ const styles = StyleSheet.create({
|
|||||||
marginRight: 'auto',
|
marginRight: 'auto',
|
||||||
},
|
},
|
||||||
border: {
|
border: {
|
||||||
borderBottomWidth: hairlineWidth,
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
titleContainer: {
|
titleContainer: {
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import Animated from 'react-native-reanimated'
|
|||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {addStyle} from 'lib/styles'
|
import {addStyle} from 'lib/styles'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
|
|
||||||
interface AddedProps {
|
interface AddedProps {
|
||||||
desktopFixedHeight?: boolean | number
|
desktopFixedHeight?: boolean | number
|
||||||
@@ -50,8 +49,8 @@ export const CenteredView = React.forwardRef(function CenteredView(
|
|||||||
}
|
}
|
||||||
if (sideBorders) {
|
if (sideBorders) {
|
||||||
style = addStyle(style, {
|
style = addStyle(style, {
|
||||||
borderLeftWidth: hairlineWidth,
|
borderLeftWidth: StyleSheet.hairlineWidth,
|
||||||
borderRightWidth: hairlineWidth,
|
borderRightWidth: StyleSheet.hairlineWidth,
|
||||||
})
|
})
|
||||||
style = addStyle(style, pal.border)
|
style = addStyle(style, pal.border)
|
||||||
}
|
}
|
||||||
@@ -163,8 +162,8 @@ export const ScrollView = React.forwardRef(function ScrollViewImpl(
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
contentContainer: {
|
contentContainer: {
|
||||||
borderLeftWidth: hairlineWidth,
|
borderLeftWidth: StyleSheet.hairlineWidth,
|
||||||
borderRightWidth: hairlineWidth,
|
borderRightWidth: StyleSheet.hairlineWidth,
|
||||||
// @ts-ignore web only
|
// @ts-ignore web only
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {clamp} from '#/lib/numbers'
|
|||||||
import {colors} from '#/lib/styles'
|
import {colors} from '#/lib/styles'
|
||||||
import {isWeb} from '#/platform/detection'
|
import {isWeb} from '#/platform/detection'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
|
|
||||||
const AnimatedTouchableOpacity =
|
const AnimatedTouchableOpacity =
|
||||||
Animated.createAnimatedComponent(TouchableOpacity)
|
Animated.createAnimatedComponent(TouchableOpacity)
|
||||||
@@ -74,7 +73,7 @@ const styles = StyleSheet.create({
|
|||||||
// @ts-ignore 'fixed' is web only -prf
|
// @ts-ignore 'fixed' is web only -prf
|
||||||
position: isWeb ? 'fixed' : 'absolute',
|
position: isWeb ? 'fixed' : 'absolute',
|
||||||
left: 18,
|
left: 18,
|
||||||
borderWidth: hairlineWidth,
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
width: 52,
|
width: 52,
|
||||||
height: 52,
|
height: 52,
|
||||||
borderRadius: 26,
|
borderRadius: 26,
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import React, {useCallback, useId, useMemo, useState} from 'react'
|
||||||
|
|
||||||
|
import {VideoPlayerProvider} from './VideoPlayerContext'
|
||||||
|
|
||||||
|
const ActiveVideoContext = React.createContext<{
|
||||||
|
activeViewId: string | null
|
||||||
|
setActiveView: (viewId: string, src: string) => void
|
||||||
|
} | null>(null)
|
||||||
|
|
||||||
|
export function ActiveVideoProvider({children}: {children: React.ReactNode}) {
|
||||||
|
const [activeViewId, setActiveViewId] = useState<string | null>(null)
|
||||||
|
const [source, setSource] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const value = useMemo(
|
||||||
|
() => ({
|
||||||
|
activeViewId,
|
||||||
|
setActiveView: (viewId: string, src: string) => {
|
||||||
|
setActiveViewId(viewId)
|
||||||
|
setSource(src)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[activeViewId],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ActiveVideoContext.Provider value={value}>
|
||||||
|
<VideoPlayerProvider source={source ?? ''} viewId={activeViewId}>
|
||||||
|
{children}
|
||||||
|
</VideoPlayerProvider>
|
||||||
|
</ActiveVideoContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useActiveVideoView() {
|
||||||
|
const context = React.useContext(ActiveVideoContext)
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useActiveVideo must be used within a ActiveVideoProvider')
|
||||||
|
}
|
||||||
|
const id = useId()
|
||||||
|
|
||||||
|
return {
|
||||||
|
active: context.activeViewId === id,
|
||||||
|
setActive: useCallback(
|
||||||
|
(source: string) => context.setActiveView(id, source),
|
||||||
|
[context, id],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,7 +39,6 @@ import {Link} from '../Link'
|
|||||||
import {PostMeta} from '../PostMeta'
|
import {PostMeta} from '../PostMeta'
|
||||||
import {Text} from '../text/Text'
|
import {Text} from '../text/Text'
|
||||||
import {PostEmbeds} from '.'
|
import {PostEmbeds} from '.'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
|
|
||||||
export function MaybeQuoteEmbed({
|
export function MaybeQuoteEmbed({
|
||||||
embed,
|
embed,
|
||||||
@@ -262,7 +261,7 @@ const styles = StyleSheet.create({
|
|||||||
marginTop: 8,
|
marginTop: 8,
|
||||||
paddingVertical: 12,
|
paddingVertical: 12,
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
borderWidth: hairlineWidth,
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
errorContainer: {
|
errorContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@@ -272,7 +271,7 @@ const styles = StyleSheet.create({
|
|||||||
marginTop: 8,
|
marginTop: 8,
|
||||||
paddingVertical: 14,
|
paddingVertical: 14,
|
||||||
paddingHorizontal: 14,
|
paddingHorizontal: 14,
|
||||||
borderWidth: hairlineWidth,
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
alert: {
|
alert: {
|
||||||
marginBottom: 6,
|
marginBottom: 6,
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import React, {useCallback} from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {msg} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {Button, ButtonIcon} from '#/components/Button'
|
||||||
|
import {Play_Filled_Corner2_Rounded as PlayIcon} from '#/components/icons/Play'
|
||||||
|
import {useActiveVideoView} from './ActiveVideoContext'
|
||||||
|
import {VideoEmbedInner} from './VideoEmbedInner'
|
||||||
|
|
||||||
|
export function VideoEmbed({source}: {source: string}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {active, setActive} = useActiveVideoView()
|
||||||
|
const {_} = useLingui()
|
||||||
|
|
||||||
|
const onPress = useCallback(() => setActive(source), [setActive, source])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.w_full,
|
||||||
|
a.rounded_sm,
|
||||||
|
{aspectRatio: 16 / 9},
|
||||||
|
a.overflow_hidden,
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
a.my_xs,
|
||||||
|
]}>
|
||||||
|
{active ? (
|
||||||
|
<VideoEmbedInner source={source} />
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
style={[a.flex_1, t.atoms.bg_contrast_25]}
|
||||||
|
onPress={onPress}
|
||||||
|
label={_(msg`Play video`)}
|
||||||
|
variant="ghost"
|
||||||
|
color="secondary"
|
||||||
|
size="large">
|
||||||
|
<ButtonIcon icon={PlayIcon} />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
import React, {useCallback, useEffect, useRef, useState} from 'react'
|
||||||
|
import {Pressable, StyleSheet, useWindowDimensions, View} from 'react-native'
|
||||||
|
import Animated, {
|
||||||
|
measure,
|
||||||
|
runOnJS,
|
||||||
|
useAnimatedRef,
|
||||||
|
useFrameCallback,
|
||||||
|
useSharedValue,
|
||||||
|
} from 'react-native-reanimated'
|
||||||
|
import {VideoPlayer, VideoView} from 'expo-video'
|
||||||
|
|
||||||
|
import {atoms as a} from '#/alf'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
import {useVideoPlayer} from './VideoPlayerContext'
|
||||||
|
|
||||||
|
export const VideoEmbedInner = ({}: {source: string}) => {
|
||||||
|
const player = useVideoPlayer()
|
||||||
|
const aref = useAnimatedRef<Animated.View>()
|
||||||
|
const {height: windowHeight} = useWindowDimensions()
|
||||||
|
const hasLeftView = useSharedValue(false)
|
||||||
|
const ref = useRef<VideoView>(null)
|
||||||
|
|
||||||
|
const onEnterView = useCallback(() => {
|
||||||
|
if (player.status === 'readyToPlay') {
|
||||||
|
player.play()
|
||||||
|
}
|
||||||
|
}, [player])
|
||||||
|
|
||||||
|
const onLeaveView = useCallback(() => {
|
||||||
|
player.pause()
|
||||||
|
}, [player])
|
||||||
|
|
||||||
|
const enterFullscreen = useCallback(() => {
|
||||||
|
if (ref.current) {
|
||||||
|
ref.current.enterFullscreen()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useFrameCallback(() => {
|
||||||
|
const measurement = measure(aref)
|
||||||
|
|
||||||
|
if (measurement) {
|
||||||
|
if (hasLeftView.value) {
|
||||||
|
// Check if the video is in view
|
||||||
|
if (
|
||||||
|
measurement.pageY >= 0 &&
|
||||||
|
measurement.pageY + measurement.height <= windowHeight
|
||||||
|
) {
|
||||||
|
runOnJS(onEnterView)()
|
||||||
|
hasLeftView.value = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check if the video is out of view
|
||||||
|
if (
|
||||||
|
measurement.pageY + measurement.height < 0 ||
|
||||||
|
measurement.pageY > windowHeight
|
||||||
|
) {
|
||||||
|
runOnJS(onLeaveView)()
|
||||||
|
hasLeftView.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Animated.View
|
||||||
|
style={[a.flex_1, a.relative]}
|
||||||
|
ref={aref}
|
||||||
|
collapsable={false}>
|
||||||
|
<VideoView
|
||||||
|
ref={ref}
|
||||||
|
player={player}
|
||||||
|
style={a.flex_1}
|
||||||
|
nativeControls={true}
|
||||||
|
/>
|
||||||
|
<VideoControls player={player} enterFullscreen={enterFullscreen} />
|
||||||
|
</Animated.View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function VideoControls({
|
||||||
|
player,
|
||||||
|
enterFullscreen,
|
||||||
|
}: {
|
||||||
|
player: VideoPlayer
|
||||||
|
enterFullscreen: () => void
|
||||||
|
}) {
|
||||||
|
const [currentTime, setCurrentTime] = useState(Math.floor(player.currentTime))
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setCurrentTime(Math.floor(player.duration - player.currentTime))
|
||||||
|
// how often should we update the time?
|
||||||
|
// 1000 gets out of sync with the video time
|
||||||
|
}, 250)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval)
|
||||||
|
}
|
||||||
|
}, [player])
|
||||||
|
|
||||||
|
const minutes = Math.floor(currentTime / 60)
|
||||||
|
const seconds = String(currentTime % 60).padStart(2, '0')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[a.absolute, a.inset_0]}>
|
||||||
|
<View style={styles.timeContainer} pointerEvents="none">
|
||||||
|
<Text style={styles.timeElapsed}>
|
||||||
|
{minutes}:{seconds}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Pressable
|
||||||
|
onPress={enterFullscreen}
|
||||||
|
style={a.flex_1}
|
||||||
|
accessibilityLabel="Video"
|
||||||
|
accessibilityHint="Tap to enter full screen"
|
||||||
|
accessibilityRole="button"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
timeContainer: {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.75)',
|
||||||
|
borderRadius: 6,
|
||||||
|
paddingHorizontal: 6,
|
||||||
|
paddingVertical: 3,
|
||||||
|
position: 'absolute',
|
||||||
|
left: 5,
|
||||||
|
bottom: 5,
|
||||||
|
},
|
||||||
|
timeElapsed: {
|
||||||
|
color: 'white',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import React, {useEffect, useRef} from 'react'
|
||||||
|
import Hls from 'hls.js'
|
||||||
|
|
||||||
|
import {atoms as a} from '#/alf'
|
||||||
|
|
||||||
|
export const VideoEmbedInner = ({source}: {source: string}) => {
|
||||||
|
const ref = useRef<HTMLVideoElement>(null)
|
||||||
|
|
||||||
|
// Use HLS.js to play HLS video
|
||||||
|
useEffect(() => {
|
||||||
|
if (ref.current) {
|
||||||
|
if (ref.current.canPlayType('application/vnd.apple.mpegurl')) {
|
||||||
|
ref.current.src = source
|
||||||
|
} else if (Hls.isSupported()) {
|
||||||
|
var hls = new Hls()
|
||||||
|
hls.loadSource(source)
|
||||||
|
hls.attachMedia(ref.current)
|
||||||
|
} else {
|
||||||
|
// TODO: fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [source])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (ref.current) {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
if (ref.current) {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
if (ref.current.paused) {
|
||||||
|
ref.current.play()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!ref.current.paused) {
|
||||||
|
ref.current.pause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{threshold: 0},
|
||||||
|
)
|
||||||
|
|
||||||
|
observer.observe(ref.current)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <video ref={ref} style={a.flex_1} controls playsInline autoPlay loop />
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import React, {useContext, useEffect} from 'react'
|
||||||
|
import type {VideoPlayer} from 'expo-video'
|
||||||
|
import {useVideoPlayer as useExpoVideoPlayer} from 'expo-video'
|
||||||
|
|
||||||
|
const VideoPlayerContext = React.createContext<VideoPlayer | null>(null)
|
||||||
|
|
||||||
|
export function VideoPlayerProvider({
|
||||||
|
viewId,
|
||||||
|
source,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
viewId: string | null
|
||||||
|
source: string
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
const player = useExpoVideoPlayer(source, player => {
|
||||||
|
player.loop = true
|
||||||
|
player.play()
|
||||||
|
})
|
||||||
|
|
||||||
|
// make sure we're playing every time the viewId changes
|
||||||
|
// this means the video is different
|
||||||
|
useEffect(() => {
|
||||||
|
player.play()
|
||||||
|
}, [viewId, player])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VideoPlayerContext.Provider value={player}>
|
||||||
|
{children}
|
||||||
|
</VideoPlayerContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useVideoPlayer() {
|
||||||
|
const context = useContext(VideoPlayerContext)
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useVideoPlayer must be used within a VideoPlayerProvider')
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export function VideoPlayerProvider({children}: {children: React.ReactNode}) {
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useVideoPlayer() {
|
||||||
|
throw new Error('useVideoPlayer must not be used on web')
|
||||||
|
}
|
||||||
@@ -19,18 +19,17 @@ import {
|
|||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
|
||||||
import {ImagesLightbox, useLightboxControls} from '#/state/lightbox'
|
import {ImagesLightbox, useLightboxControls} from '#/state/lightbox'
|
||||||
|
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard'
|
import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard'
|
||||||
import {atoms as a} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
|
import {Embed as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
|
||||||
import {ContentHider} from '../../../../components/moderation/ContentHider'
|
import {ContentHider} from '../../../../components/moderation/ContentHider'
|
||||||
import {AutoSizedImage} from '../images/AutoSizedImage'
|
import {AutoSizedImage} from '../images/AutoSizedImage'
|
||||||
import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
|
import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
|
||||||
import {ExternalLinkEmbed} from './ExternalLinkEmbed'
|
import {ExternalLinkEmbed} from './ExternalLinkEmbed'
|
||||||
import {ListEmbed} from './ListEmbed'
|
import {ListEmbed} from './ListEmbed'
|
||||||
import {MaybeQuoteEmbed} from './QuoteEmbed'
|
import {MaybeQuoteEmbed} from './QuoteEmbed'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
|
||||||
import {Embed as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
|
|
||||||
|
|
||||||
type Embed =
|
type Embed =
|
||||||
| AppBskyEmbedRecord.View
|
| AppBskyEmbedRecord.View
|
||||||
@@ -205,7 +204,7 @@ const styles = StyleSheet.create({
|
|||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
},
|
},
|
||||||
customFeedOuter: {
|
customFeedOuter: {
|
||||||
borderWidth: hairlineWidth,
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
marginTop: 4,
|
marginTop: 4,
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
|
|||||||
@@ -33,14 +33,13 @@ import {ViewHeader} from 'view/com/util/ViewHeader'
|
|||||||
import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
|
import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
|
||||||
import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
|
import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {Divider} from '#/components/Divider'
|
||||||
|
import * as FeedCard from '#/components/FeedCard'
|
||||||
import {IconCircle} from '#/components/IconCircle'
|
import {IconCircle} from '#/components/IconCircle'
|
||||||
|
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
|
||||||
import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline'
|
import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline'
|
||||||
import {ListMagnifyingGlass_Stroke2_Corner0_Rounded} from '#/components/icons/ListMagnifyingGlass'
|
import {ListMagnifyingGlass_Stroke2_Corner0_Rounded} from '#/components/icons/ListMagnifyingGlass'
|
||||||
import {ListSparkle_Stroke2_Corner0_Rounded} from '#/components/icons/ListSparkle'
|
import {ListSparkle_Stroke2_Corner0_Rounded} from '#/components/icons/ListSparkle'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
import {Divider} from '#/components/Divider'
|
|
||||||
import * as FeedCard from '#/components/FeedCard'
|
|
||||||
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
|
|
||||||
import * as ListCard from '#/components/ListCard'
|
import * as ListCard from '#/components/ListCard'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Feeds'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Feeds'>
|
||||||
@@ -793,13 +792,13 @@ const styles = StyleSheet.create({
|
|||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
paddingVertical: 14,
|
paddingVertical: 14,
|
||||||
gap: 12,
|
gap: 12,
|
||||||
borderBottomWidth: hairlineWidth,
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
savedFeedMobile: {
|
savedFeedMobile: {
|
||||||
paddingVertical: 10,
|
paddingVertical: 10,
|
||||||
},
|
},
|
||||||
offlineSlug: {
|
offlineSlug: {
|
||||||
borderWidth: hairlineWidth,
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
paddingHorizontal: 4,
|
paddingHorizontal: 4,
|
||||||
paddingVertical: 2,
|
paddingVertical: 2,
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import {MyLists} from '#/view/com/lists/MyLists'
|
|||||||
import {Button} from 'view/com/util/forms/Button'
|
import {Button} from 'view/com/util/forms/Button'
|
||||||
import {SimpleViewHeader} from 'view/com/util/SimpleViewHeader'
|
import {SimpleViewHeader} from 'view/com/util/SimpleViewHeader'
|
||||||
import {Text} from 'view/com/util/text/Text'
|
import {Text} from 'view/com/util/text/Text'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Lists'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Lists'>
|
||||||
export function ListsScreen({}: Props) {
|
export function ListsScreen({}: Props) {
|
||||||
@@ -55,8 +54,11 @@ export function ListsScreen({}: Props) {
|
|||||||
style={[
|
style={[
|
||||||
pal.border,
|
pal.border,
|
||||||
isMobile
|
isMobile
|
||||||
? {borderBottomWidth: hairlineWidth}
|
? {borderBottomWidth: StyleSheet.hairlineWidth}
|
||||||
: {borderLeftWidth: hairlineWidth, borderRightWidth: hairlineWidth},
|
: {
|
||||||
|
borderLeftWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderRightWidth: StyleSheet.hairlineWidth,
|
||||||
|
},
|
||||||
]}>
|
]}>
|
||||||
<View style={{flex: 1}}>
|
<View style={{flex: 1}}>
|
||||||
<Text type="title-lg" style={[pal.text, {fontWeight: 'bold'}]}>
|
<Text type="title-lg" style={[pal.text, {fontWeight: 'bold'}]}>
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ import {useDialogControl} from '#/components/Dialog'
|
|||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
|
import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
|
||||||
import {RichText} from '#/components/RichText'
|
import {RichText} from '#/components/RichText'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
|
|
||||||
const SECTION_TITLES_CURATE = ['Posts', 'About']
|
const SECTION_TITLES_CURATE = ['Posts', 'About']
|
||||||
const SECTION_TITLES_MOD = ['About']
|
const SECTION_TITLES_MOD = ['About']
|
||||||
@@ -803,7 +802,7 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
|
|||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
{
|
{
|
||||||
borderTopWidth: hairlineWidth,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
padding: isMobile ? 14 : 20,
|
padding: isMobile ? 14 : 20,
|
||||||
gap: 12,
|
gap: 12,
|
||||||
},
|
},
|
||||||
@@ -954,7 +953,7 @@ function ErrorScreen({error}: {error: string}) {
|
|||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
paddingHorizontal: 18,
|
paddingHorizontal: 18,
|
||||||
paddingVertical: 14,
|
paddingVertical: 14,
|
||||||
borderTopWidth: hairlineWidth,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
]}>
|
]}>
|
||||||
<Text type="title-lg" style={[pal.text, s.mb10]}>
|
<Text type="title-lg" style={[pal.text, s.mb10]}>
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
|
|||||||
import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
|
import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline'
|
import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
|
|
||||||
const HITSLOP_TOP = {
|
const HITSLOP_TOP = {
|
||||||
top: 20,
|
top: 20,
|
||||||
@@ -93,7 +92,8 @@ export function SavedFeeds({}: Props) {
|
|||||||
<ViewHeader title={_(msg`Edit My Feeds`)} showOnDesktop showBorder />
|
<ViewHeader title={_(msg`Edit My Feeds`)} showOnDesktop showBorder />
|
||||||
<ScrollView style={s.flex1} contentContainerStyle={[styles.noBorder]}>
|
<ScrollView style={s.flex1} contentContainerStyle={[styles.noBorder]}>
|
||||||
{noSavedFeedsOfAnyType && (
|
{noSavedFeedsOfAnyType && (
|
||||||
<View style={[pal.border, {borderBottomWidth: hairlineWidth}]}>
|
<View
|
||||||
|
style={[pal.border, {borderBottomWidth: StyleSheet.hairlineWidth}]}>
|
||||||
<NoSavedFeedsOfAnyType />
|
<NoSavedFeedsOfAnyType />
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
@@ -135,7 +135,8 @@ export function SavedFeeds({}: Props) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{noFollowingFeed && (
|
{noFollowingFeed && (
|
||||||
<View style={[pal.border, {borderBottomWidth: hairlineWidth}]}>
|
<View
|
||||||
|
style={[pal.border, {borderBottomWidth: StyleSheet.hairlineWidth}]}>
|
||||||
<NoFollowingFeed />
|
<NoFollowingFeed />
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
@@ -437,12 +438,12 @@ const styles = StyleSheet.create({
|
|||||||
paddingHorizontal: 14,
|
paddingHorizontal: 14,
|
||||||
paddingTop: 20,
|
paddingTop: 20,
|
||||||
paddingBottom: 10,
|
paddingBottom: 10,
|
||||||
borderBottomWidth: hairlineWidth,
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
itemContainer: {
|
itemContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderBottomWidth: hairlineWidth,
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
},
|
},
|
||||||
footerText: {
|
footerText: {
|
||||||
paddingHorizontal: 26,
|
paddingHorizontal: 26,
|
||||||
|
|||||||
@@ -75,17 +75,17 @@ function SuggestedItemsHeader({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoadMoreItems =
|
type LoadMoreItem =
|
||||||
| {
|
| {
|
||||||
type: 'profile'
|
type: 'profile'
|
||||||
key: string
|
key: string
|
||||||
avatar: string
|
avatar: string | undefined
|
||||||
moderation: ModerationDecision
|
moderation: ModerationDecision
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'feed'
|
type: 'feed'
|
||||||
key: string
|
key: string
|
||||||
avatar: string
|
avatar: string | undefined
|
||||||
moderation: undefined
|
moderation: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,27 +98,28 @@ function LoadMore({
|
|||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const items = React.useMemo(() => {
|
const items: LoadMoreItem[] = React.useMemo(() => {
|
||||||
return item.items
|
return item.items
|
||||||
.map(_item => {
|
.map(_item => {
|
||||||
|
let loadMoreItem: LoadMoreItem | undefined
|
||||||
if (_item.type === 'profile') {
|
if (_item.type === 'profile') {
|
||||||
return {
|
loadMoreItem = {
|
||||||
type: 'profile',
|
type: 'profile',
|
||||||
key: _item.profile.did,
|
key: _item.profile.did,
|
||||||
avatar: _item.profile.avatar,
|
avatar: _item.profile.avatar,
|
||||||
moderation: moderateProfile(_item.profile, moderationOpts!),
|
moderation: moderateProfile(_item.profile, moderationOpts!),
|
||||||
}
|
}
|
||||||
} else if (_item.type === 'feed') {
|
} else if (_item.type === 'feed') {
|
||||||
return {
|
loadMoreItem = {
|
||||||
type: 'feed',
|
type: 'feed',
|
||||||
key: _item.feed.uri,
|
key: _item.feed.uri,
|
||||||
avatar: _item.feed.avatar,
|
avatar: _item.feed.avatar,
|
||||||
moderation: undefined,
|
moderation: undefined,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return undefined
|
return loadMoreItem
|
||||||
})
|
})
|
||||||
.filter(Boolean) as LoadMoreItems[]
|
.filter(n => !!n)
|
||||||
}, [item.items, moderationOpts])
|
}, [item.items, moderationOpts])
|
||||||
|
|
||||||
if (items.length === 0) return null
|
if (items.length === 0) return null
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ export function ExportCarDialog({
|
|||||||
This feature is in beta. You can read more about repository
|
This feature is in beta. You can read more about repository
|
||||||
exports in{' '}
|
exports in{' '}
|
||||||
<InlineLinkText
|
<InlineLinkText
|
||||||
|
label={_(msg`View blogpost for more details`)}
|
||||||
to="https://docs.bsky.app/blog/repo-export"
|
to="https://docs.bsky.app/blog/repo-export"
|
||||||
style={[a.text_sm]}>
|
style={[a.text_sm]}>
|
||||||
this blogpost
|
this blogpost
|
||||||
|
|||||||
@@ -62,13 +62,12 @@ import {UserAvatar} from 'view/com/util/UserAvatar'
|
|||||||
import {ScrollView} from 'view/com/util/Views'
|
import {ScrollView} from 'view/com/util/Views'
|
||||||
import {DeactivateAccountDialog} from '#/screens/Settings/components/DeactivateAccountDialog'
|
import {DeactivateAccountDialog} from '#/screens/Settings/components/DeactivateAccountDialog'
|
||||||
import {useTheme} from '#/alf'
|
import {useTheme} from '#/alf'
|
||||||
|
import {atoms as a} from '#/alf'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings'
|
import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings'
|
||||||
import {navigate, resetToTab} from '#/Navigation'
|
import {navigate, resetToTab} from '#/Navigation'
|
||||||
import {Email2FAToggle} from './Email2FAToggle'
|
import {Email2FAToggle} from './Email2FAToggle'
|
||||||
import {ExportCarDialog} from './ExportCarDialog'
|
import {ExportCarDialog} from './ExportCarDialog'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
import {atoms as a} from '#/alf'
|
|
||||||
|
|
||||||
function SettingsAccountCard({
|
function SettingsAccountCard({
|
||||||
account,
|
account,
|
||||||
@@ -326,7 +325,7 @@ export function SettingsScreen({}: Props) {
|
|||||||
showBackButton={isMobile}
|
showBackButton={isMobile}
|
||||||
style={[
|
style={[
|
||||||
pal.border,
|
pal.border,
|
||||||
{borderBottomWidth: hairlineWidth},
|
{borderBottomWidth: StyleSheet.hairlineWidth},
|
||||||
!isMobile && {borderLeftWidth: 1, borderRightWidth: 1},
|
!isMobile && {borderLeftWidth: 1, borderRightWidth: 1},
|
||||||
]}>
|
]}>
|
||||||
<View style={{flex: 1}}>
|
<View style={{flex: 1}}>
|
||||||
|
|||||||
@@ -13,18 +13,20 @@ export function Links() {
|
|||||||
<H1>Links</H1>
|
<H1>Links</H1>
|
||||||
|
|
||||||
<View style={[a.gap_md, a.align_start]}>
|
<View style={[a.gap_md, a.align_start]}>
|
||||||
<InlineLinkText to="https://google.com" style={[a.text_lg]}>
|
<InlineLinkText label="foo" to="https://google.com" style={[a.text_lg]}>
|
||||||
https://google.com
|
https://google.com
|
||||||
</InlineLinkText>
|
</InlineLinkText>
|
||||||
<InlineLinkText to="https://google.com" style={[a.text_lg]}>
|
<InlineLinkText label="foo" to="https://google.com" style={[a.text_lg]}>
|
||||||
External with custom children (google.com)
|
External with custom children (google.com)
|
||||||
</InlineLinkText>
|
</InlineLinkText>
|
||||||
<InlineLinkText
|
<InlineLinkText
|
||||||
|
label="foo"
|
||||||
to="https://bsky.social"
|
to="https://bsky.social"
|
||||||
style={[a.text_md, t.atoms.text_contrast_low]}>
|
style={[a.text_md, t.atoms.text_contrast_low]}>
|
||||||
Internal (bsky.social)
|
Internal (bsky.social)
|
||||||
</InlineLinkText>
|
</InlineLinkText>
|
||||||
<InlineLinkText
|
<InlineLinkText
|
||||||
|
label="foo"
|
||||||
to="https://bsky.app/profile/bsky.app"
|
to="https://bsky.app/profile/bsky.app"
|
||||||
style={[a.text_md]}>
|
style={[a.text_md]}>
|
||||||
Internal (bsky.app)
|
Internal (bsky.app)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {StyleSheet} from 'react-native'
|
import {StyleSheet} from 'react-native'
|
||||||
|
|
||||||
import {colors} from 'lib/styles'
|
import {colors} from 'lib/styles'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
|
|
||||||
export const styles = StyleSheet.create({
|
export const styles = StyleSheet.create({
|
||||||
bottomBar: {
|
bottomBar: {
|
||||||
@@ -10,7 +9,7 @@ export const styles = StyleSheet.create({
|
|||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
borderTopWidth: hairlineWidth,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
paddingLeft: 5,
|
paddingLeft: 5,
|
||||||
paddingRight: 10,
|
paddingRight: 10,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|||||||
import {s} from 'lib/styles'
|
import {s} from 'lib/styles'
|
||||||
import {TextLink} from 'view/com/util/Link'
|
import {TextLink} from 'view/com/util/Link'
|
||||||
import {Text} from 'view/com/util/text/Text'
|
import {Text} from 'view/com/util/text/Text'
|
||||||
|
import {ProgressGuideList} from '#/components/ProgressGuide/List'
|
||||||
import {DesktopFeeds} from './Feeds'
|
import {DesktopFeeds} from './Feeds'
|
||||||
import {DesktopSearch} from './Search'
|
import {DesktopSearch} from './Search'
|
||||||
import hairlineWidth = StyleSheet.hairlineWidth
|
|
||||||
import {ProgressGuideList} from '#/components/ProgressGuide/List'
|
|
||||||
|
|
||||||
export function DesktopRightNav({routeName}: {routeName: string}) {
|
export function DesktopRightNav({routeName}: {routeName: string}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
@@ -135,8 +134,8 @@ const styles = StyleSheet.create({
|
|||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
},
|
},
|
||||||
desktopFeedsContainer: {
|
desktopFeedsContainer: {
|
||||||
borderTopWidth: hairlineWidth,
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
borderBottomWidth: hairlineWidth,
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
marginTop: 18,
|
marginTop: 18,
|
||||||
marginBottom: 18,
|
marginBottom: 18,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13345,6 +13345,11 @@ history@^5.3.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.7.6"
|
"@babel/runtime" "^7.7.6"
|
||||||
|
|
||||||
|
hls.js@^1.5.11:
|
||||||
|
version "1.5.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-1.5.11.tgz#3941347df454983859ae8c75fe19e8818719a826"
|
||||||
|
integrity sha512-q3We1izi2+qkOO+TvZdHv+dx6aFzdtk3xc1/Qesrvto4thLTT/x/1FK85c5h1qZE4MmMBNgKg+MIW8nxQfxwBw==
|
||||||
|
|
||||||
hmac-drbg@^1.0.1:
|
hmac-drbg@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
||||||
@@ -21373,10 +21378,10 @@ typedarray-to-buffer@^3.1.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-typedarray "^1.0.0"
|
is-typedarray "^1.0.0"
|
||||||
|
|
||||||
typescript@^5.3.3:
|
typescript@^5.5.4:
|
||||||
version "5.3.3"
|
version "5.5.4"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba"
|
||||||
integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
|
integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==
|
||||||
|
|
||||||
ua-parser-js@^0.7.33:
|
ua-parser-js@^0.7.33:
|
||||||
version "0.7.35"
|
version "0.7.35"
|
||||||
|
|||||||
Reference in New Issue
Block a user