From c888e95c5a63e8a161b55a541e58c644ed93a117 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Thu, 24 Sep 2026 18:54:19 +0200 Subject: [PATCH] fix: create new contacts in the address book being viewed --- src/screens/ContactsScreen.tsx | 11 +++++++-- src/stores/__tests__/contacts-store.test.ts | 26 ++++++++++++++++++++- src/stores/contacts-store.ts | 14 +++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/screens/ContactsScreen.tsx b/src/screens/ContactsScreen.tsx index 140c02ab..52bd518d 100644 --- a/src/screens/ContactsScreen.tsx +++ b/src/screens/ContactsScreen.tsx @@ -25,6 +25,7 @@ import type { ContactCard } from '../api/types'; import { useContactsStore, sortContactsByName, + selectCreateTargetBookId, selectGroupMembers, selectUncategorized, type ContactCategory, @@ -247,9 +248,15 @@ export default function ContactsScreen() { } }; + // A new contact lands in the address book being viewed, not the default one. + const createBookId = React.useMemo( + () => selectCreateTargetBookId(selectedCategory, addressBooks), + [selectedCategory, addressBooks], + ); + const openNewMenu = () => { Alert.alert('Create', undefined, [ - { text: 'New contact', onPress: () => navigation.navigate('ContactForm', {}) }, + { text: 'New contact', onPress: () => navigation.navigate('ContactForm', { addressBookId: createBookId }) }, { text: 'New group', onPress: () => navigation.navigate('ContactForm', { asGroup: true }) }, { text: 'Cancel', style: 'cancel' }, ]); @@ -407,7 +414,7 @@ export default function ContactsScreen() { navigation.navigate('ContactForm', {})} + onPress={() => navigation.navigate('ContactForm', { addressBookId: createBookId })} onLongPress={openNewMenu} style={styles.addBtn} hitSlop={8} diff --git a/src/stores/__tests__/contacts-store.test.ts b/src/stores/__tests__/contacts-store.test.ts index 092d3c7f..90db6073 100644 --- a/src/stores/__tests__/contacts-store.test.ts +++ b/src/stores/__tests__/contacts-store.test.ts @@ -52,8 +52,9 @@ import { selectVisibleContacts, selectGroupMembers, sortContactsByName, + selectCreateTargetBookId, } from '../contacts-store'; -import type { ContactCard } from '../../api/types'; +import type { AddressBook, ContactCard } from '../../api/types'; const mockGetAddressBooks = contactsApi.getAddressBooks as ReturnType; const mockGetAllAddressBooks = contactsApi.getAllAddressBooks as ReturnType; @@ -533,3 +534,26 @@ describe('sortContactsByName (#963)', () => { expect(sortContactsByName(list, false).map((c) => c.id)).toEqual(['a', 'e2', 'e1', 'z']); }); }); + +describe('selectCreateTargetBookId', () => { + const books = [ + { id: 'personal', name: 'Personal', isDefault: true }, + { id: 'work', name: 'Work' }, + { id: 'acc-team:shared', name: 'Team', myRights: { mayWrite: false } }, + ] as AddressBook[]; + + it('creates in the address book being viewed', () => { + expect(selectCreateTargetBookId({ type: 'addressBook', addressBookId: 'work' }, books)).toBe('work'); + }); + + it('leaves the default book to the form everywhere else', () => { + expect(selectCreateTargetBookId({ type: 'all' }, books)).toBeUndefined(); + expect(selectCreateTargetBookId({ type: 'keyword', keyword: 'VIP' }, books)).toBeUndefined(); + expect(selectCreateTargetBookId({ type: 'group', groupId: 'g1' }, books)).toBeUndefined(); + }); + + it('never targets a read-only or vanished book', () => { + expect(selectCreateTargetBookId({ type: 'addressBook', addressBookId: 'acc-team:shared' }, books)).toBeUndefined(); + expect(selectCreateTargetBookId({ type: 'addressBook', addressBookId: 'gone' }, books)).toBeUndefined(); + }); +}); diff --git a/src/stores/contacts-store.ts b/src/stores/contacts-store.ts index 6045d941..056ecb66 100644 --- a/src/stores/contacts-store.ts +++ b/src/stores/contacts-store.ts @@ -874,6 +874,20 @@ export function selectAddressBooksWithCount( })); } +/** + * The address book a contact created from the list should go into: the one + * being viewed (webmail 494adb9b), unless it is read-only. `undefined` lets + * the form fall back to the default book. + */ +export function selectCreateTargetBookId( + category: ContactCategory, + addressBooks: AddressBook[], +): string | undefined { + if (category.type !== 'addressBook') return undefined; + const book = addressBooks.find((b) => b.id === category.addressBookId); + return book && book.myRights?.mayWrite !== false ? book.id : undefined; +} + export function selectGroupMembers(state: Pick, groupId: string): ContactCard[] { const group = state.contacts.find((c) => c.id === groupId); if (!group?.members) return [];