fix: create new contacts in the address book being viewed

This commit is contained in:
Linus Rath
2026-09-24 19:03:35 +02:00
parent e6be8922b7
commit c888e95c5a
3 changed files with 48 additions and 3 deletions
+9 -2
View File
@@ -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() {
<Upload size={20} color={c.text} />
</Pressable>
<Pressable
onPress={() => navigation.navigate('ContactForm', {})}
onPress={() => navigation.navigate('ContactForm', { addressBookId: createBookId })}
onLongPress={openNewMenu}
style={styles.addBtn}
hitSlop={8}
+25 -1
View File
@@ -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<typeof vi.fn>;
const mockGetAllAddressBooks = contactsApi.getAllAddressBooks as ReturnType<typeof vi.fn>;
@@ -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();
});
});
+14
View File
@@ -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<ContactsState, 'contacts'>, groupId: string): ContactCard[] {
const group = state.contacts.find((c) => c.id === groupId);
if (!group?.members) return [];