mirror of
https://github.com/laurent22/joplin.git
synced 2026-03-13 08:09:59 +08:00
This commit is contained in:
@@ -6,7 +6,7 @@ const shim: typeof ShimType = require('@joplin/lib/shim').default;
|
||||
import { isCallbackUrl } from '@joplin/lib/callbackUrlUtils';
|
||||
import { FileLocker } from '@joplin/utils/fs';
|
||||
import { IpcMessageHandler, IpcServer, Message, newHttpError, sendMessage, SendMessageOptions, startServer, stopServer } from '@joplin/utils/ipc';
|
||||
import { BrowserWindow, Tray, WebContents, screen, App, nativeTheme } from 'electron';
|
||||
import { BrowserWindow, Tray, WebContents, screen, App, nativeTheme, powerMonitor } from 'electron';
|
||||
import bridge from './bridge';
|
||||
import * as url from 'url';
|
||||
const path = require('path');
|
||||
@@ -401,6 +401,15 @@ export default class ElectronAppWrapper {
|
||||
};
|
||||
addWindowEventHandlers(this.win_.webContents);
|
||||
|
||||
// BrowserWindow 'focus' fires when the OS gives focus to the application window
|
||||
// (i.e. coming from another app or from the taskbar), not on intra-app focus switches.
|
||||
// We use a dedicated IPC channel so the renderer can trigger an immediate sync on
|
||||
// OS-level focus gain without conflating it with the 'window-focused' channel that
|
||||
// handles Joplin-internal window routing.
|
||||
this.win_.on('focus', () => {
|
||||
this.win_?.webContents.send('main-window-focused');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
this.win_.on('close', (event: any) => {
|
||||
// If it's on macOS, the app is completely closed only if the user chooses to close the app (willQuitApp_ will be true)
|
||||
@@ -892,6 +901,11 @@ export default class ElectronAppWrapper {
|
||||
event.preventDefault();
|
||||
void this.openCallbackUrl(url);
|
||||
});
|
||||
|
||||
// When the OS wakes from sleep, notify the renderer so it can trigger an immediate sync.
|
||||
powerMonitor.on('resume', () => {
|
||||
this.win_?.webContents.send('system-resumed');
|
||||
});
|
||||
}
|
||||
|
||||
public async openCallbackUrl(url: string) {
|
||||
|
||||
@@ -733,6 +733,23 @@ class Application extends BaseApplication {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Trigger an immediate sync when the main window gains OS-level focus (i.e. the user
|
||||
// switches back to Joplin from another application) or when the system wakes from sleep.
|
||||
// A 30-second cool-down prevents duplicate syncs during rapid focus-in/focus-out cycles.
|
||||
const minResumeSyncIntervalMs = 30_000;
|
||||
let lastFocusSyncTime = 0;
|
||||
|
||||
const scheduleResumeSync = () => {
|
||||
const now = Date.now();
|
||||
if (now - lastFocusSyncTime > minResumeSyncIntervalMs) {
|
||||
lastFocusSyncTime = now;
|
||||
void reg.scheduleSync(0);
|
||||
}
|
||||
};
|
||||
|
||||
ipcRenderer.on('main-window-focused', scheduleResumeSync);
|
||||
ipcRenderer.on('system-resumed', scheduleResumeSync);
|
||||
});
|
||||
|
||||
addTask('app/initPluginService', () => this.initPluginService());
|
||||
|
||||
@@ -19,7 +19,7 @@ import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer';
|
||||
import SyncTargetJoplinCloud from '@joplin/lib/SyncTargetJoplinCloud';
|
||||
import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive';
|
||||
import { Keyboard, BackHandler, Animated, StatusBar, Platform, Dimensions } from 'react-native';
|
||||
import { AppState as RNAppState, EmitterSubscription, View, Text, Linking, NativeEventSubscription, Appearance, ActivityIndicator } from 'react-native';
|
||||
import { AppState as RNAppState, AppStateStatus, EmitterSubscription, View, Text, Linking, NativeEventSubscription, Appearance, ActivityIndicator } from 'react-native';
|
||||
import getResponsiveValue from './components/getResponsiveValue';
|
||||
import NetInfo, { NetInfoSubscription } from '@react-native-community/netinfo';
|
||||
const DropdownAlert = require('react-native-dropdownalert').default;
|
||||
@@ -295,7 +295,8 @@ class AppComponent extends React.Component<AppComponentProps, AppComponentState>
|
||||
private unsubscribeScreenWidthChangeHandler_: EmitterSubscription|undefined;
|
||||
private unsubscribeNetInfoHandler_: NetInfoSubscription|undefined;
|
||||
private unsubscribeNewShareListener_: UnsubscribeShareListener|undefined;
|
||||
private onAppStateChange_: ()=> void;
|
||||
private onAppStateChange_: (nextAppState: AppStateStatus)=> void;
|
||||
private lastResumeSyncTime_ = 0;
|
||||
private backButtonHandler_: BackButtonHandler;
|
||||
private handleNewShare_: ()=> void;
|
||||
private handleOpenURL_: (event: unknown)=> void;
|
||||
@@ -315,8 +316,24 @@ class AppComponent extends React.Component<AppComponentProps, AppComponentState>
|
||||
return this.backButtonHandler();
|
||||
};
|
||||
|
||||
this.onAppStateChange_ = () => {
|
||||
this.onAppStateChange_ = (nextAppState: AppStateStatus) => {
|
||||
PoorManIntervals.update();
|
||||
|
||||
// Trigger sync immediately when the app becomes active (resume from background/lock screen).
|
||||
// Only run when the app becomes active, with a 30-second minimum interval
|
||||
// prevent sync spam on rapid lock/unlock cycles.
|
||||
const minResumeSyncIntervalMs = 30_000;
|
||||
if (nextAppState === 'active') {
|
||||
const elapsed = Date.now() - this.lastResumeSyncTime_;
|
||||
if (elapsed >= minResumeSyncIntervalMs) {
|
||||
logger.info(`onAppStateChange_: App became active - scheduling immediate sync (elapsed since last resume sync: ${elapsed}ms)`);
|
||||
this.lastResumeSyncTime_ = Date.now();
|
||||
|
||||
void reg.scheduleSync(0, null, true);
|
||||
} else {
|
||||
logger.info(`onAppStateChange_: App became active but skipping sync - minimum interval not reached (${elapsed}ms < ${minResumeSyncIntervalMs}ms)`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
|
||||
Reference in New Issue
Block a user