diff --git a/modules/Share-with-Bluesky/Info.plist b/modules/Share-with-Bluesky/Info.plist
index 43f46a5e56..d73a9093fc 100644
--- a/modules/Share-with-Bluesky/Info.plist
+++ b/modules/Share-with-Bluesky/Info.plist
@@ -40,4 +40,4 @@
CFBundleShortVersionString
$(MARKETING_VERSION)
-
+
\ No newline at end of file
diff --git a/modules/Share-with-Bluesky/ShareViewController.swift b/modules/Share-with-Bluesky/ShareViewController.swift
index 63143277a5..46851a0d79 100644
--- a/modules/Share-with-Bluesky/ShareViewController.swift
+++ b/modules/Share-with-Bluesky/ShareViewController.swift
@@ -23,7 +23,7 @@ class ShareViewController: UIViewController {
await self.handleUrl(item: firstAttachment)
} else if firstAttachment.hasItemConformingToTypeIdentifier("public.image") {
await self.handleImages(items: attachments)
- } else if firstAttachment.hasItemConformingToTypeIdentifier("public.video") {
+ } else if firstAttachment.hasItemConformingToTypeIdentifier("public.movie") {
await self.handleVideos(items: attachments)
} else {
self.completeRequest()
@@ -101,13 +101,13 @@ class ShareViewController: UIViewController {
private func handleVideos(items: [NSItemProvider]) async {
let firstItem = items.first
- if let dataUri = try? await firstItem?.loadItem(forTypeIdentifier: "public.video") as? URL {
+ if let dataUri = try? await firstItem?.loadItem(forTypeIdentifier: "public.movie") as? URL {
let ext = String(dataUri.lastPathComponent.split(separator: ".").last ?? "mp4")
if let tempUrl = getTempUrl(ext: ext) {
let data = try? Data(contentsOf: dataUri)
try? data?.write(to: tempUrl)
- if let encoded = dataUri.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),
+ if let encoded = tempUrl.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),
let url = URL(string: "\(self.appScheme)://intent/compose?videoUri=\(encoded)") {
_ = self.openURL(url)
}
@@ -150,7 +150,8 @@ class ShareViewController: UIViewController {
var responder: UIResponder? = self
while responder != nil {
if let application = responder as? UIApplication {
- return application.perform(#selector(openURL(_:)), with: url) != nil
+ application.open(url)
+ return true
}
responder = responder?.next
}
diff --git a/src/alf/fonts.ts b/src/alf/fonts.ts
index b11ce939f8..46c741bc79 100644
--- a/src/alf/fonts.ts
+++ b/src/alf/fonts.ts
@@ -45,15 +45,18 @@ export function applyFonts(
// '100': 'Inter-Thin',
// '200': 'Inter-ExtraLight',
// '300': 'Inter-Light',
+ // '500': 'Inter-Medium',
+ // '700': 'Inter-Bold',
+ // '900': 'Inter-Black',
'100': 'Inter-Regular',
'200': 'Inter-Regular',
'300': 'Inter-Regular',
'400': 'Inter-Regular',
- '500': 'Inter-Medium',
+ '500': 'Inter-SemiBold',
'600': 'Inter-SemiBold',
- '700': 'Inter-Bold',
+ '700': 'Inter-SemiBold',
'800': 'Inter-ExtraBold',
- '900': 'Inter-Black',
+ '900': 'Inter-ExtraBold',
}[style.fontWeight as string] || 'Inter-Regular'
if (style.fontStyle === 'italic') {
diff --git a/src/alf/tokens.ts b/src/alf/tokens.ts
index 3f30702e85..f3ac17e6af 100644
--- a/src/alf/tokens.ts
+++ b/src/alf/tokens.ts
@@ -60,6 +60,15 @@ export const fontWeight = {
} as const
export const gradients = {
+ primary: {
+ values: [
+ [0, '#054CFF'],
+ [0.4, '#1085FE'],
+ [0.6, '#1085FE'],
+ [1, '#59B9FF'],
+ ],
+ hover_value: '#1085FE',
+ },
sky: {
values: [
[0, '#0A7AFF'],
diff --git a/src/components/AccountList.tsx b/src/components/AccountList.tsx
index 883c06c144..68bb482af2 100644
--- a/src/components/AccountList.tsx
+++ b/src/components/AccountList.tsx
@@ -126,10 +126,12 @@ function AccountItem({
-
+
{profile?.displayName || account.handle}{' '}
- {account.handle}
+
+ {account.handle}
+
{isCurrentAccount ? (
(
secondary: tokens.gradients.sky,
secondary_inverted: tokens.gradients.sky,
negative: tokens.gradients.sky,
+ gradient_primary: tokens.gradients.primary,
gradient_sky: tokens.gradients.sky,
gradient_midnight: tokens.gradients.midnight,
gradient_sunrise: tokens.gradients.sunrise,
@@ -444,7 +446,7 @@ export const Button = React.forwardRef(
[state, variant, color, size, disabled],
)
- const flattenedBaseStyles = flatten(baseStyles)
+ const flattenedBaseStyles = flatten([baseStyles, style])
return (
(
a.align_center,
a.justify_center,
flattenedBaseStyles,
- flatten(style),
...(state.hovered || state.pressed
? [hoverStyles, flatten(hoverStyleProp)]
: []),
@@ -626,9 +627,9 @@ export function useSharedButtonTextStyles() {
}
if (size === 'large') {
- baseStyles.push(a.text_md, a.leading_tight, web({paddingTop: 1}))
+ baseStyles.push(a.text_md, a.leading_tight, web({top: -0.4}))
} else if (size === 'small') {
- baseStyles.push(a.text_sm, a.leading_tight, web({paddingTop: 1}))
+ baseStyles.push(a.text_sm, a.leading_tight, web({top: -0.4}))
} else if (size === 'tiny') {
baseStyles.push(a.text_xs, a.leading_tight)
}
diff --git a/src/components/dialogs/nuxs/TenMillion/Trigger.tsx b/src/components/dialogs/nuxs/TenMillion/Trigger.tsx
deleted file mode 100644
index 9616b3b1d3..0000000000
--- a/src/components/dialogs/nuxs/TenMillion/Trigger.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-import React from 'react'
-import {View} from 'react-native'
-import Svg, {Circle, Path} from 'react-native-svg'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {Nux, useUpsertNuxMutation} from '#/state/queries/nuxs'
-import {atoms as a, ViewStyleProp} from '#/alf'
-import {Button, ButtonProps} from '#/components/Button'
-import * as Dialog from '#/components/Dialog'
-import {InlineLinkText} from '#/components/Link'
-import * as Prompt from '#/components/Prompt'
-import {TenMillion} from './'
-
-export function Trigger({children}: {children: ButtonProps['children']}) {
- const {_} = useLingui()
- const {mutate: upsertNux} = useUpsertNuxMutation()
- const [show, setShow] = React.useState(false)
- const [fallback, setFallback] = React.useState(false)
- const control = Prompt.usePromptControl()
-
- const handleOnPress = () => {
- if (!fallback) {
- setShow(true)
- upsertNux({
- id: Nux.TenMillionDialog,
- completed: true,
- data: undefined,
- })
- } else {
- control.open()
- }
- }
-
- const onHandleFallback = () => {
- setFallback(true)
- control.open()
- }
-
- return (
- <>
-
-
- {show && !fallback && (
- setShow(false)}
- onFallback={onHandleFallback}
- />
- )}
-
-
-
-
- Bluesky is celebrating 10 million users!
-
-
-
-
- Together, we're rebuilding the social internet. We're glad you're
- here.
-
-
-
-
- To learn more,{' '}
- {
- control.close()
- }}
- style={[a.text_md, a.leading_snug]}>
- check out our post.
-
-
-
-
-
- >
- )
-}
-
-export function Icon({width, style}: {width: number} & ViewStyleProp) {
- return (
-
- )
-}
diff --git a/src/components/dialogs/nuxs/TenMillion/icons/OnePercent.tsx b/src/components/dialogs/nuxs/TenMillion/icons/OnePercent.tsx
deleted file mode 100644
index 9c8d47afd0..0000000000
--- a/src/components/dialogs/nuxs/TenMillion/icons/OnePercent.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react'
-import Svg, {Path} from 'react-native-svg'
-
-export function OnePercent({fill}: {fill?: string}) {
- return (
-
- )
-}
diff --git a/src/components/dialogs/nuxs/TenMillion/icons/PointOnePercent.tsx b/src/components/dialogs/nuxs/TenMillion/icons/PointOnePercent.tsx
deleted file mode 100644
index 1f9467e442..0000000000
--- a/src/components/dialogs/nuxs/TenMillion/icons/PointOnePercent.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react'
-import Svg, {Path} from 'react-native-svg'
-
-export function PointOnePercent({fill}: {fill?: string}) {
- return (
-
- )
-}
diff --git a/src/components/dialogs/nuxs/TenMillion/icons/TenPercent.tsx b/src/components/dialogs/nuxs/TenMillion/icons/TenPercent.tsx
deleted file mode 100644
index 4197be8357..0000000000
--- a/src/components/dialogs/nuxs/TenMillion/icons/TenPercent.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react'
-import Svg, {Path} from 'react-native-svg'
-
-export function TenPercent({fill}: {fill?: string}) {
- return (
-
- )
-}
diff --git a/src/components/dialogs/nuxs/TenMillion/icons/TwentyFivePercent.tsx b/src/components/dialogs/nuxs/TenMillion/icons/TwentyFivePercent.tsx
deleted file mode 100644
index 0d37971410..0000000000
--- a/src/components/dialogs/nuxs/TenMillion/icons/TwentyFivePercent.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react'
-import Svg, {Path} from 'react-native-svg'
-
-export function TwentyFivePercent({fill}: {fill?: string}) {
- return (
-
- )
-}
diff --git a/src/components/dialogs/nuxs/TenMillion/index.tsx b/src/components/dialogs/nuxs/TenMillion/index.tsx
deleted file mode 100644
index 21e775a108..0000000000
--- a/src/components/dialogs/nuxs/TenMillion/index.tsx
+++ /dev/null
@@ -1,712 +0,0 @@
-import React from 'react'
-import {View} from 'react-native'
-import Animated, {FadeIn} from 'react-native-reanimated'
-import ViewShot from 'react-native-view-shot'
-import {Image} from 'expo-image'
-import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker'
-import * as MediaLibrary from 'expo-media-library'
-import {moderateProfile} from '@atproto/api'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {networkRetry} from '#/lib/async/retry'
-import {getCanvas} from '#/lib/canvas'
-import {shareUrl} from '#/lib/sharing'
-import {logEvent} from '#/lib/statsig/statsig'
-import {sanitizeDisplayName} from '#/lib/strings/display-names'
-import {sanitizeHandle} from '#/lib/strings/handles'
-import {isIOS, isNative} from '#/platform/detection'
-import {useModerationOpts} from '#/state/preferences/moderation-opts'
-import {useProfileQuery} from '#/state/queries/profile'
-import {useAgent, useSession} from '#/state/session'
-import {useComposerControls} from '#/state/shell'
-import {formatCount} from '#/view/com/util/numeric/format'
-import * as Toast from '#/view/com/util/Toast'
-import {Logomark} from '#/view/icons/Logomark'
-import {
- atoms as a,
- ThemeProvider,
- tokens,
- useBreakpoints,
- useTheme,
-} from '#/alf'
-import {Button, ButtonIcon, ButtonText} from '#/components/Button'
-import * as Dialog from '#/components/Dialog'
-import {useNuxDialogContext} from '#/components/dialogs/nuxs'
-import {OnePercent} from '#/components/dialogs/nuxs/TenMillion/icons/OnePercent'
-import {PointOnePercent} from '#/components/dialogs/nuxs/TenMillion/icons/PointOnePercent'
-import {TenPercent} from '#/components/dialogs/nuxs/TenMillion/icons/TenPercent'
-import {Divider} from '#/components/Divider'
-import {GradientFill} from '#/components/GradientFill'
-import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
-import {Download_Stroke2_Corner0_Rounded as Download} from '#/components/icons/Download'
-import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image'
-import {Loader} from '#/components/Loader'
-import {Text} from '#/components/Typography'
-
-const DEBUG = false
-const RATIO = 8 / 10
-const WIDTH = 2000
-const HEIGHT = WIDTH * RATIO
-
-function getFontSize(count: number) {
- const length = count.toString().length
- if (length < 7) {
- return 80
- } else if (length < 5) {
- return 100
- } else {
- return 70
- }
-}
-
-function getPercentBadge(percent: number) {
- if (percent <= 0.001) {
- return PointOnePercent
- } else if (percent <= 0.01) {
- return OnePercent
- } else if (percent <= 0.1) {
- return TenPercent
- }
- return null
-}
-
-function Frame({children}: {children: React.ReactNode}) {
- return (
-
- {children}
-
- )
-}
-
-export function TenMillion({
- showTimeout,
- onClose,
- onFallback,
-}: {
- showTimeout?: number
- onClose?: () => void
- onFallback?: () => void
-}) {
- const agent = useAgent()
- const nuxDialogs = useNuxDialogContext()
- const [userNumber, setUserNumber] = React.useState(0)
- const fetching = React.useRef(false)
-
- React.useEffect(() => {
- async function fetchUserNumber() {
- const isBlueskyHosted = agent.sessionManager.pdsUrl
- ?.toString()
- .includes('bsky.network')
-
- if (isBlueskyHosted && agent.session?.accessJwt) {
- const res = await fetch(
- `https://bsky.social/xrpc/com.atproto.temp.getSignupNumber`,
- {
- headers: {
- Authorization: `Bearer ${agent.session.accessJwt}`,
- },
- },
- )
-
- if (!res.ok) {
- throw new Error('Network request failed')
- }
-
- const data = await res.json()
-
- if (data.number && data.number <= 10_000_000) {
- setUserNumber(data.number)
- } else {
- // should be rare
- nuxDialogs.dismissActiveNux()
- onFallback?.()
- }
- } else {
- nuxDialogs.dismissActiveNux()
- onFallback?.()
- }
- }
-
- if (!fetching.current) {
- fetching.current = true
- networkRetry(3, fetchUserNumber).catch(() => {
- nuxDialogs.dismissActiveNux()
- onFallback?.()
- })
- }
- }, [
- agent.sessionManager.pdsUrl,
- agent.session?.accessJwt,
- setUserNumber,
- nuxDialogs.dismissActiveNux,
- nuxDialogs,
- onFallback,
- ])
-
- return userNumber ? (
-
- ) : null
-}
-
-export function TenMillionInner({
- userNumber,
- showTimeout,
- onClose: onCloseOuter,
-}: {
- userNumber: number
- showTimeout: number
- onClose?: () => void
-}) {
- const t = useTheme()
- const lightTheme = useTheme('light')
- const {_, i18n} = useLingui()
- const control = Dialog.useDialogControl()
- const {gtMobile} = useBreakpoints()
- const {openComposer} = useComposerControls()
- const {currentAccount} = useSession()
- const {
- isLoading: isProfileLoading,
- data: profile,
- error: profileError,
- } = useProfileQuery({
- did: currentAccount!.did,
- })
- const moderationOpts = useModerationOpts()
- const nuxDialogs = useNuxDialogContext()
- const moderation = React.useMemo(() => {
- return profile && moderationOpts
- ? moderateProfile(profile, moderationOpts)
- : undefined
- }, [profile, moderationOpts])
- const [uri, setUri] = React.useState(null)
- const percent = userNumber / 10_000_000
- const Badge = getPercentBadge(percent)
- const isLoadingData = isProfileLoading || !moderation || !profile
- const isLoadingImage = !uri
-
- const displayName = React.useMemo(() => {
- if (!profile || !moderation) return ''
- return sanitizeDisplayName(
- profile.displayName || sanitizeHandle(profile.handle),
- moderation.ui('displayName'),
- )
- }, [profile, moderation])
- const handle = React.useMemo(() => {
- if (!profile) return ''
- return sanitizeHandle(profile.handle, '@')
- }, [profile])
- const joinedDate = React.useMemo(() => {
- if (!profile || !profile.createdAt) return ''
- const date = i18n.date(profile.createdAt, {
- month: 'short',
- day: 'numeric',
- year: 'numeric',
- })
- return date
- }, [i18n, profile])
-
- const error: string = React.useMemo(() => {
- if (profileError) {
- return _(
- msg`Oh no! We weren't able to generate an image for you to share. Rest assured, we're glad you're here 🦋`,
- )
- }
- return ''
- }, [_, profileError])
-
- /*
- * Opening and closing
- */
- React.useEffect(() => {
- const timeout = setTimeout(() => {
- control.open()
- }, showTimeout)
- return () => {
- clearTimeout(timeout)
- }
- }, [control, showTimeout])
- const onClose = React.useCallback(() => {
- nuxDialogs.dismissActiveNux()
- onCloseOuter?.()
- }, [nuxDialogs, onCloseOuter])
-
- /*
- * Actions
- */
- const sharePost = React.useCallback(() => {
- if (uri) {
- control.close(() => {
- setTimeout(() => {
- logEvent('tmd:post', {})
- openComposer({
- text: _(
- msg`Bluesky now has over 10 million users, and I was #${i18n.number(
- userNumber,
- )}!`,
- ),
- imageUris: [
- {
- uri,
- width: WIDTH,
- height: HEIGHT,
- altText: _(
- msg`A virtual certificate with text "Celebrating 10M users on Bluesky, #${i18n.number(
- userNumber,
- )}, ${displayName} ${handle}, joined on ${joinedDate}"`,
- ),
- },
- ],
- })
- }, 1e3)
- })
- }
- }, [
- _,
- i18n,
- control,
- openComposer,
- uri,
- userNumber,
- displayName,
- handle,
- joinedDate,
- ])
- const onNativeShare = React.useCallback(() => {
- if (uri) {
- control.close(() => {
- logEvent('tmd:share', {})
- shareUrl(uri)
- })
- }
- }, [uri, control])
- const onNativeDownload = React.useCallback(async () => {
- if (uri) {
- const res = await requestMediaLibraryPermissionsAsync()
-
- if (!res) {
- Toast.show(
- _(
- msg`You must grant access to your photo library to save the image.`,
- ),
- 'xmark',
- )
- return
- }
-
- try {
- await MediaLibrary.createAssetAsync(uri)
- logEvent('tmd:download', {})
- Toast.show(_(msg`Image saved to your camera roll!`))
- } catch (e: unknown) {
- console.log(e)
- Toast.show(_(msg`An error occurred while saving the image!`), 'xmark')
- return
- }
- }
- }, [_, uri])
- const onWebDownload = React.useCallback(async () => {
- if (uri) {
- const canvas = await getCanvas(uri)
- const imgHref = canvas
- .toDataURL('image/png')
- .replace('image/png', 'image/octet-stream')
- const link = document.createElement('a')
- link.setAttribute('download', `Bluesky 10M Users.png`)
- link.setAttribute('href', imgHref)
- link.click()
- logEvent('tmd:download', {})
- }
- }, [uri])
-
- /*
- * Canvas stuff
- */
- const imageRef = React.useRef(null)
- const captureInProgress = React.useRef(false)
- const onCanvasReady = React.useCallback(async () => {
- if (
- imageRef.current &&
- imageRef.current.capture &&
- !captureInProgress.current
- ) {
- captureInProgress.current = true
- const uri = await imageRef.current.capture()
- setUri(uri)
- }
- }, [setUri])
- const canvas = isLoadingData ? null : (
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* Centered content */}
-
-
-
- Celebrating {formatCount(i18n, 10000000)} users
- {' '}
- 🎉
-
-
-
- #
-
-
- {i18n.number(userNumber)}
-
-
-
- {Badge && (
-
-
-
- )}
-
- {/* End centered content */}
-
-
-
- {/*
-
- */}
-
-
- {displayName}
-
-
-
- {handle}
-
-
- {profile.createdAt && (
-
- Joined on {joinedDate}
-
- )}
-
-
-
-
-
-
-
-
-
-
-
- )
-
- return (
-
-
-
-
-
-
- {error ? (
-
-
- (╯°□°)╯︵ ┻━┻
-
-
- {error}
-
-
- ) : isLoadingData || isLoadingImage ? (
-
- ) : (
-
-
-
- )}
-
-
-
- {canvas}
-
-
-
- Thanks for being one of our first 10 million users.
-
-
-
-
- Together, we're rebuilding the social internet. We're glad
- you're here.
-
-
-
-
-
-
- {gtMobile && (
-
- Brag a little!
-
- )}
-
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/src/components/dialogs/nuxs/index.tsx b/src/components/dialogs/nuxs/index.tsx
index b93831ad35..e7476972ad 100644
--- a/src/components/dialogs/nuxs/index.tsx
+++ b/src/components/dialogs/nuxs/index.tsx
@@ -16,10 +16,11 @@ import {
import {useProfileQuery} from '#/state/queries/profile'
import {SessionAccount, useSession} from '#/state/session'
import {useOnboardingState} from '#/state/shell'
+/*
+ * NUXs
+ */
import {NeueTypography} from '#/components/dialogs/nuxs/NeueTypography'
import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing'
-// NUXs
-import {TenMillion} from '#/components/dialogs/nuxs/TenMillion'
import {IS_DEV} from '#/env'
type Context = {
@@ -36,14 +37,11 @@ const queuedNuxs: {
preferences: UsePreferencesQueryResponse
}) => boolean
}[] = [
- {
- id: Nux.TenMillionDialog,
- },
{
id: Nux.NeueTypography,
enabled(props) {
if (props.currentProfile.createdAt) {
- if (new Date(props.currentProfile.createdAt) < new Date('2024-09-25')) {
+ if (new Date(props.currentProfile.createdAt) < new Date('2024-10-01')) {
return true
}
}
@@ -176,7 +174,6 @@ function Inner({
return (
- {activeNux === Nux.TenMillionDialog && }
{activeNux === Nux.NeueTypography && }
)
diff --git a/src/components/hooks/useHeaderOffset.ts b/src/components/hooks/useHeaderOffset.ts
index e2290c04fb..5c80e18fe0 100644
--- a/src/components/hooks/useHeaderOffset.ts
+++ b/src/components/hooks/useHeaderOffset.ts
@@ -9,8 +9,8 @@ export function useHeaderOffset() {
return 0
}
const navBarHeight = 42
- const tabBarPad = 10 + 10 + 3 // padding + border
- const normalLineHeight = 1.2
- const tabBarText = 16 * normalLineHeight * fontScale
+ const tabBarPad = 10 + 10 + 6 // padding + arbitrary
+ const normalLineHeight = 20 // matches tab bar
+ const tabBarText = normalLineHeight * fontScale
return navBarHeight + tabBarPad + tabBarText
}
diff --git a/src/lib/hooks/useIntentHandler.ts b/src/lib/hooks/useIntentHandler.ts
index fd16387034..ce1d474d31 100644
--- a/src/lib/hooks/useIntentHandler.ts
+++ b/src/lib/hooks/useIntentHandler.ts
@@ -1,11 +1,11 @@
import React from 'react'
import * as Linking from 'expo-linking'
-import {logEvent} from 'lib/statsig/statsig'
-import {isNative} from 'platform/detection'
-import {useSession} from 'state/session'
-import {useComposerControls} from 'state/shell'
-import {useCloseAllActiveElements} from 'state/util'
+import {logEvent} from '#/lib/statsig/statsig'
+import {isNative} from '#/platform/detection'
+import {useSession} from '#/state/session'
+import {useComposerControls} from '#/state/shell'
+import {useCloseAllActiveElements} from '#/state/util'
import {useIntentDialogs} from '#/components/intents/IntentDialogs'
import {Referrer} from '../../../modules/expo-bluesky-swiss-army'
@@ -52,6 +52,7 @@ export function useIntentHandler() {
composeIntent({
text: params.get('text'),
imageUrisStr: params.get('imageUris'),
+ videoUri: params.get('videoUri'),
})
return
}
@@ -80,14 +81,25 @@ export function useComposeIntent() {
({
text,
imageUrisStr,
+ videoUri,
}: {
text: string | null
- imageUrisStr: string | null // unused for right now, will be used later with intents
+ imageUrisStr: string | null
+ videoUri: string | null
}) => {
if (!hasSession) return
closeAllActiveElements()
+ // Whenever a video URI is present, we don't support adding images right now.
+ if (videoUri) {
+ openComposer({
+ text: text ?? undefined,
+ videoUri,
+ })
+ return
+ }
+
const imageUris = imageUrisStr
?.split(',')
.filter(part => {
diff --git a/src/logger/index.ts b/src/logger/index.ts
index 98635c6a97..7bd812af00 100644
--- a/src/logger/index.ts
+++ b/src/logger/index.ts
@@ -1,10 +1,10 @@
import format from 'date-fns/format'
import {nanoid} from 'nanoid/non-secure'
+import {isNetworkError} from '#/lib/strings/errors'
import {DebugContext} from '#/logger/debugContext'
import {add} from '#/logger/logDump'
import {Sentry} from '#/logger/sentry'
-import {isNetworkError} from 'lib/strings/errors'
import * as env from '#/env'
export enum LogLevel {
diff --git a/src/state/queries/nuxs/definitions.ts b/src/state/queries/nuxs/definitions.ts
index 63a8079623..8166602c8a 100644
--- a/src/state/queries/nuxs/definitions.ts
+++ b/src/state/queries/nuxs/definitions.ts
@@ -3,23 +3,16 @@ import zod from 'zod'
import {BaseNux} from '#/state/queries/nuxs/types'
export enum Nux {
- TenMillionDialog = 'TenMillionDialog',
NeueTypography = 'NeueTypography',
}
export const nuxNames = new Set(Object.values(Nux))
-export type AppNux =
- | BaseNux<{
- id: Nux.TenMillionDialog
- data: undefined
- }>
- | BaseNux<{
- id: Nux.NeueTypography
- data: undefined
- }>
+export type AppNux = BaseNux<{
+ id: Nux.NeueTypography
+ data: undefined
+}>
export const NuxSchemas: Record | undefined> = {
- [Nux.TenMillionDialog]: undefined,
[Nux.NeueTypography]: undefined,
}
diff --git a/src/state/queries/video/video.ts b/src/state/queries/video/video.ts
index 32d02a63cb..0d77935da9 100644
--- a/src/state/queries/video/video.ts
+++ b/src/state/queries/video/video.ts
@@ -7,17 +7,17 @@ import {QueryClient, useQuery, useQueryClient} from '@tanstack/react-query'
import {AbortError} from '#/lib/async/cancelable'
import {SUPPORTED_MIME_TYPES, SupportedMimeTypes} from '#/lib/constants'
-import {logger} from '#/logger'
-import {isWeb} from '#/platform/detection'
import {
ServerError,
UploadLimitError,
VideoTooLargeError,
-} from 'lib/media/video/errors'
-import {CompressedVideo} from 'lib/media/video/types'
-import {useCompressVideoMutation} from 'state/queries/video/compress-video'
-import {useVideoAgent} from 'state/queries/video/util'
-import {useUploadVideoMutation} from 'state/queries/video/video-upload'
+} from '#/lib/media/video/errors'
+import {CompressedVideo} from '#/lib/media/video/types'
+import {logger} from '#/logger'
+import {isWeb} from '#/platform/detection'
+import {useCompressVideoMutation} from '#/state/queries/video/compress-video'
+import {useVideoAgent} from '#/state/queries/video/util'
+import {useUploadVideoMutation} from '#/state/queries/video/video-upload'
type Status = 'idle' | 'compressing' | 'processing' | 'uploading' | 'done'
@@ -101,9 +101,11 @@ function reducer(queryClient: QueryClient) {
export function useUploadVideo({
setStatus,
+ initialVideoUri,
}: {
setStatus: (status: string) => void
onSuccess: () => void
+ initialVideoUri?: string
}) {
const {_} = useLingui()
const queryClient = useQueryClient()
@@ -237,21 +239,24 @@ export function useUploadVideo({
signal: state.abortController.signal,
})
- const selectVideo = (asset: ImagePickerAsset) => {
- // compression step on native converts to mp4, so no need to check there
- if (isWeb) {
- const mimeType = getMimeType(asset)
- if (!SUPPORTED_MIME_TYPES.includes(mimeType as SupportedMimeTypes)) {
- throw new Error(_(msg`Unsupported video type: ${mimeType}`))
+ const selectVideo = React.useCallback(
+ (asset: ImagePickerAsset) => {
+ // compression step on native converts to mp4, so no need to check there
+ if (isWeb) {
+ const mimeType = getMimeType(asset)
+ if (!SUPPORTED_MIME_TYPES.includes(mimeType as SupportedMimeTypes)) {
+ throw new Error(_(msg`Unsupported video type: ${mimeType}`))
+ }
}
- }
- dispatch({
- type: 'SetAsset',
- asset,
- })
- onSelectVideo(asset)
- }
+ dispatch({
+ type: 'SetAsset',
+ asset,
+ })
+ onSelectVideo(asset)
+ },
+ [_, onSelectVideo],
+ )
const clearVideo = () => {
dispatch({type: 'Reset'})
@@ -265,6 +270,13 @@ export function useUploadVideo({
})
}, [])
+ // Whenever we receive an initial video uri, we should immediately run compression if necessary
+ useEffect(() => {
+ if (initialVideoUri) {
+ selectVideo({uri: initialVideoUri} as ImagePickerAsset)
+ }
+ }, [initialVideoUri, selectVideo])
+
return {
state,
dispatch,
diff --git a/src/state/shell/composer/index.tsx b/src/state/shell/composer/index.tsx
index 8e12386bd3..1c3aa6a81d 100644
--- a/src/state/shell/composer/index.tsx
+++ b/src/state/shell/composer/index.tsx
@@ -38,6 +38,7 @@ export interface ComposerOpts {
openEmojiPicker?: (pos: DOMRect | undefined) => void
text?: string
imageUris?: {uri: string; width: number; height: number; altText?: string}[]
+ videoUri?: string
}
type StateContext = ComposerOpts | undefined
diff --git a/src/style.css b/src/style.css
index 980d92ef77..b0b01ba3eb 100644
--- a/src/style.css
+++ b/src/style.css
@@ -9,7 +9,7 @@
@font-face {
font-family: 'Inter-Regular';
src: local('Inter-Regular'),
- url(/assets/fonts/inter/Inter-Regular.otf) format('font/otf');
+ url(/assets/fonts/inter/Inter-Regular.otf) format('opentype');
font-weight: 400;
font-style: normal;
font-display: swap;
@@ -17,7 +17,7 @@
@font-face {
font-family: 'Inter-Italic';
src: local('Inter-Italic'),
- url(/assets/fonts/inter/Inter-Italic.otf) format('font/otf');
+ url(/assets/fonts/inter/Inter-Italic.otf) format('opentype');
font-weight: 400;
font-style: italic;
font-display: swap;
@@ -25,14 +25,14 @@
/*
@font-face {
font-family: "Inter-Medium";
- src: local("Inter-Medium"), url(/assets/fonts/inter/Inter-Medium.otf) format("font/otf");
+ src: local("Inter-Medium"), url(/assets/fonts/inter/Inter-Medium.otf) format("opentype");
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Inter-MediumItalic";
- src: local("Inter-MediumItalic"), url(/assets/fonts/inter/Inter-MediumItalic.otf) format("font/otf");
+ src: local("Inter-MediumItalic"), url(/assets/fonts/inter/Inter-MediumItalic.otf) format("opentype");
font-weight: 500;
font-style: italic;
font-display: swap;
@@ -41,7 +41,7 @@
@font-face {
font-family: 'Inter-SemiBold';
src: local('Inter-SemiBold'),
- url(/assets/fonts/inter/Inter-SemiBold.otf) format('font/otf');
+ url(/assets/fonts/inter/Inter-SemiBold.otf) format('opentype');
font-weight: 600;
font-style: normal;
font-display: swap;
@@ -49,7 +49,7 @@
@font-face {
font-family: 'Inter-SemiBoldItalic';
src: local('Inter-SemiBoldItalic'),
- url(/assets/fonts/inter/Inter-SemiBoldItalic.otf) format('font/otf');
+ url(/assets/fonts/inter/Inter-SemiBoldItalic.otf) format('opentype');
font-weight: 600;
font-style: italic;
font-display: swap;
@@ -57,14 +57,14 @@
/*
@font-face {
font-family: "Inter-Bold";
- src: local("Inter-Bold"), url(/assets/fonts/inter/Inter-Bold.otf) format("font/otf");
+ src: local("Inter-Bold"), url(/assets/fonts/inter/Inter-Bold.otf) format("opentype");
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Inter-BoldItalic";
- src: local("Inter-BoldItalic"), url(/assets/fonts/inter/Inter-BoldItalic.otf) format("font/otf");
+ src: local("Inter-BoldItalic"), url(/assets/fonts/inter/Inter-BoldItalic.otf) format("opentype");
font-weight: 700;
font-style: italic;
font-display: swap;
@@ -73,7 +73,7 @@
@font-face {
font-family: 'Inter-ExtraBold';
src: local('Inter-ExtraBold'),
- url(/assets/fonts/inter/Inter-ExtraBold.otf) format('font/otf');
+ url(/assets/fonts/inter/Inter-ExtraBold.otf) format('opentype');
font-weight: 800;
font-style: normal;
font-display: swap;
@@ -81,7 +81,7 @@
@font-face {
font-family: 'Inter-ExtraBoldItalic';
src: local('Inter-ExtraBoldItalic'),
- url(/assets/fonts/inter/Inter-ExtraBoldItalic.otf) format('font/otf');
+ url(/assets/fonts/inter/Inter-ExtraBoldItalic.otf) format('opentype');
font-weight: 800;
font-style: italic;
font-display: swap;
@@ -89,14 +89,14 @@
/*
@font-face {
font-family: "Inter-Black";
- src: local("Inter-Black"), url(/assets/fonts/inter/Inter-Black.otf) format("font/otf");
+ src: local("Inter-Black"), url(/assets/fonts/inter/Inter-Black.otf) format("opentype");
font-weight: 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Inter-BlackItalic";
- src: local("Inter-BlackItalic"), url(/assets/fonts/inter/Inter-BlackItalic.otf) format("font/otf");
+ src: local("Inter-BlackItalic"), url(/assets/fonts/inter/Inter-BlackItalic.otf) format("opentype");
font-weight: 900;
font-style: italic;
font-display: swap;
diff --git a/src/view/com/auth/SplashScreen.tsx b/src/view/com/auth/SplashScreen.tsx
index a18f17612e..ae18f13905 100644
--- a/src/view/com/auth/SplashScreen.tsx
+++ b/src/view/com/auth/SplashScreen.tsx
@@ -39,21 +39,21 @@ export const SplashScreen = ({
What's up?
-
+
-
-
- {ctx => (
-
- )}
-
- {/* */}
-
+
+
+
{
)}
-
+
-
+
{forceLTR(
sanitizeDisplayName(
displayName,
@@ -92,25 +93,23 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
disableMismatchWarning
disableUnderline
onPress={onBeforePressAuthor}
- style={[a.text_md, t.atoms.text_contrast_medium, a.leading_tight]}>
+ style={[a.text_md, t.atoms.text_contrast_medium, a.leading_snug]}>
+ style={[a.text_md, t.atoms.text_contrast_medium, a.leading_snug]}>
{NON_BREAKING_SPACE + sanitizeHandle(handle, '@')}
-
- ·
-
+ {!isAndroid && (
+
+ ·
+
+ )}
{({timeElapsed}) => (
@@ -124,7 +123,7 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
style={[
a.text_md,
t.atoms.text_contrast_medium,
- a.leading_tight,
+ a.leading_snug,
web({
whiteSpace: 'nowrap',
}),
diff --git a/src/view/com/util/UserBanner.tsx b/src/view/com/util/UserBanner.tsx
index 0e07a57454..13f4081fce 100644
--- a/src/view/com/util/UserBanner.tsx
+++ b/src/view/com/util/UserBanner.tsx
@@ -202,7 +202,7 @@ const styles = StyleSheet.create({
},
bannerImage: {
width: '100%',
- height: '100%',
+ height: 150,
},
defaultBanner: {
backgroundColor: '#0070ff',
diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx
index 36639e7ed7..de46d18c0c 100644
--- a/src/view/screens/Search/Search.tsx
+++ b/src/view/screens/Search/Search.tsx
@@ -985,7 +985,7 @@ let SearchInputBox = ({
const t = useThemeNew()
return (
-
+
diff --git a/src/view/shell/Composer.tsx b/src/view/shell/Composer.tsx
index 049f35d35d..4a07cf9d96 100644
--- a/src/view/shell/Composer.tsx
+++ b/src/view/shell/Composer.tsx
@@ -54,6 +54,7 @@ export function Composer({winHeight}: {winHeight: number}) {
mention={state.mention}
text={state.text}
imageUris={state.imageUris}
+ videoUri={state.videoUri}
/>
)
diff --git a/src/view/shell/bottom-bar/BottomBar.tsx b/src/view/shell/bottom-bar/BottomBar.tsx
index 02d9427330..f0e6ba3395 100644
--- a/src/view/shell/bottom-bar/BottomBar.tsx
+++ b/src/view/shell/bottom-bar/BottomBar.tsx
@@ -355,8 +355,7 @@ function Btn({
router.matchPath(href), [href])
const currentRouteInfo = useNavigationState(state => {
if (!state) {
@@ -183,8 +185,8 @@ function NavItem({count, href, icon, iconFilled, label}: NavItemProps) {
return (
{isCurrent ? iconFilled : icon}
{typeof count === 'string' && count ? (
{count}
) : null}
- {isDesktop && (
-
+ {gtTablet && (
+
{label}
)}
@@ -268,21 +297,20 @@ function ComposeBtn() {
return null
}
return (
-
-
+
-
-
-
-
+ size="large"
+ variant="solid"
+ color="primary"
+ style={[a.rounded_full]}>
+
+
New Post
-
-
+
+
)
}
@@ -440,67 +468,4 @@ const styles = StyleSheet.create({
width: 30,
height: 30,
},
-
- navItemWrapper: {
- flexDirection: 'row',
- alignItems: 'center',
- paddingHorizontal: 12,
- padding: 12,
- borderRadius: 8,
- gap: 10,
- },
- navItemIconWrapper: {
- alignItems: 'center',
- justifyContent: 'center',
- width: 28,
- height: 24,
- marginTop: 2,
- zIndex: 1,
- },
- navItemIconWrapperTablet: {
- width: 40,
- height: 40,
- },
- navItemCount: {
- position: 'absolute',
- top: 0,
- left: 15,
- backgroundColor: colors.blue3,
- color: colors.white,
- fontSize: 12,
- fontWeight: '600',
- paddingHorizontal: 4,
- borderRadius: 6,
- },
- navItemCountTablet: {
- left: 18,
- fontSize: 14,
- },
-
- newPostBtnContainer: {
- flexDirection: 'row',
- },
- newPostBtn: {
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'center',
- borderRadius: 24,
- paddingTop: 10,
- paddingBottom: 12, // visually aligns the text vertically inside the button
- paddingLeft: 16,
- paddingRight: 18, // looks nicer like this
- backgroundColor: colors.blue3,
- marginLeft: 12,
- marginTop: 20,
- marginBottom: 10,
- gap: 8,
- },
- newPostBtnIconWrapper: {
- marginTop: 2, // aligns the icon visually with the text
- },
- newPostBtnLabel: {
- color: colors.white,
- fontSize: 16,
- fontWeight: '600',
- },
})