From b8e4b02b88e8040148b17a1d3ba4b9de8a1d5441 Mon Sep 17 00:00:00 2001 From: chahinebrini Date: Wed, 20 May 2026 04:49:11 +0200 Subject: [PATCH] =?UTF-8?q?perf(images):=20migrate=20react-native=20Image?= =?UTF-8?q?=20=E2=86=92=20expo-image=20(memory+disk=20cache)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avatare (Dicebear-URLs), Chat-Attachments und Feed-Bilder wurden bei jedem App-Reload neu vom Netzwerk geladen — RN Image hat nur flüchtigen Memory-Cache. expo-image (~3.0.11) bringt persistenten Disk-Cache (cachePolicy 'memory-disk' default). 14 Files migriert: UserAvatar, ChatBubble, RoomCard, ChatInput, PostCard, ComposeCard, NotificationsDropdown, AppHeader, ProfileHeader, AddDomainSheet, DomainGrid, room, profile/edit, signup. API-Mapping: resizeMode→contentFit. PostCard onLoad las e.nativeEvent. source — expo-image liefert e.source direkt (sonst wäre der Post-Bild- Aspect-Ratio-Fix still gebrochen). PostCard: nur Image-Zeilen angefasst, Like/Count/Memo-Logik unberührt (memory/feedback_minimal_post_changes.md). Kommt mit v0.3.3 (expo-image ist Native-Modul, braucht neuen Build). --- apps/rebreak-native/app/(auth)/signup.tsx | 2 +- apps/rebreak-native/app/profile/edit.tsx | 2 +- apps/rebreak-native/app/room.tsx | 6 +++--- apps/rebreak-native/components/AppHeader.tsx | 3 ++- .../rebreak-native/components/ComposeCard.tsx | 4 ++-- .../components/NotificationsDropdown.tsx | 3 ++- apps/rebreak-native/components/PostCard.tsx | 9 +++++---- apps/rebreak-native/components/UserAvatar.tsx | 5 +++-- .../components/blocker/AddDomainSheet.tsx | 10 +++++++++- .../components/blocker/DomainGrid.tsx | 2 +- .../components/chat/ChatBubble.tsx | 4 ++-- .../components/chat/ChatInput.tsx | 4 ++-- .../components/chat/RoomCard.tsx | 5 +++-- .../components/profile/ProfileHeader.tsx | 3 ++- apps/rebreak-native/package.json | 3 ++- pnpm-lock.yaml | 20 +++++++++++++++++++ 16 files changed, 60 insertions(+), 25 deletions(-) diff --git a/apps/rebreak-native/app/(auth)/signup.tsx b/apps/rebreak-native/app/(auth)/signup.tsx index fd38134..7cef0cd 100644 --- a/apps/rebreak-native/app/(auth)/signup.tsx +++ b/apps/rebreak-native/app/(auth)/signup.tsx @@ -4,10 +4,10 @@ import { Text, TextInput, TouchableOpacity, - Image, ActivityIndicator, Platform, } from 'react-native'; +import { Image } from 'expo-image'; import { useRouter } from 'expo-router'; import { SafeAreaView } from 'react-native-safe-area-context'; import Svg, { Path } from 'react-native-svg'; diff --git a/apps/rebreak-native/app/profile/edit.tsx b/apps/rebreak-native/app/profile/edit.tsx index 9bfb409..7121753 100644 --- a/apps/rebreak-native/app/profile/edit.tsx +++ b/apps/rebreak-native/app/profile/edit.tsx @@ -5,10 +5,10 @@ import { TextInput, TouchableOpacity, ScrollView, - Image, ActivityIndicator, Alert, } from 'react-native'; +import { Image } from 'expo-image'; import { useRouter } from 'expo-router'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Ionicons } from '@expo/vector-icons'; diff --git a/apps/rebreak-native/app/room.tsx b/apps/rebreak-native/app/room.tsx index efa99f7..ac85ac1 100644 --- a/apps/rebreak-native/app/room.tsx +++ b/apps/rebreak-native/app/room.tsx @@ -15,7 +15,7 @@ import { Keyboard, KeyboardAvoidingView, } from 'react-native'; -import { Image } from 'react-native'; +import { Image } from 'expo-image'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; import { useRouter, useLocalSearchParams } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; @@ -322,7 +322,7 @@ export default function RoomScreen() { {room?.avatarUrl ? ( - + ) : ( {initials} )} @@ -556,7 +556,7 @@ function RoomSettingsModal({ style={modal.avatarWrap} > {room.avatarUrl ? ( - + ) : ( diff --git a/apps/rebreak-native/components/AppHeader.tsx b/apps/rebreak-native/components/AppHeader.tsx index 5fdad97..08989ac 100644 --- a/apps/rebreak-native/components/AppHeader.tsx +++ b/apps/rebreak-native/components/AppHeader.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; -import { View, Text, TouchableOpacity, Image } from 'react-native'; +import { View, Text, TouchableOpacity } from 'react-native'; +import { Image } from 'expo-image'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Ionicons } from '@expo/vector-icons'; import { useRouter, type RelativePathString } from 'expo-router'; diff --git a/apps/rebreak-native/components/ComposeCard.tsx b/apps/rebreak-native/components/ComposeCard.tsx index 8aa577a..8a4efac 100644 --- a/apps/rebreak-native/components/ComposeCard.tsx +++ b/apps/rebreak-native/components/ComposeCard.tsx @@ -5,10 +5,10 @@ import { TextInput, Pressable, TouchableOpacity, - Image, ActivityIndicator, Alert, } from 'react-native'; +import { Image } from 'expo-image'; import { Ionicons } from '@expo/vector-icons'; import { useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; @@ -126,7 +126,7 @@ export function ComposeCard({ onPosted }: Props) { source={{ uri: imageUri }} className="w-full rounded-xl" style={{ height: 160 }} - resizeMode="cover" + contentFit="cover" /> setImageUri(null)} diff --git a/apps/rebreak-native/components/NotificationsDropdown.tsx b/apps/rebreak-native/components/NotificationsDropdown.tsx index 39da854..daf87d1 100644 --- a/apps/rebreak-native/components/NotificationsDropdown.tsx +++ b/apps/rebreak-native/components/NotificationsDropdown.tsx @@ -1,5 +1,6 @@ import { useEffect, useRef } from 'react'; -import { View, Text, Pressable, TouchableOpacity, Modal, FlatList, Animated, Image } from 'react-native'; +import { View, Text, Pressable, TouchableOpacity, Modal, FlatList, Animated } from 'react-native'; +import { Image } from 'expo-image'; import { Ionicons } from '@expo/vector-icons'; import { useRouter, type RelativePathString } from 'expo-router'; import { useTranslation } from 'react-i18next'; diff --git a/apps/rebreak-native/components/PostCard.tsx b/apps/rebreak-native/components/PostCard.tsx index 685466f..452bd39 100644 --- a/apps/rebreak-native/components/PostCard.tsx +++ b/apps/rebreak-native/components/PostCard.tsx @@ -1,5 +1,6 @@ import { memo, useState, useCallback, useRef, useEffect } from 'react'; -import { View, Text, Image, Pressable, Animated, TouchableOpacity } from 'react-native'; +import { View, Text, Pressable, Animated, TouchableOpacity } from 'react-native'; +import { Image } from 'expo-image'; import { Ionicons } from '@expo/vector-icons'; import { useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; @@ -308,7 +309,7 @@ function PostCardImpl({ post, onCommentPress }: Props) { { - const { width, height } = e.nativeEvent.source; + const { width, height } = e.source; if (width && height) { const ratio = width / height; setImageAspectRatio(Math.max(0.6, Math.min(1.78, ratio))); @@ -316,7 +317,7 @@ function PostCardImpl({ post, onCommentPress }: Props) { }} className="w-full rounded-xl mt-3" style={{ aspectRatio: imageAspectRatio ?? 1.78 }} - resizeMode="cover" + contentFit="cover" /> )} @@ -439,7 +440,7 @@ function DomainFavicon({ domain, size }: DomainFaviconProps) { setFailed(true)} /> ); diff --git a/apps/rebreak-native/components/UserAvatar.tsx b/apps/rebreak-native/components/UserAvatar.tsx index eb2dc15..ce5d82d 100644 --- a/apps/rebreak-native/components/UserAvatar.tsx +++ b/apps/rebreak-native/components/UserAvatar.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; -import { View, Text, Image } from 'react-native'; +import { View, Text } from 'react-native'; +import { Image } from 'expo-image'; import { useOnlineUsers } from '../hooks/useOnlineUsers'; import { resolveAvatar } from '../lib/resolveAvatar'; import { useColors } from '../lib/theme'; @@ -89,7 +90,7 @@ export function UserAvatar({ borderRadius: radius, backgroundColor: colors.surfaceElevated, }} - resizeMode="cover" + contentFit="cover" /> ) : ( {isImageOnly && ( diff --git a/apps/rebreak-native/components/chat/ChatInput.tsx b/apps/rebreak-native/components/chat/ChatInput.tsx index 0abba2c..d432ebb 100644 --- a/apps/rebreak-native/components/chat/ChatInput.tsx +++ b/apps/rebreak-native/components/chat/ChatInput.tsx @@ -2,7 +2,6 @@ import { useState, useRef } from 'react'; import { View, Text, - Image, TextInput, TouchableOpacity, StyleSheet, @@ -10,6 +9,7 @@ import { Platform, Alert, } from 'react-native'; +import { Image } from 'expo-image'; import * as ImagePicker from 'expo-image-picker'; // TODO(sdk54): migrate to new expo-file-system class-based API (File/Directory/Paths) — see Task #14 import * as FileSystem from 'expo-file-system/legacy'; @@ -165,7 +165,7 @@ export function ChatInput({ {attachment && ( {attachment.isImage ? ( - + ) : ( diff --git a/apps/rebreak-native/components/chat/RoomCard.tsx b/apps/rebreak-native/components/chat/RoomCard.tsx index a630229..7bb4398 100644 --- a/apps/rebreak-native/components/chat/RoomCard.tsx +++ b/apps/rebreak-native/components/chat/RoomCard.tsx @@ -1,4 +1,5 @@ -import { View, Text, Image, TouchableOpacity, StyleSheet } from 'react-native'; +import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; +import { Image } from 'expo-image'; import { Ionicons } from '@expo/vector-icons'; import { useTranslation } from 'react-i18next'; import { useColors } from '../../lib/theme'; @@ -43,7 +44,7 @@ export function RoomCard({ room, onPress }: Props) { {room.avatarUrl ? ( - + ) : !room.isPublic ? ( {initials} ) : ( diff --git a/apps/rebreak-native/components/profile/ProfileHeader.tsx b/apps/rebreak-native/components/profile/ProfileHeader.tsx index 6a5ff4a..14b89fe 100644 --- a/apps/rebreak-native/components/profile/ProfileHeader.tsx +++ b/apps/rebreak-native/components/profile/ProfileHeader.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; -import { View, Text, TouchableOpacity, Image } from 'react-native'; +import { View, Text, TouchableOpacity } from 'react-native'; +import { Image } from 'expo-image'; import { Ionicons } from '@expo/vector-icons'; import Svg, { Path } from 'react-native-svg'; import { useColors } from '../../lib/theme'; diff --git a/apps/rebreak-native/package.json b/apps/rebreak-native/package.json index 47a7f12..5648283 100644 --- a/apps/rebreak-native/package.json +++ b/apps/rebreak-native/package.json @@ -36,7 +36,8 @@ "expo-file-system": "~19.0.22", "expo-font": "~14.0.11", "expo-haptics": "^15.0.8", -"expo-image-manipulator": "~14.0.7", + "expo-image": "~3.0.11", + "expo-image-manipulator": "~14.0.7", "expo-image-picker": "~17.0.11", "expo-linking": "~8.0.12", "expo-local-authentication": "~17.0.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87b6a51..772dec3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -192,6 +192,9 @@ importers: expo-haptics: specifier: ^15.0.8 version: 15.0.8(expo@54.0.34) + expo-image: + specifier: ~3.0.11 + version: 3.0.11(expo@54.0.34)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0) expo-image-manipulator: specifier: ~14.0.7 version: 14.0.8(expo@54.0.34) @@ -5567,6 +5570,17 @@ packages: peerDependencies: expo: '*' + expo-image@3.0.11: + resolution: {integrity: sha512-4TudfUCLgYgENv+f48omnU8tjS2S0Pd9EaON5/s1ZUBRwZ7K8acEr4NfvLPSaeXvxW24iLAiyQ7sV7BXQH3RoA==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + react-native-web: '*' + peerDependenciesMeta: + react-native-web: + optional: true + expo-json-utils@0.15.0: resolution: {integrity: sha512-duRT6oGl80IDzH2LD2yEFWNwGIC2WkozsB6HF3cDYNoNNdUvFk6uN3YiwsTsqVM/D0z6LEAQ01/SlYvN+Fw0JQ==} @@ -15566,6 +15580,12 @@ snapshots: expo: 54.0.34(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.23)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0)(typescript@5.8.3) expo-image-loader: 6.0.0(expo@54.0.34) + expo-image@3.0.11(expo@54.0.34)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0): + dependencies: + expo: 54.0.34(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.23)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0)(typescript@5.8.3) + react: 19.1.0 + react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.1.0) + expo-json-utils@0.15.0: {} expo-keep-awake@15.0.8(expo@54.0.34)(react@19.1.0):