diff --git a/.env.example b/.env.example index 6ab02256e4..979589f58b 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,6 @@ # Copy this to `.env` and `.env.test` files +BITDRIFT_API_KEY= SENTRY_AUTH_TOKEN= EXPO_PUBLIC_ENV=development EXPO_PUBLIC_LOG_LEVEL=debug diff --git a/app.config.js b/app.config.js index 8b288e1a73..bc283152bd 100644 --- a/app.config.js +++ b/app.config.js @@ -222,6 +222,7 @@ module.exports = function (config) { }, ], 'react-native-compressor', + '@bitdrift/react-native', './plugins/starterPackAppClipExtension/withStarterPackAppClip.js', './plugins/withAndroidManifestPlugin.js', './plugins/withAndroidManifestFCMIconPlugin.js', diff --git a/bskyembed/snippet/embed.ts b/bskyembed/snippet/embed.ts index 380cda5fb9..3c1b14b955 100644 --- a/bskyembed/snippet/embed.ts +++ b/bskyembed/snippet/embed.ts @@ -20,6 +20,7 @@ window.addEventListener('message', event => { return } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const id = (event.data as {id: string}).id if (!id) { return @@ -33,6 +34,7 @@ window.addEventListener('message', event => { return } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const height = (event.data as {height: number}).height if (height) { embed.style.height = `${height}px` @@ -47,7 +49,7 @@ window.addEventListener('message', event => { * @returns */ function scan(node = document) { - const embeds = node.querySelectorAll('[data-bluesky-uri]') + const embeds = node.querySelectorAll('[data-bluesky-uri]') for (let i = 0; i < embeds.length; i++) { const id = String(Math.random()).slice(2) diff --git a/bskyembed/src/color-mode.ts b/bskyembed/src/color-mode.ts new file mode 100644 index 0000000000..2b392c6178 --- /dev/null +++ b/bskyembed/src/color-mode.ts @@ -0,0 +1,17 @@ +export function applyTheme(theme: 'light' | 'dark') { + document.documentElement.classList.remove('light', 'dark') + document.documentElement.classList.add(theme) +} + +export function initColorMode() { + applyTheme( + window.matchMedia('(prefers-color-scheme: dark)').matches + ? 'dark' + : 'light', + ) + window + .matchMedia('(prefers-color-scheme: dark)') + .addEventListener('change', mql => { + applyTheme(mql.matches ? 'dark' : 'light') + }) +} diff --git a/bskyembed/src/components/container.tsx b/bskyembed/src/components/container.tsx index 5b1b2b7fb4..8e142a25be 100644 --- a/bskyembed/src/components/container.tsx +++ b/bskyembed/src/components/container.tsx @@ -37,7 +37,7 @@ export function Container({ return (
{ if (ref.current && href) { // forwardRef requires preact/compat - let's keep it simple diff --git a/bskyembed/src/components/embed.tsx b/bskyembed/src/components/embed.tsx index 74eacf16d4..20ffcb2b29 100644 --- a/bskyembed/src/components/embed.tsx +++ b/bskyembed/src/components/embed.tsx @@ -78,9 +78,9 @@ export function Embed({ return ( + className="transition-colors hover:bg-neutral-100 dark:hover:bg-slate-700 border dark:border-slate-600 rounded-lg p-2 gap-1.5 w-full flex flex-col">
-
+

{record.author.displayName} - + @{record.author.handle}

@@ -209,7 +209,7 @@ function Info({children}: {children: ComponentChildren}) { return (
-

{children}

+

{children}

) } @@ -308,7 +308,7 @@ function ExternalEmbed({ return ( {content.external.thumb && ( )}
-

+

{toNiceDomain(content.external.uri)}

{content.external.title}

-

+

{content.external.description}

@@ -345,23 +345,29 @@ function GenericWithImageEmbed({ return ( + className="w-full rounded-lg border dark:border-slate-600 py-2 px-3 flex flex-col gap-2">
{image ? ( {title} ) : (
)}

{title}

-

{subtitle}

+

+ {subtitle} +

- {description &&

{description}

} + {description && ( +

+ {description} +

+ )} ) } @@ -406,7 +412,7 @@ function StarterPackEmbed({ return ( + className="w-full rounded-lg overflow-hidden border dark:border-slate-600 flex flex-col items-stretch">
@@ -415,7 +421,7 @@ function StarterPackEmbed({

{content.record.name}

-

+

Starter pack by{' '} {content.creator.displayName || `@${content.creator.handle}`}

@@ -425,7 +431,7 @@ function StarterPackEmbed({

{content.record.description}

)} {!!content.joinedAllTimeCount && content.joinedAllTimeCount > 50 && ( -

+

{content.joinedAllTimeCount} users have joined!

)} diff --git a/bskyembed/src/components/post.tsx b/bskyembed/src/components/post.tsx index 4db5eeb45e..26945eb69d 100644 --- a/bskyembed/src/components/post.tsx +++ b/bskyembed/src/components/post.tsx @@ -38,7 +38,7 @@ export function Post({thread}: Props) {
-
+
+ className="text-[15px] text-textLight dark:text-textDimmed hover:underline line-clamp-1">

@{post.author.handle}

@@ -69,15 +69,15 @@ export function Post({thread}: Props) { -
+
{!!post.likeCount && (
-

+

{prettyNumber(post.likeCount)}

@@ -85,17 +85,19 @@ export function Post({thread}: Props) { {!!post.repostCount && (
-

+

{prettyNumber(post.repostCount)}

)}
-

Reply

+

+ Reply +

-

+

{post.replyCount ? `Read ${prettyNumber(post.replyCount)} ${ post.replyCount > 1 ? 'replies' : 'reply' diff --git a/bskyembed/src/index.css b/bskyembed/src/index.css index 22b2b8be5c..289e34cf00 100644 --- a/bskyembed/src/index.css +++ b/bskyembed/src/index.css @@ -5,3 +5,7 @@ .break-word { word-break: break-word; } + +:root { + color-scheme: light dark; +} diff --git a/bskyembed/src/screens/landing.tsx b/bskyembed/src/screens/landing.tsx index a9e08cd3f2..a3448e90ac 100644 --- a/bskyembed/src/screens/landing.tsx +++ b/bskyembed/src/screens/landing.tsx @@ -6,6 +6,7 @@ import {useEffect, useMemo, useRef, useState} from 'preact/hooks' import arrowBottom from '../../assets/arrowBottom_stroke2_corner0_rounded.svg' import logo from '../../assets/logo.svg' +import {initColorMode} from '../color-mode' import {Container} from '../components/container' import {Link} from '../components/link' import {Post} from '../components/post' @@ -21,6 +22,8 @@ export const EMBED_SCRIPT = `${EMBED_SERVICE}/static/embed.js` const root = document.getElementById('app') if (!root) throw new Error('No root element') +initColorMode() + const agent = new BskyAgent({ service: 'https://public.api.bsky.app', }) @@ -108,7 +111,7 @@ function LandingPage() { }, [uri]) return ( -

+
@@ -121,20 +124,22 @@ function LandingPage() { type="text" value={uri} onInput={e => setUri(e.currentTarget.value)} - className="border rounded-lg py-3 w-full max-w-[600px] px-4" + className="border rounded-lg py-3 w-full max-w-[600px] px-4 dark:bg-dimmedBg dark:border-slate-500" placeholder={DEFAULT_POST} /> - + {loading ? ( - +
+ +
) : (
{!error && thread && uri && } {!error && thread && } {error && ( -
+

{error}

)} @@ -149,15 +154,15 @@ function Skeleton() {
-
+
-
-
+
+
-
-
-
+
+
+
) @@ -220,7 +225,7 @@ function Snippet({thread}: {thread: AppBskyFeedDefs.ThreadViewPost}) { ref={ref} type="text" value={snippet} - className="border rounded-lg py-3 w-full px-4" + className="border rounded-lg py-3 w-full px-4 dark:bg-dimmedBg dark:border-slate-500" readOnly autoFocus onFocus={() => { @@ -228,7 +233,7 @@ function Snippet({thread}: {thread: AppBskyFeedDefs.ThreadViewPost}) { }} /> diff --git a/src/lib/bitdrift.ts b/src/lib/bitdrift.ts new file mode 100644 index 0000000000..2b22155e78 --- /dev/null +++ b/src/lib/bitdrift.ts @@ -0,0 +1,7 @@ +import {init} from '@bitdrift/react-native' + +const BITDRIFT_API_KEY = process.env.BITDRIFT_API_KEY + +if (BITDRIFT_API_KEY) { + init(BITDRIFT_API_KEY, {url: 'https://api-bsky.bitdrift.io'}) +} diff --git a/src/logger/bitdriftTransport.ts b/src/logger/bitdriftTransport.ts new file mode 100644 index 0000000000..c2235e0d4b --- /dev/null +++ b/src/logger/bitdriftTransport.ts @@ -0,0 +1,23 @@ +import { + debug as bdDebug, + error as bdError, + info as bdInfo, + warn as bdWarn, +} from '@bitdrift/react-native' + +import {LogLevel, Transport} from './types' + +export function createBitdriftTransport(): Transport { + const logFunctions = { + [LogLevel.Debug]: bdDebug, + [LogLevel.Info]: bdInfo, + [LogLevel.Log]: bdInfo, + [LogLevel.Warn]: bdWarn, + [LogLevel.Error]: bdError, + } as const + + return (level, message) => { + const log = logFunctions[level] + log(message.toString()) + } +} diff --git a/src/logger/bitdriftTransport.web.ts b/src/logger/bitdriftTransport.web.ts new file mode 100644 index 0000000000..ecea3f6f3b --- /dev/null +++ b/src/logger/bitdriftTransport.web.ts @@ -0,0 +1,7 @@ +import {Transport} from './index' + +export function createBitdriftTransport(): Transport { + return (_level, _message) => { + // noop + } +} diff --git a/src/logger/index.ts b/src/logger/index.ts index 7bd812af00..02e5d5f257 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -6,74 +6,12 @@ import {DebugContext} from '#/logger/debugContext' import {add} from '#/logger/logDump' import {Sentry} from '#/logger/sentry' import * as env from '#/env' +import {createBitdriftTransport} from './bitdriftTransport' +import {Metadata} from './types' +import {ConsoleTransportEntry, LogLevel, Transport} from './types' -export enum LogLevel { - Debug = 'debug', - Info = 'info', - Log = 'log', - Warn = 'warn', - Error = 'error', -} - -type Transport = ( - level: LogLevel, - message: string | Error, - metadata: Metadata, - timestamp: number, -) => void - -/** - * A union of some of Sentry's breadcrumb properties as well as Sentry's - * `captureException` parameter, `CaptureContext`. - */ -type Metadata = { - /** - * Applied as Sentry breadcrumb types. Defaults to `default`. - * - * @see https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#breadcrumb-types - */ - type?: - | 'default' - | 'debug' - | 'error' - | 'navigation' - | 'http' - | 'info' - | 'query' - | 'transaction' - | 'ui' - | 'user' - - /** - * Passed through to `Sentry.captureException` - * - * @see https://github.com/getsentry/sentry-javascript/blob/903addf9a1a1534a6cb2ba3143654b918a86f6dd/packages/types/src/misc.ts#L65 - */ - tags?: { - [key: string]: - | number - | string - | boolean - | bigint - | symbol - | null - | undefined - } - - /** - * Any additional data, passed through to Sentry as `extra` param on - * exceptions, or the `data` param on breadcrumbs. - */ - [key: string]: unknown -} & Parameters[1] - -export type ConsoleTransportEntry = { - id: string - timestamp: number - level: LogLevel - message: string | Error - metadata: Metadata -} +export {LogLevel} +export type {ConsoleTransportEntry, Transport} const enabledLogLevels: { [key in LogLevel]: LogLevel[] @@ -328,6 +266,10 @@ export class Logger { */ export const logger = new Logger() +if (!env.IS_TEST) { + logger.addTransport(createBitdriftTransport()) +} + if (env.IS_DEV && !env.IS_TEST) { logger.addTransport(consoleTransport) diff --git a/src/logger/types.ts b/src/logger/types.ts new file mode 100644 index 0000000000..252e7373be --- /dev/null +++ b/src/logger/types.ts @@ -0,0 +1,69 @@ +import type {Sentry} from '#/logger/sentry' + +export enum LogLevel { + Debug = 'debug', + Info = 'info', + Log = 'log', + Warn = 'warn', + Error = 'error', +} + +export type Transport = ( + level: LogLevel, + message: string | Error, + metadata: Metadata, + timestamp: number, +) => void + +/** + * A union of some of Sentry's breadcrumb properties as well as Sentry's + * `captureException` parameter, `CaptureContext`. + */ +export type Metadata = { + /** + * Applied as Sentry breadcrumb types. Defaults to `default`. + * + * @see https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#breadcrumb-types + */ + type?: + | 'default' + | 'debug' + | 'error' + | 'navigation' + | 'http' + | 'info' + | 'query' + | 'transaction' + | 'ui' + | 'user' + + /** + * Passed through to `Sentry.captureException` + * + * @see https://github.com/getsentry/sentry-javascript/blob/903addf9a1a1534a6cb2ba3143654b918a86f6dd/packages/types/src/misc.ts#L65 + */ + tags?: { + [key: string]: + | number + | string + | boolean + | bigint + | symbol + | null + | undefined + } + + /** + * Any additional data, passed through to Sentry as `extra` param on + * exceptions, or the `data` param on breadcrumbs. + */ + [key: string]: unknown +} & Parameters[1] + +export type ConsoleTransportEntry = { + id: string + timestamp: number + level: LogLevel + message: string | Error + metadata: Metadata +} diff --git a/src/screens/Post/PostLikedBy.tsx b/src/screens/Post/PostLikedBy.tsx index d35d332432..6838186900 100644 --- a/src/screens/Post/PostLikedBy.tsx +++ b/src/screens/Post/PostLikedBy.tsx @@ -1,13 +1,12 @@ import React from 'react' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' +import {Plural, Trans} from '@lingui/macro' import {useFocusEffect} from '@react-navigation/native' import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' import {makeRecordUri} from '#/lib/strings/url-helpers' +import {usePostThreadQuery} from '#/state/queries/post-thread' import {useSetMinimalShellMode} from '#/state/shell' import {PostLikedBy as PostLikedByComponent} from '#/view/com/post-thread/PostLikedBy' -import {ViewHeader} from '#/view/com/util/ViewHeader' import * as Layout from '#/components/Layout' type Props = NativeStackScreenProps @@ -15,7 +14,12 @@ export const PostLikedByScreen = ({route}: Props) => { const setMinimalShellMode = useSetMinimalShellMode() const {name, rkey} = route.params const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) - const {_} = useLingui() + const {data: post} = usePostThreadQuery(uri) + + let likeCount + if (post?.thread.type === 'post') { + likeCount = post.thread.post.likeCount + } useFocusEffect( React.useCallback(() => { @@ -25,7 +29,22 @@ export const PostLikedByScreen = ({route}: Props) => { return ( - + + + + {post && ( + <> + + Liked By + + + + + + )} + + + ) diff --git a/src/screens/Post/PostQuotes.tsx b/src/screens/Post/PostQuotes.tsx index 2cd6be8793..24e942abf1 100644 --- a/src/screens/Post/PostQuotes.tsx +++ b/src/screens/Post/PostQuotes.tsx @@ -1,15 +1,12 @@ import React from 'react' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' +import {Plural, Trans} from '@lingui/macro' import {useFocusEffect} from '@react-navigation/native' import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' import {makeRecordUri} from '#/lib/strings/url-helpers' -import {isWeb} from '#/platform/detection' +import {usePostThreadQuery} from '#/state/queries/post-thread' import {useSetMinimalShellMode} from '#/state/shell' import {PostQuotes as PostQuotesComponent} from '#/view/com/post-thread/PostQuotes' -import {ViewHeader} from '#/view/com/util/ViewHeader' -import {CenteredView} from '#/view/com/util/Views' import * as Layout from '#/components/Layout' type Props = NativeStackScreenProps @@ -17,7 +14,12 @@ export const PostQuotesScreen = ({route}: Props) => { const setMinimalShellMode = useSetMinimalShellMode() const {name, rkey} = route.params const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) - const {_} = useLingui() + const {data: post} = usePostThreadQuery(uri) + + let quoteCount + if (post?.thread.type === 'post') { + quoteCount = post.thread.post.quoteCount + } useFocusEffect( React.useCallback(() => { @@ -27,10 +29,27 @@ export const PostQuotesScreen = ({route}: Props) => { return ( - - - - + + + + {post && ( + <> + + Quotes + + + + + + )} + + + + ) } diff --git a/src/screens/Post/PostRepostedBy.tsx b/src/screens/Post/PostRepostedBy.tsx index 304e708081..e2f78f6625 100644 --- a/src/screens/Post/PostRepostedBy.tsx +++ b/src/screens/Post/PostRepostedBy.tsx @@ -1,15 +1,12 @@ import React from 'react' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' +import {Plural, Trans} from '@lingui/macro' import {useFocusEffect} from '@react-navigation/native' import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' import {makeRecordUri} from '#/lib/strings/url-helpers' -import {isWeb} from '#/platform/detection' +import {usePostThreadQuery} from '#/state/queries/post-thread' import {useSetMinimalShellMode} from '#/state/shell' import {PostRepostedBy as PostRepostedByComponent} from '#/view/com/post-thread/PostRepostedBy' -import {ViewHeader} from '#/view/com/util/ViewHeader' -import {CenteredView} from '#/view/com/util/Views' import * as Layout from '#/components/Layout' type Props = NativeStackScreenProps @@ -17,7 +14,12 @@ export const PostRepostedByScreen = ({route}: Props) => { const {name, rkey} = route.params const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey) const setMinimalShellMode = useSetMinimalShellMode() - const {_} = useLingui() + const {data: post} = usePostThreadQuery(uri) + + let quoteCount + if (post?.thread.type === 'post') { + quoteCount = post.thread.post.repostCount + } useFocusEffect( React.useCallback(() => { @@ -27,10 +29,27 @@ export const PostRepostedByScreen = ({route}: Props) => { return ( - - - - + + + + {post && ( + <> + + Reposted By + + + + + + )} + + + + ) } diff --git a/src/screens/Profile/Header/Metrics.tsx b/src/screens/Profile/Header/Metrics.tsx index bd4c4521cd..6fc77142ee 100644 --- a/src/screens/Profile/Header/Metrics.tsx +++ b/src/screens/Profile/Header/Metrics.tsx @@ -30,7 +30,7 @@ export function ProfileHeaderMetrics({ return ( + label={_( + msg`Liked by ${plural(likeCount, { + one: '# user', + other: '# users', + })}`, + )}> {({hovered, focused, pressed}) => ( - + + Liked by{' '} + + )} diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx index 340621398a..b2de785156 100644 --- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx +++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx @@ -244,7 +244,7 @@ let ProfileHeaderStandard = ({ {!isPlaceholderProfile && !isBlockedUser && ( - <> + {descriptionRT && !moderation.ui('profileView').blur ? ( @@ -262,14 +262,14 @@ let ProfileHeaderStandard = ({ {!isMe && !isBlockedUser && shouldShowKnownFollowers(profile.viewer?.knownFollowers) && ( - + )} - + )} +export const ProfileFollowersScreen = ({route}: Props) => { + const {name} = route.params + const setMinimalShellMode = useSetMinimalShellMode() + + const {data: resolvedDid} = useResolveDidQuery(name) + const {data: profile} = useProfileQuery({ + did: resolvedDid, + }) + + useFocusEffect( + React.useCallback(() => { + setMinimalShellMode(false) + }, [setMinimalShellMode]), + ) + + return ( + + + + + {profile && ( + <> + + {sanitizeDisplayName(profile.displayName || profile.handle)} + + + + + + )} + + + + + + ) +} diff --git a/src/screens/Profile/ProfileFollows.tsx b/src/screens/Profile/ProfileFollows.tsx new file mode 100644 index 0000000000..97a05d5cfd --- /dev/null +++ b/src/screens/Profile/ProfileFollows.tsx @@ -0,0 +1,54 @@ +import React from 'react' +import {Plural} from '@lingui/macro' +import {useFocusEffect} from '@react-navigation/native' + +import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' +import {sanitizeDisplayName} from '#/lib/strings/display-names' +import {useProfileQuery} from '#/state/queries/profile' +import {useResolveDidQuery} from '#/state/queries/resolve-uri' +import {useSetMinimalShellMode} from '#/state/shell' +import {ProfileFollows as ProfileFollowsComponent} from '#/view/com/profile/ProfileFollows' +import * as Layout from '#/components/Layout' + +type Props = NativeStackScreenProps +export const ProfileFollowsScreen = ({route}: Props) => { + const {name} = route.params + const setMinimalShellMode = useSetMinimalShellMode() + + const {data: resolvedDid} = useResolveDidQuery(name) + const {data: profile} = useProfileQuery({ + did: resolvedDid, + }) + + useFocusEffect( + React.useCallback(() => { + setMinimalShellMode(false) + }, [setMinimalShellMode]), + ) + + return ( + + + + + {profile && ( + <> + + {sanitizeDisplayName(profile.displayName || profile.handle)} + + + + + + )} + + + + + + ) +} diff --git a/src/screens/Profile/components/ProfileFeedHeader.tsx b/src/screens/Profile/components/ProfileFeedHeader.tsx index 02287334f3..afaa849819 100644 --- a/src/screens/Profile/components/ProfileFeedHeader.tsx +++ b/src/screens/Profile/components/ProfileFeedHeader.tsx @@ -458,11 +458,9 @@ function DialogInner({ to={makeCustomFeedLink(info.creatorDid, feedRkey, 'liked-by')} style={[a.underline, t.atoms.text_contrast_medium]} onPress={() => control.close()}> - + + Liked by + )} diff --git a/src/screens/Settings/AppIconSettings.tsx b/src/screens/Settings/AppIconSettings.tsx deleted file mode 100644 index 18fcd5e305..0000000000 --- a/src/screens/Settings/AppIconSettings.tsx +++ /dev/null @@ -1,260 +0,0 @@ -import React from 'react' -import {Alert, View} from 'react-native' -import {Image} from 'expo-image' -import {msg, Trans} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import * as AppIcon from '@mozzius/expo-dynamic-app-icon' -import {NativeStackScreenProps} from '@react-navigation/native-stack' - -import {PressableScale} from '#/lib/custom-animations/PressableScale' -import {CommonNavigatorParams} from '#/lib/routes/types' -import {isAndroid} from '#/platform/detection' -import {atoms as a, platform} from '#/alf' -import * as Layout from '#/components/Layout' -import {Text} from '#/components/Typography' - -type Props = NativeStackScreenProps -export function AppIconSettingsScreen({}: Props) { - const {_} = useLingui() - const sets = useAppIconSets() - - return ( - - - - - - App Icon - - - - - - Defaults - - {sets.defaults.map(icon => ( - - AppIcon.setAppIcon(icon.id)}> - - - - {icon.name} - - - ))} - - - Bluesky+ - - {sets.core.map(icon => ( - - { - if (isAndroid) { - Alert.alert( - _(msg`Change app icon to "${icon.name}"`), - _(msg`The app will be restarted`), - [ - { - text: _(msg`Cancel`), - style: 'cancel', - }, - { - text: _(msg`OK`), - onPress: () => { - AppIcon.setAppIcon(icon.id) - }, - style: 'default', - }, - ], - ) - } else { - AppIcon.setAppIcon(icon.id) - } - }}> - - - - {icon.name} - - - ))} - - - - ) -} - -function useAppIconSets() { - const {_} = useLingui() - - return React.useMemo(() => { - const defaults = [ - { - id: 'default_light', - name: _('Light'), - iosImage: () => { - return require(`../../../assets/app-icons/ios_icon_default_light.png`) - }, - androidImage: () => { - return require(`../../../assets/app-icons/android_icon_default_light.png`) - }, - }, - { - id: 'default_dark', - name: _('Dark'), - iosImage: () => { - return require(`../../../assets/app-icons/ios_icon_default_dark.png`) - }, - androidImage: () => { - return require(`../../../assets/app-icons/android_icon_default_dark.png`) - }, - }, - ] - - /** - * Bluesky+ - */ - const core = [ - { - id: 'core_aurora', - name: _('Aurora'), - iosImage: () => { - return require(`../../../assets/app-icons/ios_icon_core_aurora.png`) - }, - androidImage: () => { - return require(`../../../assets/app-icons/android_icon_core_aurora.png`) - }, - }, - // { - // id: 'core_bonfire', - // name: _('Bonfire'), - // iosImage: () => { - // return require(`../../../assets/app-icons/ios_icon_core_bonfire.png`) - // }, - // androidImage: () => { - // return require(`../../../assets/app-icons/android_icon_core_bonfire.png`) - // }, - // }, - { - id: 'core_sunrise', - name: _('Sunrise'), - iosImage: () => { - return require(`../../../assets/app-icons/ios_icon_core_sunrise.png`) - }, - androidImage: () => { - return require(`../../../assets/app-icons/android_icon_core_sunrise.png`) - }, - }, - { - id: 'core_sunset', - name: _('Sunset'), - iosImage: () => { - return require(`../../../assets/app-icons/ios_icon_core_sunset.png`) - }, - androidImage: () => { - return require(`../../../assets/app-icons/android_icon_core_sunset.png`) - }, - }, - { - id: 'core_midnight', - name: _('Midnight'), - iosImage: () => { - return require(`../../../assets/app-icons/ios_icon_core_midnight.png`) - }, - androidImage: () => { - return require(`../../../assets/app-icons/android_icon_core_midnight.png`) - }, - }, - { - id: 'core_flat_blue', - name: _('Flat Blue'), - iosImage: () => { - return require(`../../../assets/app-icons/ios_icon_core_flat_blue.png`) - }, - androidImage: () => { - return require(`../../../assets/app-icons/android_icon_core_flat_blue.png`) - }, - }, - { - id: 'core_flat_white', - name: _('Flat White'), - iosImage: () => { - return require(`../../../assets/app-icons/ios_icon_core_flat_white.png`) - }, - androidImage: () => { - return require(`../../../assets/app-icons/android_icon_core_flat_white.png`) - }, - }, - { - id: 'core_flat_black', - name: _('Flat Black'), - iosImage: () => { - return require(`../../../assets/app-icons/ios_icon_core_flat_black.png`) - }, - androidImage: () => { - return require(`../../../assets/app-icons/android_icon_core_flat_black.png`) - }, - }, - { - id: 'core_classic', - name: _('Bluesky Classicâ„¢'), - iosImage: () => { - return require(`../../../assets/app-icons/ios_icon_core_classic.png`) - }, - androidImage: () => { - return require(`../../../assets/app-icons/android_icon_core_classic.png`) - }, - }, - ] - - return { - defaults, - core, - } - }, [_]) -} diff --git a/src/screens/Settings/AppIconSettings/AppIconImage.tsx b/src/screens/Settings/AppIconSettings/AppIconImage.tsx new file mode 100644 index 0000000000..e81d5d0d50 --- /dev/null +++ b/src/screens/Settings/AppIconSettings/AppIconImage.tsx @@ -0,0 +1,33 @@ +import {Image} from 'expo-image' + +import {AppIconSet} from '#/screens/Settings/AppIconSettings/types' +import {atoms as a, platform, useTheme} from '#/alf' + +export function AppIconImage({ + icon, + size = 50, +}: { + icon: AppIconSet + size: number +}) { + const t = useTheme() + return ( + + ) +} diff --git a/src/screens/Settings/AppIconSettings/SettingsListItem.tsx b/src/screens/Settings/AppIconSettings/SettingsListItem.tsx new file mode 100644 index 0000000000..add87b1d7a --- /dev/null +++ b/src/screens/Settings/AppIconSettings/SettingsListItem.tsx @@ -0,0 +1,29 @@ +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {AppIconImage} from '#/screens/Settings/AppIconSettings/AppIconImage' +import {useCurrentAppIcon} from '#/screens/Settings/AppIconSettings/useCurrentAppIcon' +import * as SettingsList from '#/screens/Settings/components/SettingsList' +import {atoms as a} from '#/alf' +import {Shapes_Stroke2_Corner0_Rounded as Shapes} from '#/components/icons/Shapes' + +export function SettingsListItem() { + const {_} = useLingui() + const icon = useCurrentAppIcon() + + return ( + + + + + App Icon + + + + + ) +} diff --git a/src/screens/Settings/AppIconSettings/SettingsListItem.web.tsx b/src/screens/Settings/AppIconSettings/SettingsListItem.web.tsx new file mode 100644 index 0000000000..c7707d23fd --- /dev/null +++ b/src/screens/Settings/AppIconSettings/SettingsListItem.web.tsx @@ -0,0 +1 @@ +export function SettingsListItem() {} diff --git a/src/screens/Settings/AppIconSettings/index.tsx b/src/screens/Settings/AppIconSettings/index.tsx new file mode 100644 index 0000000000..0fefca29b2 --- /dev/null +++ b/src/screens/Settings/AppIconSettings/index.tsx @@ -0,0 +1,244 @@ +import {useState} from 'react' +import {Alert, View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import * as DynamicAppIcon from '@mozzius/expo-dynamic-app-icon' +import {NativeStackScreenProps} from '@react-navigation/native-stack' + +import {DISCOVER_DEBUG_DIDS} from '#/lib/constants' +import {PressableScale} from '#/lib/custom-animations/PressableScale' +import {CommonNavigatorParams} from '#/lib/routes/types' +import {isAndroid} from '#/platform/detection' +import {useSession} from '#/state/session' +import {AppIconImage} from '#/screens/Settings/AppIconSettings/AppIconImage' +import {AppIconSet} from '#/screens/Settings/AppIconSettings/types' +import {useAppIconSets} from '#/screens/Settings/AppIconSettings/useAppIconSets' +import {atoms as a, useTheme} from '#/alf' +import * as Toggle from '#/components/forms/Toggle' +import * as Layout from '#/components/Layout' +import {Text} from '#/components/Typography' + +type Props = NativeStackScreenProps +export function AppIconSettingsScreen({}: Props) { + const t = useTheme() + const {_} = useLingui() + const sets = useAppIconSets() + const {currentAccount} = useSession() + const [currentAppIcon, setCurrentAppIcon] = useState(() => + getAppIconName(DynamicAppIcon.getAppIcon()), + ) + + const onSetAppIcon = (icon: string) => { + if (isAndroid) { + const next = + sets.defaults.find(i => i.id === icon) ?? + sets.core.find(i => i.id === icon) + Alert.alert( + next + ? _(msg`Change app icon to "${next.name}"`) + : _(msg`Change app icon`), + // to determine - can we stop this happening? -sfn + _(msg`The app will be restarted`), + [ + { + text: _(msg`Cancel`), + style: 'cancel', + }, + { + text: _(msg`OK`), + onPress: () => { + setCurrentAppIcon(setAppIcon(icon)) + }, + style: 'default', + }, + ], + ) + } else { + setCurrentAppIcon(setAppIcon(icon)) + } + } + + return ( + + + + + + App Icon + + + + + + + + {sets.defaults.map((icon, i) => ( + + + {icon.name} + + ))} + + + {DISCOVER_DEBUG_DIDS[currentAccount?.did ?? ''] && ( + <> + + Bluesky+ + + + {sets.core.map((icon, i) => ( + + + {icon.name} + + ))} + + + )} + + + ) +} + +function setAppIcon(icon: string) { + if (icon === 'default_light') { + return getAppIconName(DynamicAppIcon.setAppIcon(null)) + } else { + return getAppIconName(DynamicAppIcon.setAppIcon(icon)) + } +} + +function getAppIconName(icon: string | false) { + if (!icon || icon === 'DEFAULT') { + return 'default_light' + } else { + return icon + } +} + +function Group({ + children, + label, + value, + onChange, +}: { + children: React.ReactNode + label: string + value: string + onChange: (value: string) => void +}) { + return ( + { + if (vals[0]) onChange(vals[0]) + }}> + + {children} + + + ) +} + +function Row({ + icon, + children, + isEnd, +}: { + icon: AppIconSet + children: React.ReactNode + isEnd: boolean +}) { + const t = useTheme() + const {_} = useLingui() + + return ( + + {({hovered, pressed}) => ( + + {children} + + + )} + + ) +} + +function RowText({children}: {children: React.ReactNode}) { + const t = useTheme() + return ( + + {children} + + ) +} + +function AppIcon({icon, size = 50}: {icon: AppIconSet; size: number}) { + const {_} = useLingui() + return ( + { + if (isAndroid) { + Alert.alert( + _(msg`Change app icon to "${icon.name}"`), + _(msg`The app will be restarted`), + [ + { + text: _(msg`Cancel`), + style: 'cancel', + }, + { + text: _(msg`OK`), + onPress: () => { + DynamicAppIcon.setAppIcon(icon.id) + }, + style: 'default', + }, + ], + ) + } else { + DynamicAppIcon.setAppIcon(icon.id) + } + }}> + + + ) +} diff --git a/src/screens/Settings/AppIconSettings.web.tsx b/src/screens/Settings/AppIconSettings/index.web.tsx similarity index 100% rename from src/screens/Settings/AppIconSettings.web.tsx rename to src/screens/Settings/AppIconSettings/index.web.tsx diff --git a/src/screens/Settings/AppIconSettings/types.ts b/src/screens/Settings/AppIconSettings/types.ts new file mode 100644 index 0000000000..5010f6f025 --- /dev/null +++ b/src/screens/Settings/AppIconSettings/types.ts @@ -0,0 +1,8 @@ +import {ImageSourcePropType} from 'react-native' + +export type AppIconSet = { + id: string + name: string + iosImage: () => ImageSourcePropType + androidImage: () => ImageSourcePropType +} diff --git a/src/screens/Settings/AppIconSettings/useAppIconSets.ts b/src/screens/Settings/AppIconSettings/useAppIconSets.ts new file mode 100644 index 0000000000..47fc5a15f0 --- /dev/null +++ b/src/screens/Settings/AppIconSettings/useAppIconSets.ts @@ -0,0 +1,134 @@ +import {useMemo} from 'react' +import {useLingui} from '@lingui/react' + +import {AppIconSet} from '#/screens/Settings/AppIconSettings/types' + +export function useAppIconSets() { + const {_} = useLingui() + + return useMemo(() => { + const defaults = [ + { + id: 'default_light', + name: _('Light'), + iosImage: () => { + return require(`../../../../assets/app-icons/ios_icon_default_light.png`) + }, + androidImage: () => { + return require(`../../../../assets/app-icons/android_icon_default_light.png`) + }, + }, + { + id: 'default_dark', + name: _('Dark'), + iosImage: () => { + return require(`../../../../assets/app-icons/ios_icon_default_dark.png`) + }, + androidImage: () => { + return require(`../../../../assets/app-icons/android_icon_default_dark.png`) + }, + }, + ] satisfies AppIconSet[] + + /** + * Bluesky+ + */ + const core = [ + { + id: 'core_aurora', + name: _('Aurora'), + iosImage: () => { + return require(`../../../../assets/app-icons/ios_icon_core_aurora.png`) + }, + androidImage: () => { + return require(`../../../../assets/app-icons/android_icon_core_aurora.png`) + }, + }, + // { + // id: 'core_bonfire', + // name: _('Bonfire'), + // iosImage: () => { + // return require(`../../../../assets/app-icons/ios_icon_core_bonfire.png`) + // }, + // androidImage: () => { + // return require(`../../../../assets/app-icons/android_icon_core_bonfire.png`) + // }, + // }, + { + id: 'core_sunrise', + name: _('Sunrise'), + iosImage: () => { + return require(`../../../../assets/app-icons/ios_icon_core_sunrise.png`) + }, + androidImage: () => { + return require(`../../../../assets/app-icons/android_icon_core_sunrise.png`) + }, + }, + { + id: 'core_sunset', + name: _('Sunset'), + iosImage: () => { + return require(`../../../../assets/app-icons/ios_icon_core_sunset.png`) + }, + androidImage: () => { + return require(`../../../../assets/app-icons/android_icon_core_sunset.png`) + }, + }, + { + id: 'core_midnight', + name: _('Midnight'), + iosImage: () => { + return require(`../../../../assets/app-icons/ios_icon_core_midnight.png`) + }, + androidImage: () => { + return require(`../../../../assets/app-icons/android_icon_core_midnight.png`) + }, + }, + { + id: 'core_flat_blue', + name: _('Flat Blue'), + iosImage: () => { + return require(`../../../../assets/app-icons/ios_icon_core_flat_blue.png`) + }, + androidImage: () => { + return require(`../../../../assets/app-icons/android_icon_core_flat_blue.png`) + }, + }, + { + id: 'core_flat_white', + name: _('Flat White'), + iosImage: () => { + return require(`../../../../assets/app-icons/ios_icon_core_flat_white.png`) + }, + androidImage: () => { + return require(`../../../../assets/app-icons/android_icon_core_flat_white.png`) + }, + }, + { + id: 'core_flat_black', + name: _('Flat Black'), + iosImage: () => { + return require(`../../../../assets/app-icons/ios_icon_core_flat_black.png`) + }, + androidImage: () => { + return require(`../../../../assets/app-icons/android_icon_core_flat_black.png`) + }, + }, + { + id: 'core_classic', + name: _('Bluesky Classicâ„¢'), + iosImage: () => { + return require(`../../../../assets/app-icons/ios_icon_core_classic.png`) + }, + androidImage: () => { + return require(`../../../../assets/app-icons/android_icon_core_classic.png`) + }, + }, + ] satisfies AppIconSet[] + + return { + defaults, + core, + } + }, [_]) +} diff --git a/src/screens/Settings/AppIconSettings/useCurrentAppIcon.ts b/src/screens/Settings/AppIconSettings/useCurrentAppIcon.ts new file mode 100644 index 0000000000..4bc9b665a4 --- /dev/null +++ b/src/screens/Settings/AppIconSettings/useCurrentAppIcon.ts @@ -0,0 +1,27 @@ +import {useCallback, useMemo, useState} from 'react' +import * as DynamicAppIcon from '@mozzius/expo-dynamic-app-icon' +import {useFocusEffect} from '@react-navigation/native' + +import {useAppIconSets} from '#/screens/Settings/AppIconSettings/useAppIconSets' + +export function useCurrentAppIcon() { + const appIconSets = useAppIconSets() + const [currentAppIcon, setCurrentAppIcon] = useState(() => + DynamicAppIcon.getAppIcon(), + ) + + // refresh current icon when screen is focused + useFocusEffect( + useCallback(() => { + setCurrentAppIcon(DynamicAppIcon.getAppIcon()) + }, []), + ) + + return useMemo(() => { + return ( + appIconSets.defaults.find(i => i.id === currentAppIcon) ?? + appIconSets.core.find(i => i.id === currentAppIcon) ?? + appIconSets.defaults[0] + ) + }, [appIconSets, currentAppIcon]) +} diff --git a/src/screens/Settings/AppearanceSettings.tsx b/src/screens/Settings/AppearanceSettings.tsx index 48c4a2d85d..81ac591051 100644 --- a/src/screens/Settings/AppearanceSettings.tsx +++ b/src/screens/Settings/AppearanceSettings.tsx @@ -13,7 +13,7 @@ import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' import {isNative} from '#/platform/detection' import {useSession} from '#/state/session' import {useSetThemePrefs, useThemePrefs} from '#/state/shell' -import {Logo} from '#/view/icons/Logo' +import {SettingsListItem as AppIconSettingsListItem} from '#/screens/Settings/AppIconSettings/SettingsListItem' import {atoms as a, native, useAlf, useTheme} from '#/alf' import * as ToggleButton from '#/components/forms/ToggleButton' import {Props as SVGIconProps} from '#/components/icons/common' @@ -181,15 +181,7 @@ export function AppearanceSettingsScreen({}: Props) { {isNative && DISCOVER_DEBUG_DIDS[currentAccount?.did ?? ''] && ( <> - - - - - App Icon - - + )} diff --git a/src/view/com/feeds/FeedSourceCard.tsx b/src/view/com/feeds/FeedSourceCard.tsx index 707aad7fb0..a591488891 100644 --- a/src/view/com/feeds/FeedSourceCard.tsx +++ b/src/view/com/feeds/FeedSourceCard.tsx @@ -300,11 +300,14 @@ export function FeedSourceCardLoaded({ {showLikes && feed.type === 'feed' ? ( - + + Liked by{' '} + + ) : null} diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx index d8c2ba1183..477d77affb 100644 --- a/src/view/com/post-thread/PostThread.tsx +++ b/src/view/com/post-thread/PostThread.tsx @@ -421,9 +421,6 @@ export function PostThread({uri}: {uri: string | undefined}) { ) } else if (isThreadPost(item)) { - if (!treeView && item.ctx.hasMoreSelfThread) { - return - } const prev = isThreadPost(posts[index - 1]) ? (posts[index - 1] as ThreadPost) : undefined @@ -436,6 +433,10 @@ export function PostThread({uri}: {uri: string | undefined}) { const hasUnrevealedParents = index === 0 && skeleton?.parents && maxParents < skeleton.parents.length + if (!treeView && prev && item.ctx.hasMoreSelfThread) { + return + } + return ( { 'worklet' - headerMode.set(v ? V1.get() : V0.get()) + headerMode.set(() => + withSpring(v ? 1 : 0, { + overshootClamping: true, + }), + ) }, [headerMode], ) diff --git a/src/view/com/util/numeric/__tests__/format-test.ts b/src/view/com/util/numeric/__tests__/format-test.ts deleted file mode 100644 index 74df4be4c9..0000000000 --- a/src/view/com/util/numeric/__tests__/format-test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import {describe, expect, it} from '@jest/globals' - -import {APP_LANGUAGES} from '#/locale/languages' -import {formatCount} from '../format' - -const formatCountRound = (locale: string, num: number) => { - const options: Intl.NumberFormatOptions = { - notation: 'compact', - maximumFractionDigits: 1, - } - return new Intl.NumberFormat(locale, options).format(num) -} - -const formatCountTrunc = (locale: string, num: number) => { - const options: Intl.NumberFormatOptions = { - notation: 'compact', - maximumFractionDigits: 1, - // @ts-ignore - roundingMode: 'trunc', - } - return new Intl.NumberFormat(locale, options).format(num) -} - -// prettier-ignore -const testNums = [ - 1, - 5, - 9, - 11, - 55, - 99, - 111, - 555, - 999, - 1111, - 5555, - 9999, - 11111, - 55555, - 99999, - 111111, - 555555, - 999999, - 1111111, - 5555555, - 9999999, - 11111111, - 55555555, - 99999999, - 111111111, - 555555555, - 999999999, - 1111111111, - 5555555555, - 9999999999, - 11111111111, - 55555555555, - 99999999999, - 111111111111, - 555555555555, - 999999999999, - 1111111111111, - 5555555555555, - 9999999999999, - 11111111111111, - 55555555555555, - 99999999999999, - 111111111111111, - 555555555555555, - 999999999999999, - 1111111111111111, - 5555555555555555, -] - -describe('formatCount', () => { - for (const appLanguage of APP_LANGUAGES) { - const locale = appLanguage.code2 - it('truncates for ' + locale, () => { - const mockI8nn = { - locale, - number(num: number) { - return formatCountRound(locale, num) - }, - } - for (const num of testNums) { - const formatManual = formatCount(mockI8nn as any, num) - const formatOriginal = formatCountTrunc(locale, num) - expect(formatManual).toEqual(formatOriginal) - } - }) - } -}) diff --git a/src/view/com/util/numeric/format.ts b/src/view/com/util/numeric/format.ts index 053b0069b5..8f3ebd0e71 100644 --- a/src/view/com/util/numeric/format.ts +++ b/src/view/com/util/numeric/format.ts @@ -1,50 +1,10 @@ import {I18n} from '@lingui/core' -const truncateRounding = (num: number, factors: Array): number => { - for (let i = factors.length - 1; i >= 0; i--) { - let factor = factors[i] - if (num >= 10 ** factor) { - if (factor === 10) { - // CA and ES abruptly jump from "9999,9 M" to "10 mil M" - factor-- - } - const precision = 1 - const divisor = 10 ** (factor - precision) - return Math.floor(num / divisor) * divisor - } - } - return num -} - -const koFactors = [3, 4, 8, 12] -const hiFactors = [3, 5, 7, 9, 11, 13] -const esCaFactors = [3, 6, 10, 12] -const itDeFactors = [6, 9, 12] -const jaZhFactors = [4, 8, 12] -const glFactors = [6, 12] -const restFactors = [3, 6, 9, 12] - export const formatCount = (i18n: I18n, num: number) => { - const locale = i18n.locale - let truncatedNum: number - if (locale === 'hi') { - truncatedNum = truncateRounding(num, hiFactors) - } else if (locale === 'ko') { - truncatedNum = truncateRounding(num, koFactors) - } else if (locale === 'es' || locale === 'ca') { - truncatedNum = truncateRounding(num, esCaFactors) - } else if (locale === 'ja' || locale === 'zh-CN' || locale === 'zh-TW') { - truncatedNum = truncateRounding(num, jaZhFactors) - } else if (locale === 'it' || locale === 'de') { - truncatedNum = truncateRounding(num, itDeFactors) - } else if (locale === 'gl') { - truncatedNum = truncateRounding(num, glFactors) - } else { - truncatedNum = truncateRounding(num, restFactors) - } - return i18n.number(truncatedNum, { + return i18n.number(num, { notation: 'compact', maximumFractionDigits: 1, - // Ideally we'd use roundingMode: 'trunc' but it isn't supported on RN. + // @ts-expect-error - roundingMode not in the types + roundingMode: 'trunc', }) } diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index deb4b51d82..39caaf0987 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -258,10 +258,12 @@ let PostCtrls = ({ } }} accessibilityRole="button" - accessibilityLabel={plural(post.replyCount || 0, { - one: 'Reply (# reply)', - other: 'Reply (# replies)', - })} + accessibilityLabel={_( + msg`Reply (${plural(post.replyCount || 0, { + one: '# reply', + other: '# replies', + })})`, + )} accessibilityHint="" hitSlop={POST_CTRL_HITSLOP}> diff --git a/src/view/com/util/post-ctrls/RepostButton.tsx b/src/view/com/util/post-ctrls/RepostButton.tsx index 06b1fcaf6b..ca1647a991 100644 --- a/src/view/com/util/post-ctrls/RepostButton.tsx +++ b/src/view/com/util/post-ctrls/RepostButton.tsx @@ -62,11 +62,21 @@ let RepostButton = ({ {padding: 5}, ]} hoverStyle={t.atoms.bg_contrast_25} - label={`${ + label={ isReposted - ? _(msg`Undo repost`) - : _(msg({message: 'Repost', context: 'action'})) - } (${plural(repostCount || 0, {one: '# repost', other: '# reposts'})})`} + ? _( + msg`Undo repost (${plural(repostCount || 0, { + one: '# repost', + other: '# reposts', + })})`, + ) + : _( + msg`Repost (${plural(repostCount || 0, { + one: '# repost', + other: '# reposts', + })})`, + ) + } shape="round" variant="ghost" color="secondary" diff --git a/src/view/screens/ProfileFollowers.tsx b/src/view/screens/ProfileFollowers.tsx deleted file mode 100644 index 90c0a57f97..0000000000 --- a/src/view/screens/ProfileFollowers.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useFocusEffect} from '@react-navigation/native' - -import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' -import {isWeb} from '#/platform/detection' -import {useSetMinimalShellMode} from '#/state/shell' -import {ProfileFollowers as ProfileFollowersComponent} from '#/view/com/profile/ProfileFollowers' -import {ViewHeader} from '#/view/com/util/ViewHeader' -import {CenteredView} from '#/view/com/util/Views' -import * as Layout from '#/components/Layout' - -type Props = NativeStackScreenProps -export const ProfileFollowersScreen = ({route}: Props) => { - const {name} = route.params - const setMinimalShellMode = useSetMinimalShellMode() - const {_} = useLingui() - - useFocusEffect( - React.useCallback(() => { - setMinimalShellMode(false) - }, [setMinimalShellMode]), - ) - - return ( - - - - - - - ) -} diff --git a/src/view/screens/ProfileFollows.tsx b/src/view/screens/ProfileFollows.tsx deleted file mode 100644 index 134f799937..0000000000 --- a/src/view/screens/ProfileFollows.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useFocusEffect} from '@react-navigation/native' - -import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' -import {isWeb} from '#/platform/detection' -import {useSetMinimalShellMode} from '#/state/shell' -import {ProfileFollows as ProfileFollowsComponent} from '#/view/com/profile/ProfileFollows' -import {ViewHeader} from '#/view/com/util/ViewHeader' -import {CenteredView} from '#/view/com/util/Views' -import * as Layout from '#/components/Layout' - -type Props = NativeStackScreenProps -export const ProfileFollowsScreen = ({route}: Props) => { - const {name} = route.params - const setMinimalShellMode = useSetMinimalShellMode() - const {_} = useLingui() - - useFocusEffect( - React.useCallback(() => { - setMinimalShellMode(false) - }, [setMinimalShellMode]), - ) - - return ( - - - - - - - ) -} diff --git a/yarn.lock b/yarn.lock index beea8f136b..16fe9579a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3377,6 +3377,11 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@bitdrift/react-native@0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@bitdrift/react-native/-/react-native-0.4.0.tgz#e6484343ef04824aa924df2a757bd9620b2106c1" + integrity sha512-KuYzWEkoGwjjP0ZurjHwV+zfRZjQXxbXa3zhijWv0iqzMI/7kbrBd9lm+wNQo8OrkqFVDlebCb8AGPc0jMZw7A== + "@braintree/sanitize-url@^6.0.2": version "6.0.4" resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz#923ca57e173c6b232bbbb07347b1be982f03e783" @@ -4250,62 +4255,74 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== -"@formatjs/ecma402-abstract@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz#39197ab90b1c78b7342b129a56a7acdb8f512e17" - integrity sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g== +"@formatjs/ecma402-abstract@2.3.1": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.1.tgz#cdeb3ffe1aeea9c4284b85b7e37e8e8615314c39" + integrity sha512-Ip9uV+/MpLXWRk03U/GzeJMuPeOXpJBSB5V1tjA6kJhvqssye5J5LoYLc7Z5IAHb7nR62sRoguzrFiVCP/hnzw== dependencies: - "@formatjs/intl-localematcher" "0.5.4" - tslib "^2.4.0" + "@formatjs/fast-memoize" "2.2.5" + "@formatjs/intl-localematcher" "0.5.9" + decimal.js "10" + tslib "2" -"@formatjs/intl-enumerator@1.4.7": - version "1.4.7" - resolved "https://registry.yarnpkg.com/@formatjs/intl-enumerator/-/intl-enumerator-1.4.7.tgz#6ab697f3f8f18cf0cc6a6b028cb9c40db6001f3d" - integrity sha512-03RHnFqfpB4H/jwCwlzC+wkTDk2Fi24JmVIY2PVGvTUpikN2bSr9+8oTXfOC+y7B7VxjCArUnqWXVoctkmy85w== +"@formatjs/fast-memoize@2.2.5": + version "2.2.5" + resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.5.tgz#54a4a1793d773b72c372d3dcab3595149aee7880" + integrity sha512-6PoewUMrrcqxSoBXAOJDiW1m+AmkrAj0RiXnOMD59GRaswjXhm3MDhgepXPBgonc09oSirAJTsAggzAGQf6A6g== dependencies: - tslib "^2.4.0" + tslib "2" -"@formatjs/intl-getcanonicallocales@2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@formatjs/intl-getcanonicallocales/-/intl-getcanonicallocales-2.3.0.tgz#b6c6fa1c664e30a61f27fa6399a76159d82a5842" - integrity sha512-BOXbLwqQ7nKua/l7tKqDLRN84WupDXFDhGJQMFvsMVA2dKuOdRaWTxWpL3cJ7qPkoNw11Jf+Xpj4OSPBBvW0eQ== +"@formatjs/intl-enumerator@1.8.7": + version "1.8.7" + resolved "https://registry.yarnpkg.com/@formatjs/intl-enumerator/-/intl-enumerator-1.8.7.tgz#3f004753333f80cc468ae34046bd8416772a0412" + integrity sha512-qd7UlWUivKRJ073btssUqMSqzWW9yN3Ki6EqfCZ6uvIv19mONelE5q3GMmdPWBEjgqZikBzBE2qPTqfrgJ4TCA== dependencies: - tslib "^2.4.0" + "@formatjs/ecma402-abstract" "2.3.1" + tslib "2" -"@formatjs/intl-locale@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@formatjs/intl-locale/-/intl-locale-4.0.0.tgz#c111a33078413eba2011e82140466261eb1d67cd" - integrity sha512-+4dbMEGsp1bvB3JB3UHH6YTjMnFTifnfdaHp4ROrCCu50NedA69RBsDCG3eivcZkbj57X9ehGhMWjLxlP+gyVw== +"@formatjs/intl-getcanonicallocales@2.5.4": + version "2.5.4" + resolved "https://registry.yarnpkg.com/@formatjs/intl-getcanonicallocales/-/intl-getcanonicallocales-2.5.4.tgz#9b843e1891dea83405c51eb3d00c42ef9cb6cab9" + integrity sha512-vSDOsAcc3U+Kl/0b3de8wCQkb3W30H8LUuslyz67wTAHOPSQhPimZyquhwxXpJR+K5yy9CkzTgk5YE5kFT+PFg== dependencies: - "@formatjs/ecma402-abstract" "2.0.0" - "@formatjs/intl-enumerator" "1.4.7" - "@formatjs/intl-getcanonicallocales" "2.3.0" - tslib "^2.4.0" + tslib "2" -"@formatjs/intl-localematcher@0.5.4": - version "0.5.4" - resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz#caa71f2e40d93e37d58be35cfffe57865f2b366f" - integrity sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g== +"@formatjs/intl-locale@^4.2.8": + version "4.2.8" + resolved "https://registry.yarnpkg.com/@formatjs/intl-locale/-/intl-locale-4.2.8.tgz#571d44e92b6eb43b7410b37f25e280ec384a32cf" + integrity sha512-6RY/npeA0kyoZ8QW0JRAT+VBAFBT6+4ZVeGkKCNIDjbLX2LPuU73emGR35Mbwcc6pquVFrxyo6mXxKNzib0kEA== dependencies: - tslib "^2.4.0" + "@formatjs/ecma402-abstract" "2.3.1" + "@formatjs/intl-enumerator" "1.8.7" + "@formatjs/intl-getcanonicallocales" "2.5.4" + tslib "2" -"@formatjs/intl-numberformat@^8.10.3": - version "8.10.3" - resolved "https://registry.yarnpkg.com/@formatjs/intl-numberformat/-/intl-numberformat-8.10.3.tgz#abc97cc6a7b7f1b20da9f07a976b5589c1192ab8" - integrity sha512-lH3liLMeIjZ19Zxt8RRPnBcpPweS1YNSXRURDiFfvFmRlDZUOd8+GlcVyECcPZPkIoSH/p4lfGrnaUzepxJ92g== +"@formatjs/intl-localematcher@0.5.9": + version "0.5.9" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.9.tgz#43c6ee22be85b83340bcb09bdfed53657a2720db" + integrity sha512-8zkGu/sv5euxbjfZ/xmklqLyDGQSxsLqg8XOq88JW3cmJtzhCP8EtSJXlaKZnVO4beEaoiT9wj4eIoCQ9smwxA== dependencies: - "@formatjs/ecma402-abstract" "2.0.0" - "@formatjs/intl-localematcher" "0.5.4" - tslib "^2.4.0" + tslib "2" -"@formatjs/intl-pluralrules@^5.2.14": - version "5.2.14" - resolved "https://registry.yarnpkg.com/@formatjs/intl-pluralrules/-/intl-pluralrules-5.2.14.tgz#7477bd2aa9bfde9e543d839707eff5460eb08026" - integrity sha512-l6Ev7aOGXJSh5EPDEqzsbyufdCCKXZk993QXRQebLsB0TXRhIyF4alqjdMEatLwIigK/Mka8kiVIOLeFP5Cj9Q== +"@formatjs/intl-numberformat@^8.15.1": + version "8.15.1" + resolved "https://registry.yarnpkg.com/@formatjs/intl-numberformat/-/intl-numberformat-8.15.1.tgz#b2a5b00889ed31dbef9d4e5aeee1dea3d040b068" + integrity sha512-NIouSY50xpH/SMJrRbX1Q3hMsGyQmT5MQrta/bOYhpZda1bztOlEYZAKLytk8VGs10wkGz875602mCMhtg4/LA== dependencies: - "@formatjs/ecma402-abstract" "2.0.0" - "@formatjs/intl-localematcher" "0.5.4" - tslib "^2.4.0" + "@formatjs/ecma402-abstract" "2.3.1" + "@formatjs/intl-localematcher" "0.5.9" + decimal.js "10" + tslib "2" + +"@formatjs/intl-pluralrules@^5.4.1": + version "5.4.1" + resolved "https://registry.yarnpkg.com/@formatjs/intl-pluralrules/-/intl-pluralrules-5.4.1.tgz#1c03cd2da449e1871bb7c54ea36fec1de68b7e7e" + integrity sha512-kKK4ixTsfKAzyJIVRiJGuw4zd18nEHXiKloYBO9VmLpxrwJTgLQHv2+1hcbxQcwbbo2uc8moUFQuyvxeGEFOfw== + dependencies: + "@formatjs/ecma402-abstract" "2.3.1" + "@formatjs/intl-localematcher" "0.5.9" + decimal.js "10" + tslib "2" "@fortawesome/fontawesome-common-types@6.4.2": version "6.4.2" @@ -9372,7 +9389,7 @@ decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== -decimal.js@^10.4.2: +decimal.js@10, decimal.js@^10.4.2: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== @@ -16035,10 +16052,10 @@ react-native-qrcode-styled@^0.3.3: qrcode "^1.5.4" react-fast-compare "^3.2.2" -react-native-reanimated@^3.16.3: - version "3.16.3" - resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-3.16.3.tgz#3b559dca49e9e40abcf5de834dc27fc05f856b66" - integrity sha512-OWlA6e1oHhytTpc7WiSZ7Tmb8OYwLKYZz29Sz6d6WAg60Hm5GuAiKIWUG7Ako7FLcYhFkA0pEQ2xPMEYUo9vlw== +react-native-reanimated@3.17.0-nightly-20241211-17e89ca24: + version "3.17.0-nightly-20241211-17e89ca24" + resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-3.17.0-nightly-20241211-17e89ca24.tgz#af0c36e278646eb2f79e28ad0047cfd80d0e29f5" + integrity sha512-5p7jr0DrnID1puOzMel3VZVRw5Hl/UdMUvPCI1sEG9IA2mUaWrgeoojS2wVwW1U0Pj6HXjPNEimDSXZneZKNuQ== dependencies: "@babel/plugin-transform-arrow-functions" "^7.0.0-0" "@babel/plugin-transform-class-properties" "^7.0.0-0" @@ -18105,6 +18122,11 @@ ts-node@^10.9.1: v8-compile-cache-lib "^3.0.1" yn "3.1.1" +tslib@2: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"