mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-01 00:07:01 +00:00
fix: refresh the widgets when the app goes to the background #52
Leaving the app scheduled the widget refresh with a zero-delay timer. React Native pauses JS timers while the activity is in the background (timers in the bridgeless runtime have no immediate path for a zero delay), so the refresh only ran once the app came back or a widget task happened to start. Switching accounts and pressing Home left the widgets on the previous account's mail. The refresh now starts directly from the app state change.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const refreshSnapshot = vi.hoisted(() => vi.fn(async () => undefined));
|
||||
vi.mock('../build', () => ({ refreshSnapshot, readRegistry: vi.fn() }));
|
||||
vi.mock('../render', () => ({ hasPlacedWidgets: vi.fn(async () => true), redrawAll: vi.fn(async () => undefined) }));
|
||||
vi.mock('../state', () => ({ replaceAll: vi.fn() }));
|
||||
|
||||
const { handleAppState } = await import('../sync');
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
refreshSnapshot.mockClear();
|
||||
});
|
||||
|
||||
describe('widget sync', () => {
|
||||
it('refreshes when the app goes to the background without waiting for a timer', async () => {
|
||||
// React Native pauses JS timers while the activity is in the background,
|
||||
// so not even a zero-delay timer may stand between leaving the app and
|
||||
// the refresh. Fake timers that never advance stand in for that.
|
||||
vi.useFakeTimers();
|
||||
handleAppState('background');
|
||||
for (let i = 0; i < 10; i++) await Promise.resolve();
|
||||
expect(refreshSnapshot).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('leaves the app coming back to the scheduled refreshes', async () => {
|
||||
vi.useFakeTimers();
|
||||
handleAppState('active');
|
||||
for (let i = 0; i < 10; i++) await Promise.resolve();
|
||||
expect(refreshSnapshot).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
+13
-3
@@ -35,6 +35,18 @@ function schedule(delayMs: number, urgent = false): void {
|
||||
timer = setTimeout(() => { void run(); }, wait);
|
||||
}
|
||||
|
||||
/**
|
||||
* Leaving the app is the most likely moment to glance at a widget, so the
|
||||
* widgets refresh right then. Not through a timer: React Native pauses JS
|
||||
* timers while the app is in the background, so even a zero-delay timer
|
||||
* would only fire when the app comes back.
|
||||
*/
|
||||
export function handleAppState(next: string): void {
|
||||
if (next !== 'background') return;
|
||||
if (timer) clearTimeout(timer);
|
||||
void run();
|
||||
}
|
||||
|
||||
/** Start following app state; returns the cleanup. No-op off Android. */
|
||||
export function startWidgetSync(): () => void {
|
||||
if (Platform.OS !== 'android') return () => {};
|
||||
@@ -61,9 +73,7 @@ export function startWidgetSync(): () => void {
|
||||
}
|
||||
}),
|
||||
];
|
||||
const appState = AppState.addEventListener('change', (next) => {
|
||||
if (next === 'background') schedule(0, true);
|
||||
});
|
||||
const appState = AppState.addEventListener('change', handleAppState);
|
||||
return () => {
|
||||
for (const unsubscribe of unsubscribers) unsubscribe();
|
||||
appState.remove();
|
||||
|
||||
Reference in New Issue
Block a user