diff --git a/src/alf/typography.tsx b/src/alf/typography.tsx index d79ad81ea..a5e86a4e6 100644 --- a/src/alf/typography.tsx +++ b/src/alf/typography.tsx @@ -1,12 +1,14 @@ import {Children} from 'react' -import {type TextProps as RNTextProps} from 'react-native' -import {type StyleProp, type TextStyle} from 'react-native' +import { + type StyleProp, + type TextProps as RNTextProps, + type TextStyle, +} from 'react-native' import {UITextView} from 'react-native-uitextview' import createEmojiRegex from 'emoji-regex' import {type Alf, applyFonts, atoms, flatten} from '#/alf' -import {IS_NATIVE} from '#/env' -import {IS_IOS} from '#/env' +import {IS_IOS, IS_NATIVE} from '#/env' /** * Ensures that `lineHeight` defaults to a relative value of `1`, or applies diff --git a/src/components/Layout/index.tsx b/src/components/Layout/index.tsx index ae01c05b9..a5083c37c 100644 --- a/src/components/Layout/index.tsx +++ b/src/components/Layout/index.tsx @@ -1,6 +1,11 @@ import {forwardRef, memo, useContext, useMemo} from 'react' -import {StyleSheet, View, type ViewProps, type ViewStyle} from 'react-native' -import {type StyleProp} from 'react-native' +import { + type StyleProp, + StyleSheet, + View, + type ViewProps, + type ViewStyle, +} from 'react-native' import { KeyboardAwareScrollView, type KeyboardAwareScrollViewProps, diff --git a/src/components/Select/index.web.tsx b/src/components/Select/index.web.tsx index 67c86f7dc..3aab0164c 100644 --- a/src/components/Select/index.web.tsx +++ b/src/components/Select/index.web.tsx @@ -3,8 +3,7 @@ import {View} from 'react-native' import {Select as RadixSelect} from 'radix-ui' import {useA11y} from '#/state/a11y' -import {flatten, useTheme, web} from '#/alf' -import {atoms as a} from '#/alf' +import {atoms as a, flatten, useTheme, web} from '#/alf' import {useInteractionState} from '#/components/hooks/useInteractionState' import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' import { diff --git a/src/components/Toast/Toast.tsx b/src/components/Toast/Toast.tsx index ac5bc4889..3e11cf051 100644 --- a/src/components/Toast/Toast.tsx +++ b/src/components/Toast/Toast.tsx @@ -8,8 +8,10 @@ import { type UninheritableButtonProps, } from '#/components/Button' import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheck} from '#/components/icons/CircleCheck' -import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' -import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo' +import { + CircleInfo_Stroke2_Corner0_Rounded as CircleInfo, + CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon, +} from '#/components/icons/CircleInfo' import {type Props as SVGIconProps} from '#/components/icons/common' import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' import {dismiss} from '#/components/Toast/sonner' diff --git a/src/geolocation/service.ts b/src/geolocation/service.ts index c201ecf1a..930a17943 100644 --- a/src/geolocation/service.ts +++ b/src/geolocation/service.ts @@ -25,7 +25,7 @@ const onGeolocationServiceResponseUpdate = ( async function fetchGeolocationServiceData(): Promise { if (debug.enabled) return debug.resolve(debug.geolocation) // Return local geolocation data instead of making HTTP request - return geolocationData as Geolocation + return geolocationData } /** diff --git a/src/lib/api/resolve.ts b/src/lib/api/resolve.ts index 826acfc20..bc4282a88 100644 --- a/src/lib/api/resolve.ts +++ b/src/lib/api/resolve.ts @@ -1,10 +1,10 @@ import { type AppBskyFeedDefs, type AppBskyGraphDefs, + type BskyAgent, type ComAtprotoRepoStrongRef, } from '@atproto/api' import {AtUri} from '@atproto/api' -import {type BskyAgent} from '@atproto/api' import {POST_IMG_MAX} from '#/lib/constants' import {getLinkMeta} from '#/lib/link-meta/link-meta' @@ -15,18 +15,19 @@ import { parseStarterPackUri, } from '#/lib/strings/starter-pack' import { + convertBskyAppUrlIfNeeded, isBskyCustomFeedUrl, isBskyListUrl, isBskyPostUrl, isBskyStarterPackUrl, isBskyStartUrl, isShortLink, + makeRecordUri, } from '#/lib/strings/url-helpers' import {type ComposerImage} from '#/state/gallery' import {createComposerImage} from '#/state/gallery' import {type Gif} from '#/state/queries/tenor' import {createGIFDescription} from '../gif-alt-text' -import {convertBskyAppUrlIfNeeded, makeRecordUri} from '../strings/url-helpers' type ResolvedExternalLink = { type: 'external' diff --git a/src/lib/hooks/useAnimatedValue.ts b/src/lib/hooks/useAnimatedValue.ts index 9ae14dab6..409630406 100644 --- a/src/lib/hooks/useAnimatedValue.ts +++ b/src/lib/hooks/useAnimatedValue.ts @@ -8,5 +8,5 @@ export function useAnimatedValue(initialValue: number) { lazyRef.current = new Animated.Value(initialValue) } - return lazyRef.current as Animated.Value + return lazyRef.current } diff --git a/src/lib/media/manip.ts b/src/lib/media/manip.ts index 34014ea69..e912d011f 100644 --- a/src/lib/media/manip.ts +++ b/src/lib/media/manip.ts @@ -176,22 +176,48 @@ export async function saveImageToMediaLibrary({uri}: {uri: string}) { export async function saveVideoToMediaLibrary({uri}: {uri: string}) { // download the file to cache - const downloadResponse = await RNFetchBlob.config({ - fileCache: true, - }) - .fetch('GET', uri) - .catch(() => null) - if (downloadResponse == null) return false - let videoPath = downloadResponse.path() - let extension = mimeToExt(downloadResponse.respInfo.headers['content-type']) - videoPath = normalizePath( - await moveToPermanentPath(videoPath, '.' + extension), - true, - ) + const tempPath = `${cacheDirectory ?? ''}/${String(uuid.v4())}.bin` + const dlResumable = createDownloadResumable(uri, tempPath, {cache: true}) + const dlRes = await dlResumable.downloadAsync().catch(() => null) + if (!dlRes?.uri) return false + + const contentType = + dlRes.headers['content-type'] ?? dlRes.headers['Content-Type'] + if (!contentType) { + void safeDeleteAsync(dlRes.uri) + return false + } + + let extension: string + try { + extension = mimeToExt(contentType) + } catch { + void safeDeleteAsync(dlRes.uri) + return false + } + + const videoPath = await moveToPermanentPath(dlRes.uri, '.' + extension) // save - await MediaLibrary.createAssetAsync(videoPath) - safeDeleteAsync(videoPath) + try { + if (IS_ANDROID) { + await MediaLibrary.createAlbumAsync( + ALBUM_NAME, + undefined, + undefined, + videoPath, + ) + } else { + await MediaLibrary.saveToLibraryAsync(videoPath) + } + } catch (err) { + logger.error(err instanceof Error ? err : String(err), { + message: 'Failed to save video to media library', + }) + throw err + } finally { + void safeDeleteAsync(videoPath) + } return true } diff --git a/src/lib/routes/links.ts b/src/lib/routes/links.ts index 02106134e..1a15fbd07 100644 --- a/src/lib/routes/links.ts +++ b/src/lib/routes/links.ts @@ -24,8 +24,8 @@ export function makeProfileLink( export function makeCustomFeedLink( did: string, rkey: string, - segment?: string | undefined, - feedCacheKey?: 'discover' | 'explore' | undefined, + segment?: string, + feedCacheKey?: 'discover' | 'explore', ) { return ( [`/profile`, did, 'feed', rkey, ...(segment ? [segment] : [])].join('/') + diff --git a/src/screens/Messages/components/MessagesList.tsx b/src/screens/Messages/components/MessagesList.tsx index 64dfc8f7c..e5ee58196 100644 --- a/src/screens/Messages/components/MessagesList.tsx +++ b/src/screens/Messages/components/MessagesList.tsx @@ -15,7 +15,7 @@ import { AppBskyRichtextFacet, RichText, } from '@atproto/api' -import { isNative } from '@bsky.app/alf' +import {isNative} from '@bsky.app/alf' import {useHideBottomBarBorderForScreen} from '#/lib/hooks/useHideBottomBarBorder' import {ScrollProvider} from '#/lib/ScrollContext' @@ -51,8 +51,7 @@ import {MessageItem} from '#/components/dms/MessageItem' import {NewMessagesPill} from '#/components/dms/NewMessagesPill' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' -import {IS_NATIVE} from '#/env' -import {IS_WEB} from '#/env' +import {IS_NATIVE, IS_WEB} from '#/env' import {ChatStatusInfo} from './ChatStatusInfo' import {MessageInputEmbed, useMessageEmbed} from './MessageInputEmbed' diff --git a/src/screens/Profile/Header/EditProfileDialog.tsx b/src/screens/Profile/Header/EditProfileDialog.tsx index 1c45a94cd..3959f34cf 100644 --- a/src/screens/Profile/Header/EditProfileDialog.tsx +++ b/src/screens/Profile/Header/EditProfileDialog.tsx @@ -5,7 +5,12 @@ import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' -import {MAX_DESCRIPTION, MAX_DISPLAY_NAME, HITSLOP_10, urls} from '#/lib/constants' +import { + HITSLOP_10, + MAX_DESCRIPTION, + MAX_DISPLAY_NAME, + urls, +} from '#/lib/constants' import {cleanError} from '#/lib/strings/errors' import {isOverMaxGraphemeCount} from '#/lib/strings/helpers' import {isValidWebsiteFormat} from '#/lib/strings/website' diff --git a/src/screens/VideoFeed/components/Scrubber.tsx b/src/screens/VideoFeed/components/Scrubber.tsx index 85bd4fddf..acaf02453 100644 --- a/src/screens/VideoFeed/components/Scrubber.tsx +++ b/src/screens/VideoFeed/components/Scrubber.tsx @@ -23,8 +23,7 @@ import { import {useEventListener} from 'expo' import {type VideoPlayer} from 'expo-video' -import {tokens} from '#/alf' -import {atoms as a} from '#/alf' +import {atoms as a, tokens} from '#/alf' import {formatTime} from '#/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/utils' import {Text} from '#/components/Typography' diff --git a/src/state/preferences/kawaii.tsx b/src/state/preferences/kawaii.tsx index a52780899..31618caf4 100644 --- a/src/state/preferences/kawaii.tsx +++ b/src/state/preferences/kawaii.tsx @@ -1,6 +1,6 @@ import { createContext, - PropsWithChildren, + type PropsWithChildren, useCallback, useContext, useEffect, diff --git a/src/state/queries/notifications/feed.ts b/src/state/queries/notifications/feed.ts index 57d83cb5b..ca885107d 100644 --- a/src/state/queries/notifications/feed.ts +++ b/src/state/queries/notifications/feed.ts @@ -298,7 +298,7 @@ export function* findAllPostsInQueryData( if (AppBskyFeedDefs.isPostView(item.subject)) { const quotedPost = getEmbeddedPost(item.subject?.embed) if (quotedPost && didOrHandleUriMatches(atUri, quotedPost)) { - yield embedViewRecordToPostView(quotedPost!) + yield embedViewRecordToPostView(quotedPost) } } } diff --git a/src/state/queries/usePostThread/traversal.ts b/src/state/queries/usePostThread/traversal.ts index 889c208c3..81abd958f 100644 --- a/src/state/queries/usePostThread/traversal.ts +++ b/src/state/queries/usePostThread/traversal.ts @@ -196,7 +196,7 @@ export function sortAndAnnotateThreadItems( * `repliesSeenCounter` later on, since `repliesSeenCounter` * is 1-indexed and `replyIndex` is 0-indexed. */ - childMetadata!.replyIndex = + childMetadata.replyIndex = childParentMetadata.repliesSeenCounter } diff --git a/src/state/shell/color-mode.tsx b/src/state/shell/color-mode.tsx index ad4229308..507033a79 100644 --- a/src/state/shell/color-mode.tsx +++ b/src/state/shell/color-mode.tsx @@ -1,4 +1,11 @@ -import {createContext, PropsWithChildren, useContext, useEffect, useMemo, useState} from 'react' +import { + createContext, + type PropsWithChildren, + useContext, + useEffect, + useMemo, + useState, +} from 'react' import * as persisted from '#/state/persisted' @@ -28,9 +35,7 @@ setContext.displayName = 'ColorModeSetContext' export function Provider({children}: PropsWithChildren<{}>) { const [colorMode, setColorMode] = useState(() => persisted.get('colorMode')) const [darkTheme, setDarkTheme] = useState(() => persisted.get('darkTheme')) - const [colorScheme, setColorScheme] = useState( - persisted.get('colorScheme'), - ) + const [colorScheme, setColorScheme] = useState(persisted.get('colorScheme')) const [hue, setHue] = useState(persisted.get('hue')) const stateContextValue = useMemo( diff --git a/src/view/com/notifications/NotificationFeedItem.tsx b/src/view/com/notifications/NotificationFeedItem.tsx index 6b7e8eb65..78dcff389 100644 --- a/src/view/com/notifications/NotificationFeedItem.tsx +++ b/src/view/com/notifications/NotificationFeedItem.tsx @@ -54,12 +54,16 @@ import { ChevronTop_Stroke2_Corner0_Rounded as ChevronUpIcon, } from '#/components/icons/Chevron' import {Contacts_Filled_Corner2_Rounded as ContactsIconFilled} from '#/components/icons/Contacts' -import {Heart2_Filled_Stroke2_Corner0_Rounded as HeartIconFilled} from '#/components/icons/Heart2' -import {LikeRepost_Stroke2_Corner2_Rounded as RepostHeartIcon} from '#/components/icons/Heart2' +import { + Heart2_Filled_Stroke2_Corner0_Rounded as HeartIconFilled, + LikeRepost_Stroke2_Corner2_Rounded as RepostHeartIcon, +} from '#/components/icons/Heart2' import {PersonPlus_Filled_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' -import {Repost_Stroke2_Corner3_Rounded as RepostIcon} from '#/components/icons/Repost' -import {RepostRepost_Stroke2_Corner2_Rounded as RepostRepostIcon} from '#/components/icons/Repost' +import { + Repost_Stroke2_Corner3_Rounded as RepostIcon, + RepostRepost_Stroke2_Corner2_Rounded as RepostRepostIcon, +} from '#/components/icons/Repost' import {StarterPack} from '#/components/icons/StarterPack' import {VerifiedCheck} from '#/components/icons/VerifiedCheck' import {InlineLinkText, Link} from '#/components/Link' diff --git a/src/view/com/util/PressableWithHover.tsx b/src/view/com/util/PressableWithHover.tsx index f4006a526..a4a2d194f 100644 --- a/src/view/com/util/PressableWithHover.tsx +++ b/src/view/com/util/PressableWithHover.tsx @@ -3,9 +3,9 @@ import { Pressable, type PressableProps, type StyleProp, + type View, type ViewStyle, } from 'react-native' -import {type View} from 'react-native' import {addStyle} from '#/lib/styles' import {useInteractionState} from '#/components/hooks/useInteractionState'