feat(native/chat): partner avatars + pill-shape search field
- DmItem now goes through resolveAvatar(partnerAvatar, partnerName) so the Dicebear-initials fallback kicks in for null avatars, hero ids resolve to their image url, and direct URLs pass through. Adds the PostCard-style avatarLoadFailed state for graceful broken-image fallback. - Search row pill-shaped (borderRadius 999) with 16px horizontal padding and the outline search icon for better visual rhythm.
This commit is contained in:
parent
40ccefab5b
commit
d11d548c10
@ -1,4 +1,4 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
@ -15,6 +15,7 @@ import { Ionicons } from '@expo/vector-icons';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { apiFetch } from '../../lib/api';
|
||||
import { resolveAvatar } from '../../lib/resolveAvatar';
|
||||
import { AppHeader } from '../../components/AppHeader';
|
||||
import { useColors } from '../../lib/theme';
|
||||
|
||||
@ -42,16 +43,23 @@ function DmItem({ conv, onPress }: { conv: DmConversation; onPress: () => void }
|
||||
const styles = makeStyles(colors);
|
||||
const hasUnread = conv.unreadCount > 0;
|
||||
|
||||
const avatarUrl = resolveAvatar(conv.partnerAvatar, conv.partnerName);
|
||||
const [avatarLoadFailed, setAvatarLoadFailed] = useState(false);
|
||||
useEffect(() => { setAvatarLoadFailed(false); }, [avatarUrl]);
|
||||
const avatarInitials = (conv.partnerName.slice(0, 2)).toUpperCase() || '?';
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={onPress} activeOpacity={0.7}>
|
||||
<View style={styles.dmRow}>
|
||||
<View style={styles.dmAvatar}>
|
||||
{conv.partnerAvatar ? (
|
||||
<Image source={{ uri: conv.partnerAvatar }} style={styles.dmAvatarImg} />
|
||||
{!avatarLoadFailed ? (
|
||||
<Image
|
||||
source={{ uri: avatarUrl }}
|
||||
style={styles.dmAvatarImg}
|
||||
onError={() => setAvatarLoadFailed(true)}
|
||||
/>
|
||||
) : (
|
||||
<Text style={styles.dmAvatarInitials}>
|
||||
{conv.partnerName.slice(0, 2).toUpperCase()}
|
||||
</Text>
|
||||
<Text style={styles.dmAvatarInitials}>{avatarInitials}</Text>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.dmInfo}>
|
||||
@ -130,7 +138,7 @@ export default function ChatScreen() {
|
||||
{/* Search header */}
|
||||
<View style={styles.headerSection}>
|
||||
<View style={styles.searchRow}>
|
||||
<Ionicons name="search" size={16} color={colors.textMuted} style={styles.searchIcon} />
|
||||
<Ionicons name="search-outline" size={16} color={colors.textMuted} style={styles.searchIcon} />
|
||||
<TextInput
|
||||
style={styles.searchInput}
|
||||
value={search}
|
||||
@ -194,11 +202,11 @@ function makeStyles(colors: ReturnType<typeof useColors>) {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.surfaceElevated,
|
||||
borderRadius: 10,
|
||||
paddingHorizontal: 10,
|
||||
borderRadius: 999,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
searchIcon: { marginRight: 7 },
|
||||
searchIcon: { marginRight: 8 },
|
||||
searchInput: {
|
||||
flex: 1,
|
||||
fontSize: 14,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user