Skip to content

Commit a153f03

Browse files
authored
Merge pull request #57660 from wildan-m/wildan/fix/52668-delay-2nd-location-setting
Fix location Permission for Desktop
2 parents 9738a1b + f15e586 commit a153f03

File tree

14 files changed

+401
-49
lines changed

14 files changed

+401
-49
lines changed

desktop/ELECTRON_EVENTS.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const ELECTRON_EVENTS = {
1414
DOWNLOAD_FAILED: 'download-started',
1515
DOWNLOAD_CANCELED: 'download-canceled',
1616
SILENT_UPDATE: 'silent-update',
17+
OPEN_LOCATION_SETTING: 'open-location-setting',
1718
} as const;
1819

1920
export default ELECTRON_EVENTS;

desktop/contextBridge.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const WHITELIST_CHANNELS_RENDERER_TO_MAIN = [
1818
ELECTRON_EVENTS.LOCALE_UPDATED,
1919
ELECTRON_EVENTS.DOWNLOAD,
2020
ELECTRON_EVENTS.SILENT_UPDATE,
21+
ELECTRON_EVENTS.OPEN_LOCATION_SETTING,
2122
] as const;
2223

2324
const WHITELIST_CHANNELS_MAIN_TO_RENDERER = [

desktop/main.ts

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {exec} from 'child_process';
12
import {app, BrowserWindow, clipboard, dialog, ipcMain, Menu, shell} from 'electron';
23
import type {BaseWindow, BrowserView, MenuItem, MenuItemConstructorOptions, WebContents, WebviewTag} from 'electron';
34
import contextMenu from 'electron-context-menu';
@@ -6,7 +7,7 @@ import type {ElectronLog} from 'electron-log';
67
import {autoUpdater} from 'electron-updater';
78
import {machineId} from 'node-machine-id';
89
import checkForUpdates from '@libs/checkForUpdates';
9-
import * as Localize from '@libs/Localize';
10+
import {translate} from '@libs/Localize';
1011
import CONFIG from '@src/CONFIG';
1112
import CONST from '@src/CONST';
1213
import type {TranslationPaths} from '@src/languages/types';
@@ -71,20 +72,20 @@ function pasteAsPlainText(browserWindow: BrowserWindow | BrowserView | WebviewTa
7172
function createContextMenu(preferredLocale: Locale = LOCALES.DEFAULT): () => void {
7273
return contextMenu({
7374
labels: {
74-
cut: Localize.translate(preferredLocale, 'desktopApplicationMenu.cut'),
75-
paste: Localize.translate(preferredLocale, 'desktopApplicationMenu.paste'),
76-
copy: Localize.translate(preferredLocale, 'desktopApplicationMenu.copy'),
75+
cut: translate(preferredLocale, 'desktopApplicationMenu.cut'),
76+
paste: translate(preferredLocale, 'desktopApplicationMenu.paste'),
77+
copy: translate(preferredLocale, 'desktopApplicationMenu.copy'),
7778
},
7879
append: (defaultActions, parameters, browserWindow) => [
7980
{
8081
// Only enable the menu item for Editable context which supports paste
8182
visible: parameters.isEditable && parameters.editFlags.canPaste,
8283
role: 'pasteAndMatchStyle',
8384
accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AND_MATCH_STYLE,
84-
label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAndMatchStyle'),
85+
label: translate(preferredLocale, 'desktopApplicationMenu.pasteAndMatchStyle'),
8586
},
8687
{
87-
label: Localize.translate(preferredLocale, 'desktopApplicationMenu.pasteAsPlainText'),
88+
label: translate(preferredLocale, 'desktopApplicationMenu.pasteAsPlainText'),
8889
visible: parameters.isEditable && parameters.editFlags.canPaste && clipboard.readText().length > 0,
8990
accelerator: DESKTOP_SHORTCUT_ACCELERATOR.PASTE_AS_PLAIN_TEXT,
9091
click: () => pasteAsPlainText(browserWindow),
@@ -126,7 +127,7 @@ let hasUpdate = false;
126127
let downloadedVersion: string;
127128
let isSilentUpdating = false;
128129

129-
// Note that we have to subscribe to this separately and cannot use Localize.translateLocal,
130+
// Note that we have to subscribe to this separately and cannot use translateLocal,
130131
// because the only way code can be shared between the main and renderer processes at runtime is via the context bridge
131132
// So we track preferredLocale separately via ELECTRON_EVENTS.LOCALE_UPDATED
132133
const preferredLocale: Locale = CONST.LOCALES.DEFAULT;
@@ -165,23 +166,23 @@ const manuallyCheckForUpdates = (menuItem?: MenuItem, browserWindow?: BaseWindow
165166
if (downloadPromise) {
166167
dialog.showMessageBox(browserWindow, {
167168
type: 'info',
168-
message: Localize.translate(preferredLocale, 'checkForUpdatesModal.available.title'),
169-
detail: Localize.translate(preferredLocale, 'checkForUpdatesModal.available.message', {isSilentUpdating}),
170-
buttons: [Localize.translate(preferredLocale, 'checkForUpdatesModal.available.soundsGood')],
169+
message: translate(preferredLocale, 'checkForUpdatesModal.available.title'),
170+
detail: translate(preferredLocale, 'checkForUpdatesModal.available.message', {isSilentUpdating}),
171+
buttons: [translate(preferredLocale, 'checkForUpdatesModal.available.soundsGood')],
171172
});
172173
} else if (result && 'error' in result && result.error) {
173174
dialog.showMessageBox(browserWindow, {
174175
type: 'error',
175-
message: Localize.translate(preferredLocale, 'checkForUpdatesModal.error.title'),
176-
detail: Localize.translate(preferredLocale, 'checkForUpdatesModal.error.message'),
177-
buttons: [Localize.translate(preferredLocale, 'checkForUpdatesModal.notAvailable.okay')],
176+
message: translate(preferredLocale, 'checkForUpdatesModal.error.title'),
177+
detail: translate(preferredLocale, 'checkForUpdatesModal.error.message'),
178+
buttons: [translate(preferredLocale, 'checkForUpdatesModal.notAvailable.okay')],
178179
});
179180
} else {
180181
dialog.showMessageBox(browserWindow, {
181182
type: 'info',
182-
message: Localize.translate(preferredLocale, 'checkForUpdatesModal.notAvailable.title'),
183-
detail: Localize.translate(preferredLocale, 'checkForUpdatesModal.notAvailable.message'),
184-
buttons: [Localize.translate(preferredLocale, 'checkForUpdatesModal.notAvailable.okay')],
183+
message: translate(preferredLocale, 'checkForUpdatesModal.notAvailable.title'),
184+
detail: translate(preferredLocale, 'checkForUpdatesModal.notAvailable.message'),
185+
buttons: [translate(preferredLocale, 'checkForUpdatesModal.notAvailable.okay')],
185186
cancelId: 2,
186187
});
187188
}
@@ -242,7 +243,7 @@ const localizeMenuItems = (submenu: MenuItemConstructorOptions[], updatedLocale:
242243
submenu.map((menu) => {
243244
const newMenu: MenuItemConstructorOptions = {...menu};
244245
if (menu.id) {
245-
const labelTranslation = Localize.translate(updatedLocale, `desktopApplicationMenu.${menu.id}` as TranslationPaths);
246+
const labelTranslation = translate(updatedLocale, `desktopApplicationMenu.${menu.id}` as TranslationPaths);
246247
if (labelTranslation) {
247248
newMenu.label = labelTranslation;
248249
}
@@ -310,7 +311,25 @@ const mainWindow = (): Promise<void> => {
310311
});
311312

312313
ipcMain.handle(ELECTRON_EVENTS.REQUEST_DEVICE_ID, () => machineId());
314+
ipcMain.handle(ELECTRON_EVENTS.OPEN_LOCATION_SETTING, () => {
315+
if (process.platform !== 'darwin') {
316+
// Platform not supported for location settings
317+
return Promise.resolve(undefined);
318+
}
313319

320+
return new Promise((resolve, reject) => {
321+
const command = 'open x-apple.systempreferences:com.apple.preference.security?Privacy_Location';
322+
323+
exec(command, (error) => {
324+
if (error) {
325+
console.error('Error opening location settings:', error);
326+
reject(error);
327+
return;
328+
}
329+
resolve(undefined);
330+
});
331+
});
332+
});
314333
/*
315334
* The default origin of our Electron app is app://- instead of https://new.expensify.com or https://staging.new.expensify.com
316335
* This causes CORS errors because the referer and origin headers are wrong and the API responds with an Access-Control-Allow-Origin that doesn't match app://-
@@ -353,14 +372,14 @@ const mainWindow = (): Promise<void> => {
353372
const initialMenuTemplate: MenuItemConstructorOptions[] = [
354373
{
355374
id: 'mainMenu',
356-
label: Localize.translate(preferredLocale, `desktopApplicationMenu.mainMenu`),
375+
label: translate(preferredLocale, `desktopApplicationMenu.mainMenu`),
357376
submenu: [
358377
{id: 'about', role: 'about'},
359-
{id: 'update', label: Localize.translate(preferredLocale, `desktopApplicationMenu.update`), click: quitAndInstallWithUpdate, visible: false},
360-
{id: 'checkForUpdates', label: Localize.translate(preferredLocale, `desktopApplicationMenu.checkForUpdates`), click: manuallyCheckForUpdates},
378+
{id: 'update', label: translate(preferredLocale, `desktopApplicationMenu.update`), click: quitAndInstallWithUpdate, visible: false},
379+
{id: 'checkForUpdates', label: translate(preferredLocale, `desktopApplicationMenu.checkForUpdates`), click: manuallyCheckForUpdates},
361380
{
362381
id: 'viewShortcuts',
363-
label: Localize.translate(preferredLocale, `desktopApplicationMenu.viewShortcuts`),
382+
label: translate(preferredLocale, `desktopApplicationMenu.viewShortcuts`),
364383
accelerator: 'CmdOrCtrl+J',
365384
click: () => {
366385
showKeyboardShortcutsPage(browserWindow);
@@ -378,12 +397,12 @@ const mainWindow = (): Promise<void> => {
378397
},
379398
{
380399
id: 'fileMenu',
381-
label: Localize.translate(preferredLocale, `desktopApplicationMenu.fileMenu`),
400+
label: translate(preferredLocale, `desktopApplicationMenu.fileMenu`),
382401
submenu: [{id: 'closeWindow', role: 'close', accelerator: 'Cmd+w'}],
383402
},
384403
{
385404
id: 'editMenu',
386-
label: Localize.translate(preferredLocale, `desktopApplicationMenu.editMenu`),
405+
label: translate(preferredLocale, `desktopApplicationMenu.editMenu`),
387406
submenu: [
388407
{id: 'undo', role: 'undo'},
389408
{id: 'redo', role: 'redo'},
@@ -406,7 +425,7 @@ const mainWindow = (): Promise<void> => {
406425
{type: 'separator'},
407426
{
408427
id: 'speechSubmenu',
409-
label: Localize.translate(preferredLocale, `desktopApplicationMenu.speechSubmenu`),
428+
label: translate(preferredLocale, `desktopApplicationMenu.speechSubmenu`),
410429
submenu: [
411430
{id: 'startSpeaking', role: 'startSpeaking'},
412431
{id: 'stopSpeaking', role: 'stopSpeaking'},
@@ -416,7 +435,7 @@ const mainWindow = (): Promise<void> => {
416435
},
417436
{
418437
id: 'viewMenu',
419-
label: Localize.translate(preferredLocale, `desktopApplicationMenu.viewMenu`),
438+
label: translate(preferredLocale, `desktopApplicationMenu.viewMenu`),
420439
submenu: [
421440
{id: 'reload', role: 'reload'},
422441
{id: 'forceReload', role: 'forceReload'},
@@ -431,7 +450,7 @@ const mainWindow = (): Promise<void> => {
431450
},
432451
{
433452
id: 'historyMenu',
434-
label: Localize.translate(preferredLocale, `desktopApplicationMenu.historyMenu`),
453+
label: translate(preferredLocale, `desktopApplicationMenu.historyMenu`),
435454
submenu: [
436455
{
437456
id: 'back',
@@ -472,33 +491,33 @@ const mainWindow = (): Promise<void> => {
472491
},
473492
{
474493
id: 'helpMenu',
475-
label: Localize.translate(preferredLocale, `desktopApplicationMenu.helpMenu`),
494+
label: translate(preferredLocale, `desktopApplicationMenu.helpMenu`),
476495
role: 'help',
477496
submenu: [
478497
{
479498
id: 'learnMore',
480-
label: Localize.translate(preferredLocale, `desktopApplicationMenu.learnMore`),
499+
label: translate(preferredLocale, `desktopApplicationMenu.learnMore`),
481500
click: () => {
482501
shell.openExternal(CONST.MENU_HELP_URLS.LEARN_MORE);
483502
},
484503
},
485504
{
486505
id: 'documentation',
487-
label: Localize.translate(preferredLocale, `desktopApplicationMenu.documentation`),
506+
label: translate(preferredLocale, `desktopApplicationMenu.documentation`),
488507
click: () => {
489508
shell.openExternal(CONST.MENU_HELP_URLS.DOCUMENTATION);
490509
},
491510
},
492511
{
493512
id: 'communityDiscussions',
494-
label: Localize.translate(preferredLocale, `desktopApplicationMenu.communityDiscussions`),
513+
label: translate(preferredLocale, `desktopApplicationMenu.communityDiscussions`),
495514
click: () => {
496515
shell.openExternal(CONST.MENU_HELP_URLS.COMMUNITY_DISCUSSIONS);
497516
},
498517
},
499518
{
500519
id: 'searchIssues',
501-
label: Localize.translate(preferredLocale, `desktopApplicationMenu.searchIssues`),
520+
label: translate(preferredLocale, `desktopApplicationMenu.searchIssues`),
502521
click: () => {
503522
shell.openExternal(CONST.MENU_HELP_URLS.SEARCH_ISSUES);
504523
},

src/CONST.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,6 +1464,7 @@ const CONST = {
14641464
USE_DEBOUNCED_STATE_DELAY: 300,
14651465
LIST_SCROLLING_DEBOUNCE_TIME: 200,
14661466
PUSHER_PING_PONG: 'pusher_ping_pong',
1467+
LOCATION_UPDATE_INTERVAL: 5000,
14671468
},
14681469
PRIORITY_MODE: {
14691470
GSD: 'gsd',

src/components/ConfirmContent.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ type ConfirmContentProps = {
9898

9999
/** Whether the modal is visibile */
100100
isVisible: boolean;
101+
102+
/** Whether the confirm button is loading */
103+
isConfirmLoading?: boolean;
101104
};
102105

103106
function ConfirmContent({
@@ -127,6 +130,7 @@ function ConfirmContent({
127130
titleContainerStyles,
128131
shouldReverseStackedButtons = false,
129132
isVisible,
133+
isConfirmLoading,
130134
}: ConfirmContentProps) {
131135
const styles = useThemeStyles();
132136
const {translate} = useLocalize();
@@ -209,6 +213,7 @@ function ConfirmContent({
209213
text={confirmText || translate('common.yes')}
210214
accessibilityLabel={confirmText || translate('common.yes')}
211215
isDisabled={isOffline && shouldDisableConfirmButtonWhenOffline}
216+
isLoading={isConfirmLoading}
212217
/>
213218
{shouldShowCancelButton && !shouldReverseStackedButtons && (
214219
<Button
@@ -237,6 +242,7 @@ function ConfirmContent({
237242
isPressOnEnterActive={isVisible}
238243
text={confirmText || translate('common.yes')}
239244
isDisabled={isOffline && shouldDisableConfirmButtonWhenOffline}
245+
isLoading={isConfirmLoading}
240246
/>
241247
</View>
242248
)}

src/components/ConfirmModal.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ type ConfirmModalProps = {
102102

103103
/** How to re-focus after the modal is dismissed */
104104
restoreFocusType?: BaseModalProps['restoreFocusType'];
105+
106+
/** Whether the confirm button is loading */
107+
isConfirmLoading?: boolean;
105108
};
106109

107110
function ConfirmModal({
@@ -135,6 +138,7 @@ function ConfirmModal({
135138
shouldReverseStackedButtons,
136139
shouldEnableNewFocusManagement,
137140
restoreFocusType,
141+
isConfirmLoading,
138142
}: ConfirmModalProps) {
139143
// We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to use the correct modal type
140144
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
@@ -182,6 +186,7 @@ function ConfirmModal({
182186
shouldStackButtons={shouldStackButtons}
183187
shouldReverseStackedButtons={shouldReverseStackedButtons}
184188
image={image}
189+
isConfirmLoading={isConfirmLoading}
185190
/>
186191
</Modal>
187192
);

src/components/LocationPermissionModal/index.android.tsx

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import useThemeStyles from '@hooks/useThemeStyles';
88
import {getLocationPermission, requestLocationPermission} from '@pages/iou/request/step/IOURequestStepScan/LocationPermission';
99
import type {LocationPermissionModalProps} from './types';
1010

11-
function LocationPermissionModal({startPermissionFlow, resetPermissionFlow, onDeny, onGrant}: LocationPermissionModalProps) {
11+
function LocationPermissionModal({startPermissionFlow, resetPermissionFlow, onDeny, onGrant, onInitialGetLocationCompleted}: LocationPermissionModalProps) {
1212
const [hasError, setHasError] = useState(false);
1313
const [showModal, setShowModal] = useState(false);
14+
const [isLoading, setIsLoading] = useState(false);
1415

1516
const styles = useThemeStyles();
1617
const {translate} = useLocalize();
@@ -21,6 +22,7 @@ function LocationPermissionModal({startPermissionFlow, resetPermissionFlow, onDe
2122
}
2223

2324
getLocationPermission().then((status) => {
25+
onInitialGetLocationCompleted?.();
2426
if (status === RESULTS.GRANTED || status === RESULTS.LIMITED) {
2527
return onGrant();
2628
}
@@ -32,6 +34,7 @@ function LocationPermissionModal({startPermissionFlow, resetPermissionFlow, onDe
3234
}, [startPermissionFlow]);
3335

3436
const handledBlockedPermission = (cb: () => void) => () => {
37+
setIsLoading(true);
3538
if (hasError && Linking.openSettings) {
3639
Linking.openSettings();
3740
setShowModal(false);
@@ -43,18 +46,22 @@ function LocationPermissionModal({startPermissionFlow, resetPermissionFlow, onDe
4346
};
4447

4548
const grantLocationPermission = handledBlockedPermission(() => {
46-
requestLocationPermission().then((status) => {
47-
if (status === RESULTS.GRANTED || status === RESULTS.LIMITED) {
48-
onGrant();
49-
} else if (status === RESULTS.BLOCKED) {
50-
setHasError(true);
51-
return;
52-
} else {
53-
onDeny();
54-
}
55-
setShowModal(false);
56-
setHasError(false);
57-
});
49+
requestLocationPermission()
50+
.then((status) => {
51+
if (status === RESULTS.GRANTED || status === RESULTS.LIMITED) {
52+
onGrant();
53+
} else if (status === RESULTS.BLOCKED) {
54+
setHasError(true);
55+
return;
56+
} else {
57+
onDeny();
58+
}
59+
setShowModal(false);
60+
setHasError(false);
61+
})
62+
.finally(() => {
63+
setIsLoading(false);
64+
});
5865
});
5966

6067
const skipLocationPermission = () => {
@@ -87,6 +94,7 @@ function LocationPermissionModal({startPermissionFlow, resetPermissionFlow, onDe
8794
iconHeight={120}
8895
shouldCenterIcon
8996
shouldReverseStackedButtons
97+
isConfirmLoading={isLoading}
9098
/>
9199
);
92100
}

0 commit comments

Comments
 (0)