feat: add permission-denied dialog for macOS network/mic access
When macOS denies network access (PermissionDenied), show a localized dialog explaining how to grant permission in System Settings, with an 'Open System Settings' button that opens directly to the Local Network privacy pane. Also adds author info (Edison Jwa) to About dialog and moves diagnostics button from AppBar into About dialog.
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
"@aboutVersion": {
|
||||
"placeholders": { "version": { "type": "String" } }
|
||||
},
|
||||
"aboutAuthor": "Author: Edison Jwa",
|
||||
"aboutNonAffiliation": "Chanora is independent and is not affiliated with, endorsed by, sponsored by, or officially associated with TeamSpeak.",
|
||||
"aboutLicenseHeading": "License",
|
||||
"aboutLicenseBody": "Chanora is dual-licensed under the Apache License, Version 2.0 or the MIT License, at your option. The full license texts ship as LICENSE-APACHE and LICENSE-MIT at the repository root.",
|
||||
@@ -154,5 +155,11 @@
|
||||
"placeholders": {
|
||||
"message": { "type": "String" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"networkPermissionTitle": "Network Permission Required",
|
||||
"networkPermissionBody": "Chanora needs permission to access the network. On macOS, go to System Settings → Privacy & Security → Local Network and enable Chanora, then try again.",
|
||||
"networkPermissionOpenSettings": "Open System Settings",
|
||||
"microphonePermissionTitle": "Microphone Permission Required",
|
||||
"microphonePermissionBody": "Chanora needs permission to access the microphone. On macOS, go to System Settings → Privacy & Security → Microphone and enable Chanora.",
|
||||
"permissionDenied": "Permission Denied"
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"diagnosticsAction": "诊断信息",
|
||||
"aboutAction": "关于",
|
||||
"aboutVersion": "版本 {version}",
|
||||
"aboutAuthor": "作者: Edison Jwa",
|
||||
"aboutNonAffiliation": "Chanora 是独立项目,与 TeamSpeak 之间不存在任何附属、认可、赞助或官方关联关系。",
|
||||
"aboutLicenseHeading": "许可协议",
|
||||
"aboutLicenseBody": "Chanora 采用 Apache License 2.0 或 MIT License 双协议授权,使用者可任选其一。完整协议文本以 LICENSE-APACHE 与 LICENSE-MIT 形式随仓库一同分发。",
|
||||
@@ -111,5 +112,11 @@
|
||||
"placeholders": {
|
||||
"message": { "type": "String" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"networkPermissionTitle": "需要网络权限",
|
||||
"networkPermissionBody": "Chanora 需要网络访问权限。请前往系统设置 → 隐私与安全性 → 本地网络,启用 Chanora,然后重试。",
|
||||
"networkPermissionOpenSettings": "打开系统设置",
|
||||
"microphonePermissionTitle": "需要麦克风权限",
|
||||
"microphonePermissionBody": "Chanora 需要麦克风访问权限。请前往系统设置 → 隐私与安全性 → 麦克风,启用 Chanora。",
|
||||
"permissionDenied": "权限被拒绝"
|
||||
}
|
||||
|
||||
@@ -181,6 +181,12 @@ abstract class AppL10n {
|
||||
/// **'Version {version}'**
|
||||
String aboutVersion(String version);
|
||||
|
||||
/// No description provided for @aboutAuthor.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Author: Edison Jwa'**
|
||||
String get aboutAuthor;
|
||||
|
||||
/// No description provided for @aboutNonAffiliation.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -684,6 +690,42 @@ abstract class AppL10n {
|
||||
/// In en, this message translates to:
|
||||
/// **'Could not join channel: {message}'**
|
||||
String channelJoinFailedGeneric(String message);
|
||||
|
||||
/// No description provided for @networkPermissionTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Network Permission Required'**
|
||||
String get networkPermissionTitle;
|
||||
|
||||
/// No description provided for @networkPermissionBody.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Chanora needs permission to access the network. On macOS, go to System Settings → Privacy & Security → Local Network and enable Chanora, then try again.'**
|
||||
String get networkPermissionBody;
|
||||
|
||||
/// No description provided for @networkPermissionOpenSettings.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Open System Settings'**
|
||||
String get networkPermissionOpenSettings;
|
||||
|
||||
/// No description provided for @microphonePermissionTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Microphone Permission Required'**
|
||||
String get microphonePermissionTitle;
|
||||
|
||||
/// No description provided for @microphonePermissionBody.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Chanora needs permission to access the microphone. On macOS, go to System Settings → Privacy & Security → Microphone and enable Chanora.'**
|
||||
String get microphonePermissionBody;
|
||||
|
||||
/// No description provided for @permissionDenied.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Permission Denied'**
|
||||
String get permissionDenied;
|
||||
}
|
||||
|
||||
class _AppL10nDelegate extends LocalizationsDelegate<AppL10n> {
|
||||
|
||||
@@ -54,6 +54,9 @@ class AppL10nEn extends AppL10n {
|
||||
return 'Version $version';
|
||||
}
|
||||
|
||||
@override
|
||||
String get aboutAuthor => 'Author: Edison Jwa';
|
||||
|
||||
@override
|
||||
String get aboutNonAffiliation =>
|
||||
'Chanora is independent and is not affiliated with, endorsed by, sponsored by, or officially associated with TeamSpeak.';
|
||||
@@ -339,4 +342,24 @@ class AppL10nEn extends AppL10n {
|
||||
String channelJoinFailedGeneric(String message) {
|
||||
return 'Could not join channel: $message';
|
||||
}
|
||||
|
||||
@override
|
||||
String get networkPermissionTitle => 'Network Permission Required';
|
||||
|
||||
@override
|
||||
String get networkPermissionBody =>
|
||||
'Chanora needs permission to access the network. On macOS, go to System Settings → Privacy & Security → Local Network and enable Chanora, then try again.';
|
||||
|
||||
@override
|
||||
String get networkPermissionOpenSettings => 'Open System Settings';
|
||||
|
||||
@override
|
||||
String get microphonePermissionTitle => 'Microphone Permission Required';
|
||||
|
||||
@override
|
||||
String get microphonePermissionBody =>
|
||||
'Chanora needs permission to access the microphone. On macOS, go to System Settings → Privacy & Security → Microphone and enable Chanora.';
|
||||
|
||||
@override
|
||||
String get permissionDenied => 'Permission Denied';
|
||||
}
|
||||
|
||||
@@ -52,6 +52,9 @@ class AppL10nZh extends AppL10n {
|
||||
return '版本 $version';
|
||||
}
|
||||
|
||||
@override
|
||||
String get aboutAuthor => '作者: Edison Jwa';
|
||||
|
||||
@override
|
||||
String get aboutNonAffiliation =>
|
||||
'Chanora 是独立项目,与 TeamSpeak 之间不存在任何附属、认可、赞助或官方关联关系。';
|
||||
@@ -331,4 +334,24 @@ class AppL10nZh extends AppL10n {
|
||||
String channelJoinFailedGeneric(String message) {
|
||||
return '无法加入频道:$message';
|
||||
}
|
||||
|
||||
@override
|
||||
String get networkPermissionTitle => '需要网络权限';
|
||||
|
||||
@override
|
||||
String get networkPermissionBody =>
|
||||
'Chanora 需要网络访问权限。请前往系统设置 → 隐私与安全性 → 本地网络,启用 Chanora,然后重试。';
|
||||
|
||||
@override
|
||||
String get networkPermissionOpenSettings => '打开系统设置';
|
||||
|
||||
@override
|
||||
String get microphonePermissionTitle => '需要麦克风权限';
|
||||
|
||||
@override
|
||||
String get microphonePermissionBody =>
|
||||
'Chanora 需要麦克风访问权限。请前往系统设置 → 隐私与安全性 → 麦克风,启用 Chanora。';
|
||||
|
||||
@override
|
||||
String get permissionDenied => '权限被拒绝';
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'dart:io' show Platform;
|
||||
import 'dart:io' show Platform, Process;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -429,13 +429,54 @@ class _BetaHomeState extends State<_BetaHome> {
|
||||
});
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
final errorStr = e.toString();
|
||||
if (errorStr.contains('PermissionDenied') ||
|
||||
errorStr.contains('Operation not permitted')) {
|
||||
_showPermissionDeniedDialog(errorStr);
|
||||
}
|
||||
setState(() {
|
||||
_phase = _Phase.idle;
|
||||
_error = e.toString();
|
||||
_error = errorStr;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _showPermissionDeniedDialog(String rawError) {
|
||||
final l10n = AppL10n.of(context);
|
||||
final isNetwork =
|
||||
rawError.contains('9987') || rawError.contains('connect');
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title:
|
||||
Text(isNetwork ? l10n.networkPermissionTitle : l10n.permissionDenied),
|
||||
content: Text(
|
||||
isNetwork ? l10n.networkPermissionBody : l10n.microphonePermissionBody),
|
||||
actions: [
|
||||
if (Platform.isMacOS)
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(ctx);
|
||||
try {
|
||||
Process.run(
|
||||
'open',
|
||||
[
|
||||
'x-apple.systempreferences:com.apple.preference.security?Privacy_LocalNetwork',
|
||||
],
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
child: Text(l10n.networkPermissionOpenSettings),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ignore: unused_element
|
||||
Future<void> _setPtt(bool active) async {
|
||||
try {
|
||||
@@ -853,6 +894,8 @@ class _BetaHomeState extends State<_BetaHome> {
|
||||
l10n.aboutVersion(_kAppVersion),
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(l10n.aboutAuthor, style: theme.textTheme.bodySmall),
|
||||
const SizedBox(height: 16),
|
||||
Text(l10n.aboutNonAffiliation, style: theme.textTheme.bodyMedium),
|
||||
const SizedBox(height: 12),
|
||||
@@ -870,6 +913,13 @@ class _BetaHomeState extends State<_BetaHome> {
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(ctx).pop();
|
||||
await _onShowDiagnostics(context);
|
||||
},
|
||||
child: Text(l10n.diagnosticsAction),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(),
|
||||
child: Text(l10n.closeAction),
|
||||
@@ -1001,11 +1051,6 @@ class _BetaHomeState extends State<_BetaHome> {
|
||||
icon: const Icon(Icons.info_outline),
|
||||
onPressed: () => _onShowAbout(context),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: l10n.diagnosticsAction,
|
||||
icon: const Icon(Icons.bug_report_outlined),
|
||||
onPressed: () => _onShowDiagnostics(context),
|
||||
),
|
||||
if (_phase == _Phase.connected) ...[
|
||||
// The previous Refresh action (Icons.refresh + _onRefresh)
|
||||
// was removed in v1.0.0-rc.8 — the snapshot stream the
|
||||
|
||||
BIN
Binary file not shown.
Reference in New Issue
Block a user