fix(electron): optimize meeting privacy settings (#12530)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Added support for requesting screen recording permission on macOS in addition to microphone permission.
  - Introduced a new "Permission issues" section in meeting privacy settings, including a button to restart the app if permission status is not updated.
- **Improvements**
  - Unified permission handling for screen and microphone settings, simplifying the user experience.
  - Added new localized strings for enhanced clarity regarding permission issues and app restart instructions.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
pengx17
2025-05-27 11:08:06 +00:00
parent d6476db64d
commit 4ad008f712
6 changed files with 74 additions and 29 deletions

View File

@@ -471,6 +471,21 @@ function setupMediaListeners() {
);
}
function askForScreenRecordingPermission() {
if (!isMacOS()) {
return false;
}
try {
const ShareableContent = require('@affine/native').ShareableContent;
// this will trigger the permission prompt
new ShareableContent();
return true;
} catch (error) {
logger.error('failed to ask for screen recording permission', error);
}
return false;
}
// will be called when the app is ready or when the user has enabled the recording feature in settings
export function setupRecordingFeature() {
if (!MeetingsSettingsState.value.enabled || !checkCanRecordMeeting()) {
@@ -788,10 +803,15 @@ export const checkMeetingPermissions = () => {
) as Record<(typeof mediaTypes)[number], boolean>;
};
export const askForMeetingPermission = async (type: 'microphone') => {
export const askForMeetingPermission = async (
type: 'microphone' | 'screen'
) => {
if (!isMacOS()) {
return false;
}
if (type === 'screen') {
return askForScreenRecordingPermission();
}
return systemPreferences.askForMediaAccess(type);
};

View File

@@ -77,18 +77,17 @@ export const recordingHandlers = {
checkMeetingPermissions: async () => {
return checkMeetingPermissions();
},
askForMeetingPermission: async (_, type: 'microphone') => {
askForMeetingPermission: async (_, type: 'screen' | 'microphone') => {
return askForMeetingPermission(type);
},
showRecordingPermissionSetting: async (_, type: 'screen' | 'microphone') => {
const urlMap = {
screen: 'Privacy_ScreenCapture',
microphone: 'Privacy_Microphone',
};
if (isMacOS()) {
return shell.openExternal(
`x-apple.systempreferences:com.apple.preference.security?${urlMap[type]}`
);
const urlMap = {
screen: 'Privacy_ScreenCapture',
microphone: 'Privacy_Microphone',
};
const url = `x-apple.systempreferences:com.apple.preference.security?${urlMap[type]}`;
return shell.openExternal(url);
}
// this only available on MacOS
return false;

View File

@@ -1,4 +1,5 @@
import {
Button,
IconButton,
Menu,
MenuItem,
@@ -11,6 +12,7 @@ import {
SettingWrapper,
} from '@affine/component/setting-components';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { DesktopApiService } from '@affine/core/modules/desktop-api';
import { MeetingSettingsService } from '@affine/core/modules/media/services/meeting-settings';
import type { MeetingSettingsSchema } from '@affine/electron/main/shared-state-schema';
import { Trans, useI18n } from '@affine/i18n';
@@ -133,6 +135,7 @@ const PermissionSettingRow = ({
const MeetingsSettingsMain = () => {
const t = useI18n();
const meetingSettingsService = useService(MeetingSettingsService);
const desktopApiService = useService(DesktopApiService);
const settings = useLiveData(meetingSettingsService.settings$);
const [recordingFeatureAvailable, setRecordingFeatureAvailable] =
useState(false);
@@ -175,26 +178,22 @@ const MeetingsSettingsMain = () => {
[meetingSettingsService]
);
const handleOpenScreenRecordingPermissionSetting =
useAsyncCallback(async () => {
await meetingSettingsService.showRecordingPermissionSetting('screen');
}, [meetingSettingsService]);
const handleOpenMicrophoneRecordingPermissionSetting =
useAsyncCallback(async () => {
const result =
await meetingSettingsService.askForMeetingPermission('microphone');
if (!result) {
await meetingSettingsService.showRecordingPermissionSetting(
'microphone'
);
}
}, [meetingSettingsService]);
const handleOpenPermissionSetting = useAsyncCallback(
async (type: 'screen' | 'microphone') => {
await meetingSettingsService.askForMeetingPermission(type);
await meetingSettingsService.showRecordingPermissionSetting(type);
},
[meetingSettingsService]
);
const handleOpenSavedRecordings = useAsyncCallback(async () => {
await meetingSettingsService.openSavedRecordings();
}, [meetingSettingsService]);
const handleRestartApp = useAsyncCallback(async () => {
await desktopApiService.handler.ui.restartApp();
}, [desktopApiService]);
return (
<div className={styles.meetingWrapper}>
<SettingHeader
@@ -304,8 +303,8 @@ const MeetingsSettingsMain = () => {
descriptionKey="com.affine.settings.meetings.privacy.screen-system-audio-recording.description"
permissionSettingKey="com.affine.settings.meetings.privacy.screen-system-audio-recording.permission-setting"
hasPermission={permissions?.screen || false}
onOpenPermissionSetting={
handleOpenScreenRecordingPermissionSetting
onOpenPermissionSetting={() =>
handleOpenPermissionSetting('screen')
}
/>
<PermissionSettingRow
@@ -313,11 +312,23 @@ const MeetingsSettingsMain = () => {
descriptionKey="com.affine.settings.meetings.privacy.microphone.description"
permissionSettingKey="com.affine.settings.meetings.privacy.microphone.permission-setting"
hasPermission={permissions?.microphone || false}
onOpenPermissionSetting={
handleOpenMicrophoneRecordingPermissionSetting
onOpenPermissionSetting={() =>
handleOpenPermissionSetting('microphone')
}
/>
</SettingWrapper>
<SettingWrapper>
<SettingRow
name={t['com.affine.settings.meetings.privacy.issues']()}
desc={t[
'com.affine.settings.meetings.privacy.issues.description'
]()}
>
<Button onClick={handleRestartApp}>
{t['com.affine.settings.meetings.privacy.issues.restart']()}
</Button>
</SettingRow>
</SettingWrapper>
</>
)}
</div>

View File

@@ -130,7 +130,7 @@ export class MeetingSettingsService extends Service {
);
}
async askForMeetingPermission(type: 'microphone') {
async askForMeetingPermission(type: 'microphone' | 'screen') {
return this.desktopApiService?.handler.recording.askForMeetingPermission(
type
);

View File

@@ -5598,6 +5598,18 @@ export function useAFFiNEI18N(): {
* `Click to allow`
*/
["com.affine.settings.meetings.privacy.microphone.permission-setting"](): string;
/**
* `Permission issues`
*/
["com.affine.settings.meetings.privacy.issues"](): string;
/**
* `Permissions are granted but the status isn't updated? Restart the app to refresh permissions.`
*/
["com.affine.settings.meetings.privacy.issues.description"](): string;
/**
* `Restart App`
*/
["com.affine.settings.meetings.privacy.issues.restart"](): string;
/**
* `Do nothing`
*/

View File

@@ -1398,6 +1398,9 @@
"com.affine.settings.meetings.privacy.microphone": "Microphone",
"com.affine.settings.meetings.privacy.microphone.description": "The Meeting feature requires permission to be used.",
"com.affine.settings.meetings.privacy.microphone.permission-setting": "Click to allow",
"com.affine.settings.meetings.privacy.issues": "Permission issues",
"com.affine.settings.meetings.privacy.issues.description": "Permissions are granted but the status isn't updated? Restart the app to refresh permissions.",
"com.affine.settings.meetings.privacy.issues.restart": "Restart App",
"com.affine.settings.meetings.record.recording-mode.none": "Do nothing",
"com.affine.settings.meetings.record.recording-mode.auto-start": "Auto start recording",
"com.affine.settings.meetings.record.recording-mode.prompt": "Show a recording prompt",