1. Contact requests silently 404'd
fetchContactRequests hit /api/relay/contacts, but the server mounts
the whole /relay/* group at root (no /api prefix). Result: every
poll returned 404, the catch swallowed it, and the notifications
tab stayed empty even after the user sent themselves a CONTACT_
REQUEST on-chain. Fixed the client path to /relay/contacts — same
pattern as sendEnvelope / fetchInbox in the v1.0.x relay cleanup.
2. Search screen was half-finished
SearchBar used a dual-state hack (idle-centered Text overlaid with
an invisible TextInput) that broke focus + alignment on Android and
sometimes ate taps. Rewrote as a plain single-row pill: icon +
TextInput + optional clear button. Fewer moving parts, predictable
focus, proper placeholder styling.
new-contact.tsx cleaned up:
- Title "New chat" → "Поиск" (matches the NavBar tab label and the
rest of the Russian UI).
- All labels localised: "Accept/Decline", "Intro", "Anti-spam fee",
fee-tier names, error messages, final CTA.
- Proper empty-state hint when query is empty (icon + headline +
explanation of @username / hex / DC prefix) instead of just a
floating helper text.
- Search button hidden until user types something — the empty-
state stands alone, no dead grey button under it.
- onClear handler on SearchBar resets the resolved profile too.
requests.tsx localised: title, empty-state, Accept/Decline button
copy, confirmation Alert text.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
70 lines
1.9 KiB
TypeScript
70 lines
1.9 KiB
TypeScript
/**
|
||
* SearchBar — single-TextInput pill. Icon + input в одном ряду, без
|
||
* idle/focused двойного состояния (раньше был хак с невидимым
|
||
* TextInput поверх отцентрированного Text — ломал focus и выравнивание
|
||
* на Android).
|
||
*/
|
||
import React from 'react';
|
||
import { View, TextInput, Pressable } from 'react-native';
|
||
import { Ionicons } from '@expo/vector-icons';
|
||
|
||
export interface SearchBarProps {
|
||
value: string;
|
||
onChangeText: (v: string) => void;
|
||
placeholder?: string;
|
||
autoFocus?: boolean;
|
||
onSubmitEditing?: () => void;
|
||
onClear?: () => void;
|
||
}
|
||
|
||
export function SearchBar({
|
||
value, onChangeText, placeholder = 'Поиск', autoFocus, onSubmitEditing, onClear,
|
||
}: SearchBarProps) {
|
||
return (
|
||
<View
|
||
style={{
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
backgroundColor: '#111111',
|
||
borderWidth: 1,
|
||
borderColor: '#1f1f1f',
|
||
borderRadius: 999,
|
||
paddingHorizontal: 14,
|
||
gap: 8,
|
||
}}
|
||
>
|
||
<Ionicons name="search" size={16} color="#6a6a6a" />
|
||
<TextInput
|
||
value={value}
|
||
onChangeText={onChangeText}
|
||
placeholder={placeholder}
|
||
placeholderTextColor="#5a5a5a"
|
||
autoCapitalize="none"
|
||
autoCorrect={false}
|
||
autoFocus={autoFocus}
|
||
onSubmitEditing={onSubmitEditing}
|
||
returnKeyType="search"
|
||
style={{
|
||
flex: 1,
|
||
color: '#ffffff',
|
||
fontSize: 14,
|
||
paddingVertical: 10,
|
||
padding: 0,
|
||
includeFontPadding: false,
|
||
}}
|
||
/>
|
||
{value.length > 0 && (
|
||
<Pressable
|
||
onPress={() => {
|
||
onChangeText('');
|
||
onClear?.();
|
||
}}
|
||
hitSlop={8}
|
||
>
|
||
<Ionicons name="close-circle" size={16} color="#6a6a6a" />
|
||
</Pressable>
|
||
)}
|
||
</View>
|
||
);
|
||
}
|