path = "src/screens/GroupSettingsScreen.js"
with open(path, "r", encoding="utf-8") as f:
src = f.read()
edits = []
# 1. imports
edits.append((
'''import { View, Text, TouchableOpacity, StyleSheet, ScrollView, Switch, Alert, ActivityIndicator, Modal, Share } from 'react-native';
import { BlurView } from 'expo-blur';
import { useRoute, useNavigation } from '@react-navigation/native';
import { ChevronLeft, BadgeCheck, X } from 'lucide-react-native';''',
'''import { View, Text, TouchableOpacity, StyleSheet, ScrollView, Switch, Alert, ActivityIndicator, Modal, Share, FlatList, TextInput } from 'react-native';
import { BlurView } from 'expo-blur';
import { useRoute, useNavigation } from '@react-navigation/native';
import { ChevronLeft, BadgeCheck, X, UserPlus, Check } from 'lucide-react-native';'''
))
# 2. state
edits.append((
''' const [inviteVisible, setInviteVisible] = useState(false);
const [inviteCode, setInviteCode] = useState(null);''',
''' const [inviteVisible, setInviteVisible] = useState(false);
const [inviteCode, setInviteCode] = useState(null);
const [addFriendsVisible, setAddFriendsVisible] = useState(false);
const [friends, setFriends] = useState([]);
const [friendSearch, setFriendSearch] = useState('');
const [selectedFriendIds, setSelectedFriendIds] = useState([]);
const [addingMembers, setAddingMembers] = useState(false);'''
))
# 3. showAddFriendsRow flag
edits.append((
''' function guardGroupId() {''',
''' const showAddFriendsRow = canManage || editPerm === 'all';
function guardGroupId() {'''
))
# 4. handler functions
edits.append((
''' function confirmClearHistory() {''',
''' async function openAddFriends() {
if (!guardGroupId()) return;
setFriendSearch('');
setSelectedFriendIds([]);
setAddFriendsVisible(true);
try {
const res = await apiRequest('/friends/list');
const memberIds = members.map(m => String(m.id));
const list = (res?.friends || [])
.filter(f => !memberIds.includes(String(f.id)))
.map(f => ({ id: f.id, username: f.username || f.handle || 'Friend', verified: f.verified }));
setFriends(list);
} catch (e) {
Alert.alert('Error', e.message || "Couldn't load your friends list.");
}
}
function toggleFriendSelect(id) {
setSelectedFriendIds(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]);
}
async function submitAddFriends() {
if (!selectedFriendIds.length) return;
setAddingMembers(true);
try {
await apiRequest(`/groups/${groupId}/members`, {
method: 'POST',
body: JSON.stringify({ member_ids: selectedFriendIds }),
});
setAddFriendsVisible(false);
await load();
} catch (e) {
Alert.alert('Error', e.message || "Couldn't add those friends to the group.");
} finally {
setAddingMembers(false);
}
}
function confirmClearHistory() {'''
))
# 5. member row border fix
edits.append((
''' ''',
''' '''
))
# 6. Add Friends row in Members card
edits.append((
''' ))}
Permissions''',
''' ))}
{showAddFriendsRow && (
Add Friends
)}
Permissions'''
))
# 7. modal
edits.append((
'''
);
}''',
'''
setAddFriendsVisible(false)}>
setAddFriendsVisible(false)}>
Add friends to {group.name || 'group'}
f.username.toLowerCase().includes(friendSearch.toLowerCase()))}
keyExtractor={f => String(f.id)}
ListEmptyComponent={No friends to add.}
renderItem={({ item }) => {
const isSelected = selectedFriendIds.includes(item.id);
return (
toggleFriendSelect(item.id)}>
{item.username?.[0]?.toUpperCase()}
{item.username}
{item.verified && }
{isSelected && }
);
}}
/>
{addingMembers ? (
) : (
Add{selectedFriendIds.length ? ` (${selectedFriendIds.length})` : ''}
)}
);
}'''
))
# 8. styles
edits.append((
''' shareBtn: { marginTop: 12, backgroundColor: '#4f46e5', paddingVertical: 12, paddingHorizontal: 24, borderRadius: 14 },
shareBtnText: { color: 'white', fontWeight: '700', fontSize: 14 },
});''',
''' shareBtn: { marginTop: 12, backgroundColor: '#4f46e5', paddingVertical: 12, paddingHorizontal: 24, borderRadius: 14, alignItems: 'center', justifyContent: 'center', minWidth: 120 },
shareBtnDisabled: { backgroundColor: '#c4c4cc' },
shareBtnText: { color: 'white', fontWeight: '700', fontSize: 14 },
addFriendIconWrap: { width: 32, height: 32, borderRadius: 16, alignItems: 'center', justifyContent: 'center', marginRight: 10, backgroundColor: 'rgba(79,70,229,0.12)' },
addFriendLabel: { fontWeight: '600', color: '#4f46e5' },
addFriendsCard: { width: '100%', maxHeight: '75%', padding: 24, gap: 10 },
friendSearchInput: { backgroundColor: 'rgba(255,255,255,0.6)', borderRadius: 12, paddingHorizontal: 14, paddingVertical: 10, fontSize: 14, color: '#0f0f1a', marginTop: 4 },
friendList: { maxHeight: 280, marginVertical: 6 },
friendRow: { flexDirection: 'row', alignItems: 'center', paddingVertical: 10 },
friendEmptyText: { textAlign: 'center', color: '#6b6b7a', fontSize: 13, paddingVertical: 20 },
checkCircle: { width: 22, height: 22, borderRadius: 11, borderWidth: 1.5, borderColor: '#c4c4cc', alignItems: 'center', justifyContent: 'center' },
checkCircleSelected: { backgroundColor: '#4f46e5', borderColor: '#4f46e5' },
});'''
))
for i, (old, new) in enumerate(edits, 1):
if src.count(old) != 1:
raise SystemExit(f"PATCH FAILED at edit {i}/8: marker not found exactly once (file may already be patched or changed).")
src = src.replace(old, new, 1)
with open(path, "w", encoding="utf-8") as f:
f.write(src)
print("✅ GroupSettingsScreen.js patched: Add Friends row + picker modal wired to /friends/list and /groups/{id}/members.")