diff --git a/Cargo.lock b/Cargo.lock index 90d3158..5c7db61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -345,7 +345,7 @@ dependencies = [ [[package]] name = "chanora_audio" -version = "0.0.1-pre" +version = "0.2.0-beta.1" dependencies = [ "audiopus", "chanora_protocol", @@ -368,7 +368,7 @@ dependencies = [ [[package]] name = "chanora_bridge" -version = "0.0.1-pre" +version = "0.2.0-beta.1" dependencies = [ "chanora_core", "chanora_protocol", @@ -386,7 +386,7 @@ dependencies = [ [[package]] name = "chanora_core" -version = "0.0.1-pre" +version = "0.2.0-beta.1" dependencies = [ "chanora_audio", "chanora_diagnostics", @@ -401,7 +401,7 @@ dependencies = [ [[package]] name = "chanora_diagnostics" -version = "0.0.1-pre" +version = "0.2.0-beta.1" dependencies = [ "thiserror 2.0.18", "tracing", @@ -410,7 +410,7 @@ dependencies = [ [[package]] name = "chanora_protocol" -version = "0.0.1-pre" +version = "0.2.0-beta.1" dependencies = [ "async-trait", "futures", @@ -426,7 +426,7 @@ dependencies = [ [[package]] name = "chanora_state" -version = "0.0.1-pre" +version = "0.2.0-beta.1" dependencies = [ "thiserror 2.0.18", "tracing", @@ -434,7 +434,7 @@ dependencies = [ [[package]] name = "chanora_storage" -version = "0.0.1-pre" +version = "0.2.0-beta.1" dependencies = [ "base64", "chacha20poly1305", diff --git a/apps/chanora_flutter/lib/l10n/app_en.arb b/apps/chanora_flutter/lib/l10n/app_en.arb index b150c0a..c06418e 100644 --- a/apps/chanora_flutter/lib/l10n/app_en.arb +++ b/apps/chanora_flutter/lib/l10n/app_en.arb @@ -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" } diff --git a/apps/chanora_flutter/lib/l10n/app_zh.arb b/apps/chanora_flutter/lib/l10n/app_zh.arb index 886d024..bb12acc 100644 --- a/apps/chanora_flutter/lib/l10n/app_zh.arb +++ b/apps/chanora_flutter/lib/l10n/app_zh.arb @@ -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": "权限被拒绝" } diff --git a/apps/chanora_flutter/lib/l10n/generated/app_localizations.dart b/apps/chanora_flutter/lib/l10n/generated/app_localizations.dart index ab6d1c0..2ad8037 100644 --- a/apps/chanora_flutter/lib/l10n/generated/app_localizations.dart +++ b/apps/chanora_flutter/lib/l10n/generated/app_localizations.dart @@ -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 { diff --git a/apps/chanora_flutter/lib/l10n/generated/app_localizations_en.dart b/apps/chanora_flutter/lib/l10n/generated/app_localizations_en.dart index 67c680c..22ebbed 100644 --- a/apps/chanora_flutter/lib/l10n/generated/app_localizations_en.dart +++ b/apps/chanora_flutter/lib/l10n/generated/app_localizations_en.dart @@ -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'; } diff --git a/apps/chanora_flutter/lib/l10n/generated/app_localizations_zh.dart b/apps/chanora_flutter/lib/l10n/generated/app_localizations_zh.dart index 7290070..dc244c2 100644 --- a/apps/chanora_flutter/lib/l10n/generated/app_localizations_zh.dart +++ b/apps/chanora_flutter/lib/l10n/generated/app_localizations_zh.dart @@ -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 => '权限被拒绝'; } diff --git a/apps/chanora_flutter/lib/main.dart b/apps/chanora_flutter/lib/main.dart index 8a099c0..667f343 100644 --- a/apps/chanora_flutter/lib/main.dart +++ b/apps/chanora_flutter/lib/main.dart @@ -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 _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 diff --git a/apps/chanora_flutter/macos/Frameworks/chanora_bridge.framework/Versions/A/chanora_bridge b/apps/chanora_flutter/macos/Frameworks/chanora_bridge.framework/Versions/A/chanora_bridge index 7e20bfa..3027e40 100755 Binary files a/apps/chanora_flutter/macos/Frameworks/chanora_bridge.framework/Versions/A/chanora_bridge and b/apps/chanora_flutter/macos/Frameworks/chanora_bridge.framework/Versions/A/chanora_bridge differ