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 [];