feat(mobile): avoid direct expo-image
This commit is contained in:
parent
90cb12870c
commit
73fb08ed55
|
|
@ -1,12 +1,13 @@
|
|||
import { Image as ExpoImage } from "expo-image"
|
||||
import { Animated, FlatList, Pressable, ScrollView, TouchableOpacity } from "react-native"
|
||||
import Reanimated from "react-native-reanimated"
|
||||
|
||||
import { Image } from "@/src/components/ui/image/Image"
|
||||
|
||||
export const AnimatedScrollView = Animated.createAnimatedComponent(ScrollView)
|
||||
export const AnimatedFlatList = Animated.createAnimatedComponent(FlatList)
|
||||
export const AnimatedTouchableOpacity = Animated.createAnimatedComponent(TouchableOpacity)
|
||||
|
||||
export const ReAnimatedExpoImage = Reanimated.createAnimatedComponent(ExpoImage)
|
||||
export const ReAnimateImage = Reanimated.createAnimatedComponent(Image)
|
||||
export const ReAnimatedPressable = Reanimated.createAnimatedComponent(Pressable)
|
||||
export const ReAnimatedScrollView = Reanimated.createAnimatedComponent(ScrollView)
|
||||
export const ReAnimatedTouchableOpacity = Reanimated.createAnimatedComponent(TouchableOpacity)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { getBackgroundGradient, isCJKChar } from "@follow/utils"
|
||||
import { Image } from "expo-image"
|
||||
import { LinearGradient } from "expo-linear-gradient"
|
||||
import { useMemo, useState } from "react"
|
||||
import type { DimensionValue, StyleProp, TextStyle, ViewStyle } from "react-native"
|
||||
import { StyleSheet, Text, View } from "react-native"
|
||||
import { useColor } from "react-native-uikit-colors"
|
||||
|
||||
import { Image } from "@/src/components/ui/image/Image"
|
||||
|
||||
export const FallbackIcon = ({
|
||||
title,
|
||||
url,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import type { FeedViewType } from "@follow/constants"
|
||||
import type { ImageProps } from "expo-image"
|
||||
import type { ReactNode } from "react"
|
||||
import { useMemo } from "react"
|
||||
|
||||
import type { FeedSchema } from "@/src/database/schemas/types"
|
||||
import { getFeedIconSource } from "@/src/lib/image"
|
||||
|
||||
import type { ImageProps } from "../image/Image"
|
||||
import { Image } from "../image/Image"
|
||||
|
||||
export type FeedIconRequiredFeed = Pick<
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { createBuildSafeHeaders } from "@follow/utils/src/headers"
|
||||
import { getImageProxyUrl, IMAGE_PROXY_URL } from "@follow/utils/src/img-proxy"
|
||||
import type { ImageProps as ExpoImageProps } from "expo-image"
|
||||
import type { ImageErrorEventData, ImageProps as ExpoImageProps } from "expo-image"
|
||||
import { Image as ExpoImage } from "expo-image"
|
||||
import { forwardRef, useCallback, useMemo, useState } from "react"
|
||||
|
||||
|
|
@ -55,9 +55,16 @@ export const Image = forwardRef<ExpoImage, ImageProps>(
|
|||
}, [proxy?.height, proxy?.width, safeSource])
|
||||
|
||||
const [isError, setIsError] = useState(false)
|
||||
const onError = useCallback(() => {
|
||||
setIsError(true)
|
||||
}, [])
|
||||
const onError = useCallback(
|
||||
(e: ImageErrorEventData) => {
|
||||
if (isError) {
|
||||
rest.onError?.(e)
|
||||
} else {
|
||||
setIsError(true)
|
||||
}
|
||||
},
|
||||
[isError, rest],
|
||||
)
|
||||
|
||||
return (
|
||||
<ExpoImage
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
import { FeedViewType } from "@follow/constants"
|
||||
import { createBuildSafeHeaders } from "@follow/utils/src/headers"
|
||||
import { IMAGE_PROXY_URL } from "@follow/utils/src/img-proxy"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { Image } from "expo-image"
|
||||
import { router } from "expo-router"
|
||||
import { useAtomValue } from "jotai"
|
||||
import type { FC } from "react"
|
||||
|
|
@ -12,20 +9,15 @@ import { ScrollView } from "react-native-gesture-handler"
|
|||
import Animated, { FadeInUp } from "react-native-reanimated"
|
||||
|
||||
import { FeedIcon } from "@/src/components/ui/icon/feed-icon"
|
||||
import { Image } from "@/src/components/ui/image/Image"
|
||||
import { ItemPressable } from "@/src/components/ui/pressable/ItemPressable"
|
||||
import { apiClient } from "@/src/lib/api-fetch"
|
||||
import { proxyEnv } from "@/src/lib/proxy-env"
|
||||
import { useSubscriptionByFeedId } from "@/src/store/subscription/hooks"
|
||||
|
||||
import { useSearchPageContext } from "../ctx"
|
||||
import { BaseSearchPageFlatList, ItemSeparator, RenderScrollComponent } from "./__base"
|
||||
import { useDataSkeleton } from "./hooks"
|
||||
|
||||
const buildSafeHeaders = createBuildSafeHeaders(proxyEnv.VITE_WEB_URL, [
|
||||
IMAGE_PROXY_URL,
|
||||
proxyEnv.VITE_API_URL,
|
||||
])
|
||||
|
||||
type SearchResultItem = Awaited<ReturnType<typeof apiClient.discover.$post>>["data"][number]
|
||||
|
||||
export const SearchFeed = () => {
|
||||
|
|
@ -168,12 +160,11 @@ const PreviewItem = ({ entry }: { entry: NonNullable<SearchResultItem["entries"]
|
|||
{!!firstMedia && (
|
||||
<View className="bg-gray-6 ml-auto size-[52px] shrink-0 overflow-hidden rounded-lg">
|
||||
<Image
|
||||
source={{ uri: firstMedia.url, headers: buildSafeHeaders({ url: firstMedia.url }) }}
|
||||
source={{ uri: firstMedia.url }}
|
||||
className="size-full rounded-lg"
|
||||
contentFit="cover"
|
||||
transition={500}
|
||||
placeholder={{
|
||||
blurHash: firstMedia.blurhash,
|
||||
blurhash: firstMedia.blurhash,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { useQuery } from "@tanstack/react-query"
|
||||
import { Image } from "expo-image"
|
||||
import { router } from "expo-router"
|
||||
import { useAtomValue } from "jotai"
|
||||
import { memo } from "react"
|
||||
import { Text, View } from "react-native"
|
||||
|
||||
import { FallbackIcon } from "@/src/components/ui/icon/fallback-icon"
|
||||
import { Image } from "@/src/components/ui/image/Image"
|
||||
import { ItemPressable } from "@/src/components/ui/pressable/ItemPressable"
|
||||
import { apiClient } from "@/src/lib/api-fetch"
|
||||
import { useSubscriptionByListId } from "@/src/store/subscription/hooks"
|
||||
|
|
@ -71,12 +71,7 @@ const SearchListCard = memo(({ item }: { item: SearchResultItem }) => {
|
|||
<View className="flex-row items-center gap-2 pl-4 pr-2">
|
||||
<View className="size-[32px] overflow-hidden rounded-lg">
|
||||
{item.list?.image ? (
|
||||
<Image
|
||||
source={item.list.image}
|
||||
className="size-full"
|
||||
contentFit="cover"
|
||||
transition={1000}
|
||||
/>
|
||||
<Image source={{ uri: item.list.image }} className="size-full" contentFit="cover" />
|
||||
) : (
|
||||
!!item.list?.title && <FallbackIcon title={item.list.title} size={32} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { cn } from "@follow/utils"
|
||||
import { Image } from "expo-image"
|
||||
import { router, usePathname } from "expo-router"
|
||||
import { useEffect } from "react"
|
||||
import { Pressable, Text, View } from "react-native"
|
||||
|
|
@ -10,6 +9,7 @@ import Animated, {
|
|||
withTiming,
|
||||
} from "react-native-reanimated"
|
||||
|
||||
import { Image } from "@/src/components/ui/image/Image"
|
||||
import { useActiveTrack } from "@/src/lib/player"
|
||||
import { usePrefetchImageColors } from "@/src/store/image/hooks"
|
||||
|
||||
|
|
@ -46,7 +46,7 @@ export function PlayerTabBar({ className }: { className?: string }) {
|
|||
}}
|
||||
>
|
||||
<View className="flex flex-row items-center gap-4 overflow-hidden rounded-2xl p-2">
|
||||
<Image source={activeTrack?.artwork} className="size-12 rounded-lg" />
|
||||
<Image source={{ uri: activeTrack?.artwork ?? "" }} className="size-12 rounded-lg" />
|
||||
<View className="flex-1 overflow-hidden">
|
||||
<Text className="text-label text-lg font-semibold" numberOfLines={1}>
|
||||
{activeTrack?.title ?? ""}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { cn } from "@follow/utils"
|
||||
import { Image } from "expo-image"
|
||||
import { LinearGradient } from "expo-linear-gradient"
|
||||
import { router } from "expo-router"
|
||||
import { useEffect, useMemo } from "react"
|
||||
|
|
@ -12,6 +11,8 @@ import Reanimated, {
|
|||
} from "react-native-reanimated"
|
||||
import { SheetScreen } from "react-native-sheet-transitions"
|
||||
|
||||
import { Image } from "@/src/components/ui/image/Image"
|
||||
|
||||
import { gentleSpringPreset } from "../constants/spring"
|
||||
import { useActiveTrack, useIsPlaying } from "../lib/player"
|
||||
import { PlayerScreenContext, usePlayerScreenContext } from "../modules/player/context"
|
||||
|
|
@ -36,7 +37,7 @@ function CoverArt({ cover }: { cover?: string }) {
|
|||
|
||||
return (
|
||||
<Reanimated.View className="mx-auto my-12 aspect-square w-[87%] shadow" style={[animatedStyle]}>
|
||||
<Image source={cover} className="size-full rounded-lg" />
|
||||
<Image source={{ uri: cover ?? "" }} className="size-full rounded-lg" />
|
||||
</Reanimated.View>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export const createBuildSafeHeaders =
|
|||
headers.Referer = urlObj.origin
|
||||
headers.Origin = urlObj.origin
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
console.warn(error)
|
||||
}
|
||||
|
||||
return headers
|
||||
|
|
|
|||
Loading…
Reference in New Issue