Merge branch 'main' into hailey/native-autoplay

This commit is contained in:
Hailey
2024-07-25 12:55:01 -07:00
65 changed files with 11813 additions and 10358 deletions
+3 -2
View File
@@ -132,7 +132,8 @@ appId: xyz.blueskyweb.app
id: "feedItem-by-bob.test"
- tapOn:
id: "e2eGotoFeeds"
- tapOn: "Good Ppl"
- tapOn:
id: "saved-feed-Good Ppl"
- assertVisible:
id: "feedItem-by-bob.test"
- tapOn:
@@ -168,7 +169,7 @@ appId: xyz.blueskyweb.app
id: "profilePager-selector"
direction: LEFT
- tapOn:
id: "profilePager-selector-5"
id: "profilePager-selector-6"
- tapOn: "Good Ppl"
- tapOn:
@@ -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

+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "bsky.app",
"version": "1.89.0",
"version": "1.90.0",
"private": true,
"engines": {
"node": ">=18"
@@ -143,6 +143,7 @@
"expo-web-browser": "~13.0.3",
"fast-text-encoding": "^1.0.6",
"history": "^5.3.0",
"hls.js": "^1.5.11",
"js-sha256": "^0.9.0",
"jwt-decode": "^4.0.0",
"lande": "^1.0.10",
+20
View File
@@ -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
View File
@@ -23,10 +23,12 @@ import {
} from '#/lib/statsig/statsig'
import {s} from '#/lib/styles'
import {ThemeProvider} from '#/lib/ThemeContext'
import I18nProvider from '#/locale/i18nProvider'
import {logger} from '#/logger'
import {Provider as A11yProvider} from '#/state/a11y'
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
import {Provider as DialogStateProvider} from '#/state/dialogs'
import {listenSessionDropped} from '#/state/events'
import {Provider as InvitesStateProvider} from '#/state/invites'
import {Provider as LightboxStateProvider} from '#/state/lightbox'
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 StarterPackProvider} from '#/state/shell/starter-pack'
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 {Shell} from '#/view/shell'
import {ThemeProvider as Alf} from '#/alf'
@@ -58,8 +61,6 @@ import {Provider as PortalProvider} from '#/components/Portal'
import {Splash} from '#/Splash'
import {Provider as TourProvider} from '#/tours'
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
import I18nProvider from './locale/i18nProvider'
import {listenSessionDropped} from './state/events'
SplashScreen.preventAutoHideAsync()
@@ -107,42 +108,44 @@ function InnerApp() {
<Alf theme={theme}>
<ThemeProvider theme={theme}>
<Splash isReady={isReady && hasCheckedReferrer}>
<RootSiblingParent>
<React.Fragment
// Resets the entire tree below when it changes:
key={currentAccount?.did}>
<QueryProvider currentDid={currentAccount?.did}>
<StatsigProvider>
<MessagesProvider>
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
<LabelDefsProvider>
<ModerationOptsProvider>
<LoggedOutViewProvider>
<SelectedFeedProvider>
<UnreadNotifsProvider>
<BackgroundNotificationPreferencesProvider>
<MutedThreadsProvider>
<TourProvider>
<ProgressGuideProvider>
<GestureHandlerRootView
style={s.h100pct}>
<TestCtrls />
<Shell />
</GestureHandlerRootView>
</ProgressGuideProvider>
</TourProvider>
</MutedThreadsProvider>
</BackgroundNotificationPreferencesProvider>
</UnreadNotifsProvider>
</SelectedFeedProvider>
</LoggedOutViewProvider>
</ModerationOptsProvider>
</LabelDefsProvider>
</MessagesProvider>
</StatsigProvider>
</QueryProvider>
</React.Fragment>
</RootSiblingParent>
<ActiveVideoProvider>
<RootSiblingParent>
<React.Fragment
// Resets the entire tree below when it changes:
key={currentAccount?.did}>
<QueryProvider currentDid={currentAccount?.did}>
<StatsigProvider>
<MessagesProvider>
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
<LabelDefsProvider>
<ModerationOptsProvider>
<LoggedOutViewProvider>
<SelectedFeedProvider>
<UnreadNotifsProvider>
<BackgroundNotificationPreferencesProvider>
<MutedThreadsProvider>
<TourProvider>
<ProgressGuideProvider>
<GestureHandlerRootView
style={s.h100pct}>
<TestCtrls />
<Shell />
</GestureHandlerRootView>
</ProgressGuideProvider>
</TourProvider>
</MutedThreadsProvider>
</BackgroundNotificationPreferencesProvider>
</UnreadNotifsProvider>
</SelectedFeedProvider>
</LoggedOutViewProvider>
</ModerationOptsProvider>
</LabelDefsProvider>
</MessagesProvider>
</StatsigProvider>
</QueryProvider>
</React.Fragment>
</RootSiblingParent>
</ActiveVideoProvider>
</Splash>
</ThemeProvider>
</Alf>
+38 -35
View File
@@ -12,10 +12,12 @@ import {useIntentHandler} from '#/lib/hooks/useIntentHandler'
import {QueryProvider} from '#/lib/react-query'
import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
import {ThemeProvider} from '#/lib/ThemeContext'
import I18nProvider from '#/locale/i18nProvider'
import {logger} from '#/logger'
import {Provider as A11yProvider} from '#/state/a11y'
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
import {Provider as DialogStateProvider} from '#/state/dialogs'
import {listenSessionDropped} from '#/state/events'
import {Provider as InvitesStateProvider} from '#/state/invites'
import {Provider as LightboxStateProvider} from '#/state/lightbox'
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 SelectedFeedProvider} from '#/state/shell/selected-feed'
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 {ToastContainer} from '#/view/com/util/Toast.web'
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 TourProvider} from '#/tours'
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
import I18nProvider from './locale/i18nProvider'
import {listenSessionDropped} from './state/events'
function InnerApp() {
const [isReady, setIsReady] = React.useState(false)
@@ -92,39 +93,41 @@ function InnerApp() {
<Alf theme={theme}>
<ThemeProvider theme={theme}>
<RootSiblingParent>
<React.Fragment
// Resets the entire tree below when it changes:
key={currentAccount?.did}>
<QueryProvider currentDid={currentAccount?.did}>
<StatsigProvider>
<MessagesProvider>
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
<LabelDefsProvider>
<ModerationOptsProvider>
<LoggedOutViewProvider>
<SelectedFeedProvider>
<UnreadNotifsProvider>
<BackgroundNotificationPreferencesProvider>
<MutedThreadsProvider>
<SafeAreaProvider>
<TourProvider>
<ProgressGuideProvider>
<Shell />
</ProgressGuideProvider>
</TourProvider>
</SafeAreaProvider>
</MutedThreadsProvider>
</BackgroundNotificationPreferencesProvider>
</UnreadNotifsProvider>
</SelectedFeedProvider>
</LoggedOutViewProvider>
</ModerationOptsProvider>
</LabelDefsProvider>
</MessagesProvider>
</StatsigProvider>
</QueryProvider>
</React.Fragment>
<ToastContainer />
<ActiveVideoProvider>
<React.Fragment
// Resets the entire tree below when it changes:
key={currentAccount?.did}>
<QueryProvider currentDid={currentAccount?.did}>
<StatsigProvider>
<MessagesProvider>
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
<LabelDefsProvider>
<ModerationOptsProvider>
<LoggedOutViewProvider>
<SelectedFeedProvider>
<UnreadNotifsProvider>
<BackgroundNotificationPreferencesProvider>
<MutedThreadsProvider>
<SafeAreaProvider>
<TourProvider>
<ProgressGuideProvider>
<Shell />
</ProgressGuideProvider>
</TourProvider>
</SafeAreaProvider>
</MutedThreadsProvider>
</BackgroundNotificationPreferencesProvider>
</UnreadNotifsProvider>
</SelectedFeedProvider>
</LoggedOutViewProvider>
</ModerationOptsProvider>
</LabelDefsProvider>
</MessagesProvider>
</StatsigProvider>
</QueryProvider>
</React.Fragment>
<ToastContainer />
</ActiveVideoProvider>
</RootSiblingParent>
</ThemeProvider>
</Alf>
+5 -6
View File
@@ -2,7 +2,6 @@ import {Platform, StyleSheet} from 'react-native'
import * as tokens from '#/alf/tokens'
import {native, web} from '#/alf/util/platform'
import hairlineWidth = StyleSheet.hairlineWidth
export const atoms = {
/*
@@ -284,19 +283,19 @@ export const atoms = {
borderWidth: 0,
},
border: {
borderWidth: hairlineWidth,
borderWidth: StyleSheet.hairlineWidth,
},
border_t: {
borderTopWidth: hairlineWidth,
borderTopWidth: StyleSheet.hairlineWidth,
},
border_b: {
borderBottomWidth: hairlineWidth,
borderBottomWidth: StyleSheet.hairlineWidth,
},
border_l: {
borderLeftWidth: hairlineWidth,
borderLeftWidth: StyleSheet.hairlineWidth,
},
border_r: {
borderRightWidth: hairlineWidth,
borderRightWidth: StyleSheet.hairlineWidth,
},
/*
+9
View File
@@ -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',
})
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -11,7 +11,7 @@ import {useProfileQuery} from '#/state/queries/profile'
import {useSession} from '#/state/session'
import * as Toast from '#/view/com/util/Toast'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {CenteredView} from '#/view/com/util/Views'
import {ScrollView} from '#/view/com/util/Views'
import {atoms as a, useTheme} from '#/alf'
import {Divider} from '#/components/Divider'
import * as Toggle from '#/components/forms/Toggle'
@@ -55,7 +55,7 @@ export function MessagesSettingsScreen({}: Props) {
)
return (
<CenteredView sideBorders style={a.h_full_vh}>
<ScrollView stickyHeaderIndices={[0]}>
<ViewHeader title={_(msg`Chat Settings`)} showOnDesktop showBorder />
<View style={[a.p_lg, a.gap_md]}>
<Text style={[a.text_lg, a.font_bold]}>
@@ -149,6 +149,6 @@ export function MessagesSettingsScreen({}: Props) {
</>
)}
</View>
</CenteredView>
</ScrollView>
)
}
+21 -8
View File
@@ -78,7 +78,10 @@ export interface FeedPostSliceItem {
uri: string
post: AppBskyFeedDefs.PostView
record: AppBskyFeedPost.Record
reason?: AppBskyFeedDefs.ReasonRepost | ReasonFeedSource
reason?:
| AppBskyFeedDefs.ReasonRepost
| ReasonFeedSource
| {[k: string]: unknown; $type: string}
feedContext: string | undefined
moderation: ModerationDecision
parentAuthor?: AppBskyActorDefs.ProfileViewBasic
@@ -323,7 +326,7 @@ export function usePostFeedQuery(
)
}
return {
const feedPostSlice: FeedPostSlice = {
_reactKey: slice._reactKey,
_isFeedPostSlice: true,
rootUri: slice.rootItem.post.uri,
@@ -341,15 +344,23 @@ export function usePostFeedQuery(
AppBskyFeedPost.validateRecord(item.post.record)
.success
) {
const parentAuthor =
item.reply?.parent?.author ??
slice.items[i + 1]?.reply?.grandparentAuthor
const parent = item.reply?.parent
let parentAuthor:
| AppBskyActorDefs.ProfileViewBasic
| undefined
if (AppBskyFeedDefs.isPostView(parent)) {
parentAuthor = parent.author
}
if (!parentAuthor) {
parentAuthor =
slice.items[i + 1]?.reply?.grandparentAuthor
}
const replyRef = item.reply
const isParentBlocked = AppBskyFeedDefs.isBlockedPost(
replyRef?.parent,
)
return {
const feedPostSliceItem: FeedPostSliceItem = {
_reactKey: `${slice._reactKey}-${i}-${item.post.uri}`,
uri: item.post.uri,
post: item.post,
@@ -363,13 +374,15 @@ export function usePostFeedQuery(
parentAuthor,
isParentBlocked,
}
return feedPostSliceItem
}
return undefined
})
.filter(Boolean) as FeedPostSliceItem[],
.filter(<T>(n?: T): n is T => Boolean(n)),
}
return feedPostSlice
})
.filter(Boolean) as FeedPostSlice[],
.filter(<T>(n?: T): n is T => Boolean(n)),
})),
],
}
+11 -11
View File
@@ -4,7 +4,7 @@ export type ThreadgateSetting =
| {type: 'nobody'}
| {type: 'mention'}
| {type: 'following'}
| {type: 'list'; list: string}
| {type: 'list'; list: unknown}
export function threadgateViewToSettings(
threadgate: AppBskyFeedDefs.ThreadgateView | undefined,
@@ -21,18 +21,18 @@ export function threadgateViewToSettings(
if (!record.allow?.length) {
return [{type: 'nobody'}]
}
return record.allow
const settings: ThreadgateSetting[] = record.allow
.map(allow => {
let setting: ThreadgateSetting | undefined
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 {type: 'following'}
}
if (allow.$type === 'app.bsky.feed.threadgate#listRule') {
return {type: 'list', list: allow.list}
}
return undefined
return setting
})
.filter(Boolean) as ThreadgateSetting[]
.filter(<T>(n?: T): n is T => Boolean(n))
return settings
}
+4 -5
View File
@@ -99,7 +99,6 @@ import {SelectVideoBtn} from './videos/SelectVideoBtn'
import {useVideoState} from './videos/state'
import {VideoPreview} from './videos/VideoPreview'
import {VideoTranscodeProgress} from './videos/VideoTranscodeProgress'
import hairlineWidth = StyleSheet.hairlineWidth
type CancelRef = {
onPressCancel: () => void
@@ -763,7 +762,7 @@ function useAnimatedBorders() {
const topBarAnimatedStyle = useAnimatedStyle(() => {
return {
borderBottomWidth: hairlineWidth,
borderBottomWidth: StyleSheet.hairlineWidth,
borderColor: interpolateColor(
hasScrolledTop.value,
[0, 1],
@@ -773,7 +772,7 @@ function useAnimatedBorders() {
})
const bottomBarAnimatedStyle = useAnimatedStyle(() => {
return {
borderTopWidth: hairlineWidth,
borderTopWidth: StyleSheet.hairlineWidth,
borderColor: interpolateColor(
hasScrolledBottom.value,
[0, 1],
@@ -855,7 +854,7 @@ const styles = StyleSheet.create({
marginBottom: 8,
},
errorIcon: {
borderWidth: hairlineWidth,
borderWidth: StyleSheet.hairlineWidth,
borderColor: colors.red4,
color: colors.red4,
borderRadius: 30,
@@ -891,6 +890,6 @@ const styles = StyleSheet.create({
paddingLeft: 7,
paddingRight: 16,
alignItems: 'center',
borderTopWidth: hairlineWidth,
borderTopWidth: StyleSheet.hairlineWidth,
},
})
+1 -2
View File
@@ -9,7 +9,6 @@ import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {Text} from '../util/text/Text'
import {UserAvatar} from '../util/UserAvatar'
import hairlineWidth = StyleSheet.hairlineWidth
export function ComposePrompt({onPressCompose}: {onPressCompose: () => void}) {
const {currentAccount} = useSession()
@@ -49,7 +48,7 @@ const styles = StyleSheet.create({
paddingBottom: 10,
flexDirection: 'row',
alignItems: 'center',
borderTopWidth: hairlineWidth,
borderTopWidth: StyleSheet.hairlineWidth,
},
labelMobile: {
paddingLeft: 12,
+3 -4
View File
@@ -13,6 +13,7 @@ import {msg, Plural, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {logger} from '#/logger'
import {shouldClickOpenNewTab} from '#/platform/urls'
import {FeedSourceInfo, useFeedSourceInfoQuery} from '#/state/queries/feed'
import {
useAddSavedFeedsMutation,
@@ -32,8 +33,6 @@ import * as Prompt from '#/components/Prompt'
import {RichText} from '#/components/RichText'
import {Text} from '../util/text/Text'
import {UserAvatar} from '../util/UserAvatar'
import hairlineWidth = StyleSheet.hairlineWidth
import {shouldClickOpenNewTab} from '#/platform/urls'
export function FeedSourceCard({
feedUri,
@@ -209,7 +208,7 @@ export function FeedSourceCardLoaded({
styles.container,
pal.border,
style,
{borderTopWidth: hideTopBorder ? 0 : hairlineWidth},
{borderTopWidth: hideTopBorder ? 0 : StyleSheet.hairlineWidth},
]}
onPress={e => {
const shouldOpenInNewTab = shouldClickOpenNewTab(e)
@@ -330,7 +329,7 @@ const styles = StyleSheet.create({
gap: 14,
},
border: {
borderTopWidth: hairlineWidth,
borderTopWidth: StyleSheet.hairlineWidth,
},
headerContainer: {
flexDirection: 'row',
+1 -2
View File
@@ -14,7 +14,6 @@ import {RichText as RichTextCom} from '#/components/RichText'
import {Link} from '../util/Link'
import {Text} from '../util/text/Text'
import {UserAvatar} from '../util/UserAvatar'
import hairlineWidth = StyleSheet.hairlineWidth
export const ListCard = ({
testID,
@@ -134,7 +133,7 @@ export const ListCard = ({
const styles = StyleSheet.create({
outer: {
borderTopWidth: hairlineWidth,
borderTopWidth: StyleSheet.hairlineWidth,
paddingHorizontal: 6,
},
outerNoBorder: {
+3 -4
View File
@@ -29,7 +29,6 @@ import {Button} from '../util/forms/Button'
import {Text} from '../util/text/Text'
import * as Toast from '../util/Toast'
import {UserAvatar} from '../util/UserAvatar'
import hairlineWidth = StyleSheet.hairlineWidth
export const snapPoints = ['fullscreen']
@@ -63,7 +62,7 @@ export function Component({
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])
return (
@@ -188,7 +187,7 @@ function ListItem({
style={[
styles.listItem,
pal.border,
index !== 0 && {borderTopWidth: hairlineWidth},
index !== 0 && {borderTopWidth: StyleSheet.hairlineWidth},
]}>
<View style={styles.listItemAvi}>
<UserAvatar size={40} avatar={list.avatar} type="list" />
@@ -248,7 +247,7 @@ const styles = StyleSheet.create({
gap: 10,
paddingTop: 10,
paddingBottom: isAndroid ? 10 : 0,
borderTopWidth: hairlineWidth,
borderTopWidth: StyleSheet.hairlineWidth,
},
footerBtn: {
paddingHorizontal: 24,
+1 -2
View File
@@ -24,7 +24,6 @@ import {NotificationFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceho
import {LoadMoreRetryBtn} from '#/view/com/util/LoadMoreRetryBtn'
import {CenteredView} from '#/view/com/util/Views'
import {FeedItem} from './FeedItem'
import hairlineWidth = StyleSheet.hairlineWidth
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
@@ -137,7 +136,7 @@ export function Feed({
<View
style={[
pal.border,
!isTabletOrMobile && {borderTopWidth: hairlineWidth},
!isTabletOrMobile && {borderTopWidth: StyleSheet.hairlineWidth},
]}>
<NotificationFeedLoadingPlaceholder />
</View>
+12 -17
View File
@@ -20,20 +20,28 @@ import {
import {AtUri} from '@atproto/api'
import {msg, plural, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
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 {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
import {usePalette} from 'lib/hooks/usePalette'
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 {sanitizeHandle} from 'lib/strings/handles'
import {niceDate} from 'lib/strings/time'
import {colors, s} from 'lib/styles'
import {isWeb} from 'platform/detection'
import {DM_SERVICE_HEADERS} from 'state/queries/messages/const'
import {precacheProfile} from 'state/queries/profile'
import {useAgent} from 'state/session'
import {atoms as a, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import {
ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon,
ChevronTop_Stroke2_Corner0_Rounded as ChevronUpIcon,
@@ -41,8 +49,10 @@ import {
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 {Repost_Stroke2_Corner2_Rounded as RepostIcon} from '#/components/icons/Repost'
import {StarterPack} from '#/components/icons/StarterPack'
import {Link as NewLink} from '#/components/Link'
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
import {Notification as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
import {FeedSourceCard} from '../feeds/FeedSourceCard'
import {Post} from '../post/Post'
import {ImageHorzList} from '../util/images/ImageHorzList'
@@ -52,19 +62,6 @@ import {Text} from '../util/text/Text'
import {TimeElapsed} from '../util/TimeElapsed'
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 EXPANDED_AUTHOR_EL_HEIGHT = 35
@@ -171,7 +168,6 @@ let FeedItem = ({
)
}
let isFollowBack = false
let action = ''
let icon = (
<HeartIconFilled
@@ -192,7 +188,6 @@ let FeedItem = ({
item.notification.author.viewer?.following &&
gate('ungroup_follow_backs')
) {
isFollowBack = true
action = _(msg`followed you back`)
} else {
action = _(msg`followed you`)
@@ -224,7 +219,7 @@ let FeedItem = ({
backgroundColor: pal.colors.unreadNotifBg,
borderColor: pal.colors.unreadNotifBorder,
},
{borderTopWidth: hideTopBorder ? 0 : hairlineWidth},
{borderTopWidth: hideTopBorder ? 0 : StyleSheet.hairlineWidth},
]}
href={itemHref}
noFeedback
@@ -272,7 +267,7 @@ let FeedItem = ({
visible={!isAuthorsExpanded}
authors={authors}
onToggleAuthorsExpanded={onToggleAuthorsExpanded}
showDmButton={item.type === 'starterpack-joined' || isFollowBack}
showDmButton={item.type === 'starterpack-joined'}
/>
<ExpandedAuthorsList visible={isAuthorsExpanded} authors={authors} />
<Text style={[styles.meta, a.self_start]}>
+1 -2
View File
@@ -7,7 +7,6 @@ import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {PressableWithHover} from '../util/PressableWithHover'
import {Text} from '../util/text/Text'
import {DraggableScrollView} from './DraggableScrollView'
import hairlineWidth = StyleSheet.hairlineWidth
export interface TabBarProps {
testID?: string
@@ -208,6 +207,6 @@ const mobileStyles = StyleSheet.create({
left: 0,
right: 0,
top: '100%',
borderBottomWidth: hairlineWidth,
borderBottomWidth: StyleSheet.hairlineWidth,
},
})
+5 -6
View File
@@ -44,7 +44,6 @@ import {PostEmbeds} from '../util/post-embeds'
import {PostMeta} from '../util/PostMeta'
import {Text} from '../util/text/Text'
import {PreviewableUserAvatar} from '../util/UserAvatar'
import hairlineWidth = StyleSheet.hairlineWidth
export function PostThreadItem({
post,
@@ -604,7 +603,7 @@ function PostOuterWrapper({
{
flexDirection: 'row',
paddingHorizontal: isMobile ? 10 : 6,
borderTopWidth: depth === 1 ? hairlineWidth : 0,
borderTopWidth: depth === 1 ? StyleSheet.hairlineWidth : 0,
},
]}>
{Array.from(Array(depth - 1)).map((_, n: number) => (
@@ -699,7 +698,7 @@ function getThreadAuthor(
const styles = StyleSheet.create({
outer: {
borderTopWidth: hairlineWidth,
borderTopWidth: StyleSheet.hairlineWidth,
paddingLeft: 8,
},
outerHighlighted: {
@@ -709,7 +708,7 @@ const styles = StyleSheet.create({
paddingRight: 8,
},
outerHighlightedRoot: {
borderTopWidth: hairlineWidth,
borderTopWidth: StyleSheet.hairlineWidth,
paddingTop: 16,
},
noTopBorder: {
@@ -761,8 +760,8 @@ const styles = StyleSheet.create({
expandedInfo: {
flexDirection: 'row',
padding: 10,
borderTopWidth: hairlineWidth,
borderBottomWidth: hairlineWidth,
borderTopWidth: StyleSheet.hairlineWidth,
borderBottomWidth: StyleSheet.hairlineWidth,
marginTop: 5,
marginBottom: 10,
},
+33 -32
View File
@@ -37,7 +37,6 @@ import {PostMeta} from '../util/PostMeta'
import {Text} from '../util/text/Text'
import {PreviewableUserAvatar} from '../util/UserAvatar'
import {UserInfoText} from '../util/UserInfoText'
import hairlineWidth = StyleSheet.hairlineWidth
export function Post({
post,
@@ -155,7 +154,7 @@ function PostInner({
style={[
styles.outer,
pal.border,
!hideTopBorder && {borderTopWidth: hairlineWidth},
!hideTopBorder && {borderTopWidth: StyleSheet.hairlineWidth},
style,
]}
onBeforePress={onBeforePress}>
@@ -210,38 +209,40 @@ function PostInner({
</View>
)}
<LabelsOnMyPost post={post} />
<ContentHider
modui={moderation.ui('contentView')}
style={styles.contentHider}
childContainerStyle={styles.contentHiderChild}>
<PostAlerts
{false && (
<ContentHider
modui={moderation.ui('contentView')}
style={[a.py_xs]}
/>
{richText.text ? (
<View style={styles.postTextContainer}>
<RichText
enableTags
testID="postText"
value={richText}
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
style={[a.flex_1, a.text_md]}
authorHandle={post.author.handle}
/>
</View>
) : undefined}
{limitLines ? (
<TextLink
text={_(msg`Show More`)}
style={pal.link}
onPress={onPressShowMore}
href="#"
style={styles.contentHider}
childContainerStyle={styles.contentHiderChild}>
<PostAlerts
modui={moderation.ui('contentView')}
style={[a.py_xs]}
/>
) : undefined}
{post.embed ? (
<PostEmbeds embed={post.embed} moderation={moderation} />
) : null}
</ContentHider>
{richText.text ? (
<View style={styles.postTextContainer}>
<RichText
enableTags
testID="postText"
value={richText}
numberOfLines={limitLines ? MAX_POST_LINES : undefined}
style={[a.flex_1, a.text_md]}
authorHandle={post.author.handle}
/>
</View>
) : undefined}
{limitLines ? (
<TextLink
text={_(msg`Show More`)}
style={pal.link}
onPress={onPressShowMore}
href="#"
/>
) : undefined}
{post.embed ? (
<PostEmbeds embed={post.embed} moderation={moderation} />
) : null}
</ContentHider>
)}
<PostCtrls
post={post}
record={record}
+58 -33
View File
@@ -16,8 +16,10 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query'
import {useGate} from '#/lib/statsig/statsig'
import {POST_TOMBSTONE, Shadow, usePostShadow} from '#/state/cache/post-shadow'
import {useFeedFeedbackContext} from '#/state/feed-feedback'
import {useSession} from '#/state/session'
import {useComposerControls} from '#/state/shell/composer'
import {isReasonFeedSource, ReasonFeedSource} from 'lib/api/feed/types'
import {MAX_POST_LINES} from 'lib/constants'
@@ -29,6 +31,7 @@ import {countLines} from 'lib/strings/helpers'
import {s} from 'lib/styles'
import {precacheProfile} from 'state/queries/profile'
import {atoms as a} from '#/alf'
import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost'
import {ContentHider} from '#/components/moderation/ContentHider'
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
import {RichText} from '#/components/RichText'
@@ -38,17 +41,19 @@ import {FeedNameText} from '../util/FeedInfoText'
import {Link, TextLink, TextLinkOnWebOnly} from '../util/Link'
import {PostCtrls} from '../util/post-ctrls/PostCtrls'
import {PostEmbeds} from '../util/post-embeds'
import {VideoEmbed} from '../util/post-embeds/VideoEmbed'
import {PostMeta} from '../util/PostMeta'
import {Text} from '../util/text/Text'
import {PreviewableUserAvatar} from '../util/UserAvatar'
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 {
record: AppBskyFeedPost.Record
reason: AppBskyFeedDefs.ReasonRepost | ReasonFeedSource | undefined
reason:
| AppBskyFeedDefs.ReasonRepost
| ReasonFeedSource
| {[k: string]: unknown; $type: string}
| undefined
moderation: ModerationDecision
parentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined
showReplyTo: boolean
@@ -132,6 +137,8 @@ let FeedItemInner = ({
const {openComposer} = useComposerControls()
const pal = usePalette('default')
const {_} = useLingui()
const gate = useGate()
const href = useMemo(() => {
const urip = new AtUri(post.uri)
return makeProfileLink(post.author, 'post', urip.rkey)
@@ -197,7 +204,8 @@ let FeedItemInner = ({
isThreadLastChild || (!isThreadChild && !isThreadParent)
? 8
: undefined,
borderTopWidth: hideTopBorder || isThreadChild ? 0 : hairlineWidth,
borderTopWidth:
hideTopBorder || isThreadChild ? 0 : StyleSheet.hairlineWidth,
},
]
@@ -337,9 +345,11 @@ let FeedItemInner = ({
postHref={href}
onOpenAuthor={onOpenAuthor}
/>
{!isThreadChild && showReplyTo && parentAuthor && (
<ReplyToLabel blocked={isParentBlocked} profile={parentAuthor} />
)}
{!isThreadChild &&
showReplyTo &&
(parentAuthor || isParentBlocked) && (
<ReplyToLabel blocked={isParentBlocked} profile={parentAuthor} />
)}
<LabelsOnMyPost post={post} />
<PostContent
moderation={moderation}
@@ -348,6 +358,9 @@ let FeedItemInner = ({
postAuthor={post.author}
onOpenEmbed={onOpenEmbed}
/>
{__DEV__ && gate('videos') && (
<VideoEmbed source="https://lumi.jazco.dev/watch/did:plc:q6gjnaw2blty4crticxkmujt/Qmc8w93UpTa2adJHg4ZhnDPrBs1EsbzrekzPcqF5SwusuZ/playlist.m3u8" />
)}
<PostCtrls
post={post}
record={record}
@@ -431,12 +444,46 @@ function ReplyToLabel({
profile,
blocked,
}: {
profile: AppBskyActorDefs.ProfileViewBasic
profile: AppBskyActorDefs.ProfileViewBasic | undefined
blocked?: boolean
}) {
const pal = usePalette('default')
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 (
<View style={[s.flexRow, s.mb2, s.alignCenter]}>
@@ -450,29 +497,7 @@ function ReplyToLabel({
style={[pal.textLight, s.mr2]}
lineHeight={1.2}
numberOfLines={1}>
{isMe ? (
<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>
)}
{label}
</Text>
</View>
)
+2 -3
View File
@@ -23,12 +23,11 @@ import {
KnownFollowers,
shouldShowKnownFollowers,
} from '#/components/KnownFollowers'
import * as Pills from '#/components/Pills'
import {Link} from '../util/Link'
import {Text} from '../util/text/Text'
import {PreviewableUserAvatar} from '../util/UserAvatar'
import {FollowButton} from './FollowButton'
import hairlineWidth = StyleSheet.hairlineWidth
import * as Pills from '#/components/Pills'
export function ProfileCard({
testID,
@@ -217,7 +216,7 @@ export function ProfileCardWithFollowBtn({
const styles = StyleSheet.create({
outer: {
borderTopWidth: hairlineWidth,
borderTopWidth: StyleSheet.hairlineWidth,
paddingHorizontal: 6,
paddingVertical: 4,
},
@@ -15,15 +15,13 @@ import {makeProfileLink} from 'lib/routes/links'
import {NavigationProp} from 'lib/routes/types'
import {sanitizeHandle} from 'lib/strings/handles'
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 {LoadingPlaceholder} from '../util/LoadingPlaceholder'
import {Text} from '../util/text/Text'
import {UserAvatar, UserAvatarType} from '../util/UserAvatar'
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({
isLoading,
@@ -84,7 +82,7 @@ export function ProfileSubpageHeader({
{
flexDirection: 'row',
alignItems: 'center',
borderBottomWidth: hairlineWidth,
borderBottomWidth: StyleSheet.hairlineWidth,
paddingTop: isNative ? 0 : 8,
paddingBottom: 8,
paddingHorizontal: isMobile ? 12 : 14,
+1 -2
View File
@@ -17,7 +17,6 @@ import {
Heart2_Stroke2_Corner0_Rounded as HeartIconOutline,
} from '#/components/icons/Heart2'
import {Repost_Stroke2_Corner2_Rounded as Repost} from '#/components/icons/Repost'
import hairlineWidth = StyleSheet.hairlineWidth
export function LoadingPlaceholder({
width,
@@ -236,7 +235,7 @@ export function FeedLoadingPlaceholder({
{
paddingHorizontal: 12,
paddingVertical: 18,
borderTopWidth: showTopBorder ? hairlineWidth : 0,
borderTopWidth: showTopBorder ? StyleSheet.hairlineWidth : 0,
},
pal.border,
style,
+1 -1
View File
@@ -12,7 +12,7 @@ import {atoms as a, useTheme} from '#/alf'
import {Text} from '#/components/Typography'
import {IS_TEST} from '#/env'
const TIMEOUT = 3.7e3
const TIMEOUT = 2e3
export function show(
message: string,
+3 -4
View File
@@ -13,10 +13,9 @@ import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {NavigationProp} from 'lib/routes/types'
import {useTheme} from '#/alf'
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
import {Text} from './text/Text'
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}
@@ -154,7 +153,7 @@ function DesktopWebHeader({
styles.desktopHeader,
pal.border,
{
borderBottomWidth: showBorder ? hairlineWidth : 0,
borderBottomWidth: showBorder ? StyleSheet.hairlineWidth : 0,
},
{display: 'flex', flexDirection: 'column'},
]}>
@@ -243,7 +242,7 @@ const styles = StyleSheet.create({
marginRight: 'auto',
},
border: {
borderBottomWidth: hairlineWidth,
borderBottomWidth: StyleSheet.hairlineWidth,
},
titleContainer: {
marginLeft: 'auto',
+4 -5
View File
@@ -26,7 +26,6 @@ import Animated from 'react-native-reanimated'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {addStyle} from 'lib/styles'
import hairlineWidth = StyleSheet.hairlineWidth
interface AddedProps {
desktopFixedHeight?: boolean | number
@@ -50,8 +49,8 @@ export const CenteredView = React.forwardRef(function CenteredView(
}
if (sideBorders) {
style = addStyle(style, {
borderLeftWidth: hairlineWidth,
borderRightWidth: hairlineWidth,
borderLeftWidth: StyleSheet.hairlineWidth,
borderRightWidth: StyleSheet.hairlineWidth,
})
style = addStyle(style, pal.border)
}
@@ -163,8 +162,8 @@ export const ScrollView = React.forwardRef(function ScrollViewImpl(
const styles = StyleSheet.create({
contentContainer: {
borderLeftWidth: hairlineWidth,
borderRightWidth: hairlineWidth,
borderLeftWidth: StyleSheet.hairlineWidth,
borderRightWidth: StyleSheet.hairlineWidth,
// @ts-ignore web only
minHeight: '100vh',
},
@@ -13,7 +13,6 @@ import {clamp} from '#/lib/numbers'
import {colors} from '#/lib/styles'
import {isWeb} from '#/platform/detection'
import {useSession} from '#/state/session'
import hairlineWidth = StyleSheet.hairlineWidth
const AnimatedTouchableOpacity =
Animated.createAnimatedComponent(TouchableOpacity)
@@ -74,7 +73,7 @@ const styles = StyleSheet.create({
// @ts-ignore 'fixed' is web only -prf
position: isWeb ? 'fixed' : 'absolute',
left: 18,
borderWidth: hairlineWidth,
borderWidth: StyleSheet.hairlineWidth,
width: 52,
height: 52,
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],
),
}
}
+2 -3
View File
@@ -39,7 +39,6 @@ import {Link} from '../Link'
import {PostMeta} from '../PostMeta'
import {Text} from '../text/Text'
import {PostEmbeds} from '.'
import hairlineWidth = StyleSheet.hairlineWidth
export function MaybeQuoteEmbed({
embed,
@@ -262,7 +261,7 @@ const styles = StyleSheet.create({
marginTop: 8,
paddingVertical: 12,
paddingHorizontal: 12,
borderWidth: hairlineWidth,
borderWidth: StyleSheet.hairlineWidth,
},
errorContainer: {
flexDirection: 'row',
@@ -272,7 +271,7 @@ const styles = StyleSheet.create({
marginTop: 8,
paddingVertical: 14,
paddingHorizontal: 14,
borderWidth: hairlineWidth,
borderWidth: StyleSheet.hairlineWidth,
},
alert: {
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')
}
+3 -4
View File
@@ -19,18 +19,17 @@ import {
} from '@atproto/api'
import {ImagesLightbox, useLightboxControls} from '#/state/lightbox'
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
import {usePalette} from 'lib/hooks/usePalette'
import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard'
import {atoms as a} from '#/alf'
import {Embed as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
import {ContentHider} from '../../../../components/moderation/ContentHider'
import {AutoSizedImage} from '../images/AutoSizedImage'
import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
import {ExternalLinkEmbed} from './ExternalLinkEmbed'
import {ListEmbed} from './ListEmbed'
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 =
| AppBskyEmbedRecord.View
@@ -205,7 +204,7 @@ const styles = StyleSheet.create({
fontWeight: 'bold',
},
customFeedOuter: {
borderWidth: hairlineWidth,
borderWidth: StyleSheet.hairlineWidth,
borderRadius: 8,
marginTop: 4,
paddingHorizontal: 12,
+5 -6
View File
@@ -33,14 +33,13 @@ import {ViewHeader} from 'view/com/util/ViewHeader'
import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
import {atoms as a, useTheme} from '#/alf'
import {Divider} from '#/components/Divider'
import * as FeedCard from '#/components/FeedCard'
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 {ListMagnifyingGlass_Stroke2_Corner0_Rounded} from '#/components/icons/ListMagnifyingGlass'
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'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Feeds'>
@@ -793,13 +792,13 @@ const styles = StyleSheet.create({
paddingHorizontal: 16,
paddingVertical: 14,
gap: 12,
borderBottomWidth: hairlineWidth,
borderBottomWidth: StyleSheet.hairlineWidth,
},
savedFeedMobile: {
paddingVertical: 10,
},
offlineSlug: {
borderWidth: hairlineWidth,
borderWidth: StyleSheet.hairlineWidth,
borderRadius: 4,
paddingHorizontal: 4,
paddingVertical: 2,
+5 -3
View File
@@ -16,7 +16,6 @@ import {MyLists} from '#/view/com/lists/MyLists'
import {Button} from 'view/com/util/forms/Button'
import {SimpleViewHeader} from 'view/com/util/SimpleViewHeader'
import {Text} from 'view/com/util/text/Text'
import hairlineWidth = StyleSheet.hairlineWidth
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Lists'>
export function ListsScreen({}: Props) {
@@ -55,8 +54,11 @@ export function ListsScreen({}: Props) {
style={[
pal.border,
isMobile
? {borderBottomWidth: hairlineWidth}
: {borderLeftWidth: hairlineWidth, borderRightWidth: hairlineWidth},
? {borderBottomWidth: StyleSheet.hairlineWidth}
: {
borderLeftWidth: StyleSheet.hairlineWidth,
borderRightWidth: StyleSheet.hairlineWidth,
},
]}>
<View style={{flex: 1}}>
<Text type="title-lg" style={[pal.text, {fontWeight: 'bold'}]}>
+3 -3
View File
@@ -8,7 +8,7 @@ import {AllNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
import {useNotificationFeedQuery} from '#/state/queries/notifications/feed'
import {useNotificationsSettingsMutation} from '#/state/queries/notifications/settings'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {CenteredView} from '#/view/com/util/Views'
import {ScrollView} from '#/view/com/util/Views'
import {atoms as a, useTheme} from '#/alf'
import {Error} from '#/components/Error'
import * as Toggle from '#/components/forms/Toggle'
@@ -34,7 +34,7 @@ export function NotificationsSettingsScreen({}: Props) {
: serverPriority
return (
<CenteredView style={a.flex_1} sideBorders>
<ScrollView stickyHeaderIndices={[0]}>
<ViewHeader
title={_(msg`Notification Settings`)}
showOnDesktop
@@ -89,6 +89,6 @@ export function NotificationsSettingsScreen({}: Props) {
</View>
</View>
)}
</CenteredView>
</ScrollView>
)
}
+2 -3
View File
@@ -65,7 +65,6 @@ import {useDialogControl} from '#/components/Dialog'
import * as Prompt from '#/components/Prompt'
import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
import {RichText} from '#/components/RichText'
import hairlineWidth = StyleSheet.hairlineWidth
const SECTION_TITLES_CURATE = ['Posts', 'About']
const SECTION_TITLES_MOD = ['About']
@@ -803,7 +802,7 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
<View
style={[
{
borderTopWidth: hairlineWidth,
borderTopWidth: StyleSheet.hairlineWidth,
padding: isMobile ? 14 : 20,
gap: 12,
},
@@ -954,7 +953,7 @@ function ErrorScreen({error}: {error: string}) {
marginTop: 10,
paddingHorizontal: 18,
paddingVertical: 14,
borderTopWidth: hairlineWidth,
borderTopWidth: StyleSheet.hairlineWidth,
},
]}>
<Text type="title-lg" style={[pal.text, s.mb10]}>
+6 -5
View File
@@ -32,7 +32,6 @@ import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
import {atoms as a, useTheme} from '#/alf'
import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline'
import hairlineWidth = StyleSheet.hairlineWidth
const HITSLOP_TOP = {
top: 20,
@@ -93,7 +92,8 @@ export function SavedFeeds({}: Props) {
<ViewHeader title={_(msg`Edit My Feeds`)} showOnDesktop showBorder />
<ScrollView style={s.flex1} contentContainerStyle={[styles.noBorder]}>
{noSavedFeedsOfAnyType && (
<View style={[pal.border, {borderBottomWidth: hairlineWidth}]}>
<View
style={[pal.border, {borderBottomWidth: StyleSheet.hairlineWidth}]}>
<NoSavedFeedsOfAnyType />
</View>
)}
@@ -135,7 +135,8 @@ export function SavedFeeds({}: Props) {
)}
{noFollowingFeed && (
<View style={[pal.border, {borderBottomWidth: hairlineWidth}]}>
<View
style={[pal.border, {borderBottomWidth: StyleSheet.hairlineWidth}]}>
<NoFollowingFeed />
</View>
)}
@@ -437,12 +438,12 @@ const styles = StyleSheet.create({
paddingHorizontal: 14,
paddingTop: 20,
paddingBottom: 10,
borderBottomWidth: hairlineWidth,
borderBottomWidth: StyleSheet.hairlineWidth,
},
itemContainer: {
flexDirection: 'row',
alignItems: 'center',
borderBottomWidth: hairlineWidth,
borderBottomWidth: StyleSheet.hairlineWidth,
},
footerText: {
paddingHorizontal: 26,
+9 -8
View File
@@ -75,17 +75,17 @@ function SuggestedItemsHeader({
)
}
type LoadMoreItems =
type LoadMoreItem =
| {
type: 'profile'
key: string
avatar: string
avatar: string | undefined
moderation: ModerationDecision
}
| {
type: 'feed'
key: string
avatar: string
avatar: string | undefined
moderation: undefined
}
@@ -98,27 +98,28 @@ function LoadMore({
}) {
const t = useTheme()
const {_} = useLingui()
const items = React.useMemo(() => {
const items: LoadMoreItem[] = React.useMemo(() => {
return item.items
.map(_item => {
let loadMoreItem: LoadMoreItem | undefined
if (_item.type === 'profile') {
return {
loadMoreItem = {
type: 'profile',
key: _item.profile.did,
avatar: _item.profile.avatar,
moderation: moderateProfile(_item.profile, moderationOpts!),
}
} else if (_item.type === 'feed') {
return {
loadMoreItem = {
type: 'feed',
key: _item.feed.uri,
avatar: _item.feed.avatar,
moderation: undefined,
}
}
return undefined
return loadMoreItem
})
.filter(Boolean) as LoadMoreItems[]
.filter(<T,>(n?: T): n is T => Boolean(n))
}, [item.items, moderationOpts])
if (items.length === 0) return null
+2 -3
View File
@@ -62,13 +62,12 @@ import {UserAvatar} from 'view/com/util/UserAvatar'
import {ScrollView} from 'view/com/util/Views'
import {DeactivateAccountDialog} from '#/screens/Settings/components/DeactivateAccountDialog'
import {useTheme} from '#/alf'
import {atoms as a} from '#/alf'
import {useDialogControl} from '#/components/Dialog'
import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings'
import {navigate, resetToTab} from '#/Navigation'
import {Email2FAToggle} from './Email2FAToggle'
import {ExportCarDialog} from './ExportCarDialog'
import hairlineWidth = StyleSheet.hairlineWidth
import {atoms as a} from '#/alf'
function SettingsAccountCard({
account,
@@ -326,7 +325,7 @@ export function SettingsScreen({}: Props) {
showBackButton={isMobile}
style={[
pal.border,
{borderBottomWidth: hairlineWidth},
{borderBottomWidth: StyleSheet.hairlineWidth},
!isMobile && {borderLeftWidth: 1, borderRightWidth: 1},
]}>
<View style={{flex: 1}}>
@@ -1,7 +1,6 @@
import {StyleSheet} from 'react-native'
import {colors} from 'lib/styles'
import hairlineWidth = StyleSheet.hairlineWidth
export const styles = StyleSheet.create({
bottomBar: {
@@ -10,7 +9,7 @@ export const styles = StyleSheet.create({
left: 0,
right: 0,
flexDirection: 'row',
borderTopWidth: hairlineWidth,
borderTopWidth: StyleSheet.hairlineWidth,
paddingLeft: 5,
paddingRight: 10,
},
+3 -4
View File
@@ -11,10 +11,9 @@ import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {s} from 'lib/styles'
import {TextLink} from 'view/com/util/Link'
import {Text} from 'view/com/util/text/Text'
import {ProgressGuideList} from '#/components/ProgressGuide/List'
import {DesktopFeeds} from './Feeds'
import {DesktopSearch} from './Search'
import hairlineWidth = StyleSheet.hairlineWidth
import {ProgressGuideList} from '#/components/ProgressGuide/List'
export function DesktopRightNav({routeName}: {routeName: string}) {
const pal = usePalette('default')
@@ -135,8 +134,8 @@ const styles = StyleSheet.create({
marginBottom: 10,
},
desktopFeedsContainer: {
borderTopWidth: hairlineWidth,
borderBottomWidth: hairlineWidth,
borderTopWidth: StyleSheet.hairlineWidth,
borderBottomWidth: StyleSheet.hairlineWidth,
marginTop: 18,
marginBottom: 18,
},
+5
View File
@@ -13345,6 +13345,11 @@ history@^5.3.0:
dependencies:
"@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:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"