mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 08:06:36 +00:00
fix: let the user delete or restore held-back deletions from the sync settings #34
When a sync held back many deletions made on the device (Android's
This commit is contained in:
@@ -646,6 +646,10 @@
|
||||
"choose_calendars_title": "Calendars to sync",
|
||||
"collections_count": "{selected} of {total} sync",
|
||||
"continue": "Continue",
|
||||
"deletions_delete": "Delete on server",
|
||||
"deletions_message": "Many items were deleted on this device at once, so they were not deleted on the server. Delete them there as well, or bring them back on this device.",
|
||||
"deletions_restore": "Bring them back",
|
||||
"deletions_title": "Delete them on the server too?",
|
||||
"denied_calendar": "Without access to calendars, Bulwark cannot put your calendars on this device.",
|
||||
"denied_contacts": "Without access to contacts, Bulwark cannot put your address books on this device.",
|
||||
"denied_title": "Sync stays off",
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
disableDeviceSync,
|
||||
enableDeviceSync,
|
||||
openAndroidAccountSettings,
|
||||
resolveDeletions,
|
||||
resumeDeviceSync,
|
||||
syncNow,
|
||||
} from '../../../device-sync/app/lifecycle';
|
||||
@@ -224,10 +225,30 @@ export function DeviceSyncAccountRow({ account, authority }: Props) {
|
||||
void refresh();
|
||||
};
|
||||
|
||||
const reviewDeletions = () => {
|
||||
const resolve = (choice: 'delete' | 'restore') => {
|
||||
void resolveDeletions(account.id, authority, choice)
|
||||
.catch((err: unknown) => alertFailed(err instanceof Error ? err.message : String(err)))
|
||||
.finally(() => { void refresh(); });
|
||||
};
|
||||
Alert.alert(
|
||||
t('settings.device_sync.deletions_title', 'Delete them on the server too?'),
|
||||
t(
|
||||
'settings.device_sync.deletions_message',
|
||||
'Many items were deleted on this device at once, so they were not deleted on the server. Delete them there as well, or bring them back on this device.',
|
||||
),
|
||||
[
|
||||
{ text: t('common.cancel', 'Cancel'), style: 'cancel' },
|
||||
{ text: t('settings.device_sync.deletions_restore', 'Bring them back'), onPress: () => resolve('restore') },
|
||||
{ text: t('settings.device_sync.deletions_delete', 'Delete on server'), style: 'destructive', onPress: () => resolve('delete') },
|
||||
],
|
||||
);
|
||||
};
|
||||
|
||||
const runAction = (action: StatusAction) => {
|
||||
if (action === 'signIn') navigation.navigate('AddAccount');
|
||||
else if (action === 'grantAccess') void grantAccess();
|
||||
else void openAndroidAccountSettings(account.id);
|
||||
else reviewDeletions();
|
||||
};
|
||||
|
||||
const closeChooser = (changed: boolean) => {
|
||||
|
||||
@@ -108,6 +108,7 @@ import {
|
||||
reconcileDeviceAccounts,
|
||||
releaseDeviceSyncBeforeSignOut,
|
||||
removeSuspendedAccount,
|
||||
resolveDeletions,
|
||||
resumeDeviceSync,
|
||||
TEARDOWN_TIMEOUT_MS,
|
||||
} from '../../app/lifecycle';
|
||||
@@ -483,6 +484,19 @@ describe('sign-in', () => {
|
||||
});
|
||||
|
||||
describe('settings actions', () => {
|
||||
it('deletes held-back deletions on the server, or brings the items back, with a sync', async () => {
|
||||
await enableDeviceSync(ALICE, CONTACTS_AUTHORITY);
|
||||
h.state.log = [];
|
||||
expect(await resolveDeletions(ALICE, CONTACTS_AUTHORITY, 'delete')).toBe(true);
|
||||
expect(await resolveDeletions(ALICE, CONTACTS_AUTHORITY, 'restore')).toBe(true);
|
||||
expect(h.state.log).toEqual([
|
||||
`sync alice ${CONTACTS_AUTHORITY} {"manual":true,"overrideTooManyDeletions":true}`,
|
||||
`sync alice ${CONTACTS_AUTHORITY} {"manual":true,"discardLocalDeletions":true}`,
|
||||
]);
|
||||
// Without an Android account there is nothing to resolve.
|
||||
expect(await resolveDeletions(BOB, CONTACTS_AUTHORITY, 'delete')).toBe(false);
|
||||
});
|
||||
|
||||
it('applies a new interval to Android while the authority syncs', async () => {
|
||||
await enableDeviceSync(ALICE, CONTACTS_AUTHORITY);
|
||||
h.state.log = [];
|
||||
|
||||
@@ -623,7 +623,23 @@ export async function syncNow(registryId: string, authority: Authority): Promise
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Android's sync screen of the account ("Android account settings", "Review deletions"). */
|
||||
/**
|
||||
* After a run held back many deletions made on the device
|
||||
* (`tooManyDeletions`): delete those items on the server too, or bring them
|
||||
* back on the device from the server. Android's own screens offer neither.
|
||||
*/
|
||||
export async function resolveDeletions(registryId: string, authority: Authority, choice: 'delete' | 'restore'): Promise<boolean> {
|
||||
const accountName = accountDeviceSync(registryId).androidAccountName;
|
||||
if (!deviceSyncAvailable() || !accountName) return false;
|
||||
await requestSync(
|
||||
accountName,
|
||||
authority,
|
||||
choice === 'delete' ? { manual: true, overrideTooManyDeletions: true } : { manual: true, discardLocalDeletions: true },
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Android's sync screen of the account ("Android account settings"). */
|
||||
export async function openAndroidAccountSettings(registryId: string): Promise<boolean> {
|
||||
const accountName = accountDeviceSync(registryId).androidAccountName;
|
||||
if (!deviceSyncAvailable() || !accountName) return false;
|
||||
|
||||
Reference in New Issue
Block a user