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 {
|
import {
|
||||||
View,
|
View,
|
||||||
Text,
|
Text,
|
||||||
@ -15,6 +15,7 @@ import { Ionicons } from '@expo/vector-icons';
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { apiFetch } from '../../lib/api';
|
import { apiFetch } from '../../lib/api';
|
||||||
|
import { resolveAvatar } from '../../lib/resolveAvatar';
|
||||||
import { AppHeader } from '../../components/AppHeader';
|
import { AppHeader } from '../../components/AppHeader';
|
||||||
import { useColors } from '../../lib/theme';
|
import { useColors } from '../../lib/theme';
|
||||||
|
|
||||||
@ -42,16 +43,23 @@ function DmItem({ conv, onPress }: { conv: DmConversation; onPress: () => void }
|
|||||||
const styles = makeStyles(colors);
|
const styles = makeStyles(colors);
|
||||||
const hasUnread = conv.unreadCount > 0;
|
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 (
|
return (
|
||||||
<TouchableOpacity onPress={onPress} activeOpacity={0.7}>
|
<TouchableOpacity onPress={onPress} activeOpacity={0.7}>
|
||||||
<View style={styles.dmRow}>
|
<View style={styles.dmRow}>
|
||||||
<View style={styles.dmAvatar}>
|
<View style={styles.dmAvatar}>
|
||||||
{conv.partnerAvatar ? (
|
{!avatarLoadFailed ? (
|
||||||
<Image source={{ uri: conv.partnerAvatar }} style={styles.dmAvatarImg} />
|
<Image
|
||||||
|
source={{ uri: avatarUrl }}
|
||||||
|
style={styles.dmAvatarImg}
|
||||||
|
onError={() => setAvatarLoadFailed(true)}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Text style={styles.dmAvatarInitials}>
|
<Text style={styles.dmAvatarInitials}>{avatarInitials}</Text>
|
||||||
{conv.partnerName.slice(0, 2).toUpperCase()}
|
|
||||||
</Text>
|
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.dmInfo}>
|
<View style={styles.dmInfo}>
|
||||||
@ -130,7 +138,7 @@ export default function ChatScreen() {
|
|||||||
{/* Search header */}
|
{/* Search header */}
|
||||||
<View style={styles.headerSection}>
|
<View style={styles.headerSection}>
|
||||||
<View style={styles.searchRow}>
|
<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
|
<TextInput
|
||||||
style={styles.searchInput}
|
style={styles.searchInput}
|
||||||
value={search}
|
value={search}
|
||||||
@ -194,11 +202,11 @@ function makeStyles(colors: ReturnType<typeof useColors>) {
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: colors.surfaceElevated,
|
backgroundColor: colors.surfaceElevated,
|
||||||
borderRadius: 10,
|
borderRadius: 999,
|
||||||
paddingHorizontal: 10,
|
paddingHorizontal: 16,
|
||||||
paddingVertical: 8,
|
paddingVertical: 8,
|
||||||
},
|
},
|
||||||
searchIcon: { marginRight: 7 },
|
searchIcon: { marginRight: 8 },
|
||||||
searchInput: {
|
searchInput: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user