From c04aaf4a5145ba29ed98aa57c3ae3dcc8e70a6f8 Mon Sep 17 00:00:00 2001 From: Edison Jwa Date: Thu, 11 Jun 2026 21:03:19 +0900 Subject: [PATCH] docs(flutter): add dart doc comments to core services and widgets (TODO-085) Add dart doc comments to poke_notification_service, poke_preferences_service, link_trust_service, voice_settings, snapshot_view covering public API. --- .../lib/services/link_trust_service.dart | 17 ++++++++++++++ .../services/poke_notification_service.dart | 22 +++++++++++++++++++ .../services/poke_preferences_service.dart | 17 ++++++++++++++ .../lib/widgets/snapshot_view.dart | 11 ++++++++-- .../lib/widgets/voice_settings.dart | 13 +++++++++-- 5 files changed, 76 insertions(+), 4 deletions(-) diff --git a/apps/chanora_flutter/lib/services/link_trust_service.dart b/apps/chanora_flutter/lib/services/link_trust_service.dart index 733a93a..af10a8f 100644 --- a/apps/chanora_flutter/lib/services/link_trust_service.dart +++ b/apps/chanora_flutter/lib/services/link_trust_service.dart @@ -3,11 +3,19 @@ import 'package:flutter/material.dart'; import '../l10n/generated/app_localizations.dart'; import 'package:shared_preferences/shared_preferences.dart'; +/// Manages user-trusted link domains to suppress external-link warnings. +/// +/// Trusted domains are persisted in [SharedPreferences] under +/// `'trusted_domains'`. Supports wildcard patterns (e.g. `'*.example.com'`) +/// that match any subdomain of the base host. +/// +/// This is a singleton; use [LinkTrustService.instance] to obtain it. class LinkTrustService extends ChangeNotifier { static LinkTrustService? _instance; final Set _trusted = {}; bool _loaded = false; + /// Returns the singleton [LinkTrustService] instance. static LinkTrustService get instance { _instance ??= LinkTrustService._(); return _instance!; @@ -26,6 +34,10 @@ class LinkTrustService extends ChangeNotifier { notifyListeners(); } + /// Returns `true` if [host] matches any trusted domain pattern. + /// + /// Matching is case-insensitive. Wildcard patterns like `'*.example.com'` + /// match both `example.com` and any `*.example.com` subdomain. bool isTrusted(String host) { host = host.toLowerCase(); for (final pattern in _trusted) { @@ -34,6 +46,7 @@ class LinkTrustService extends ChangeNotifier { return false; } + /// Persists [host] as a trusted domain and notifies listeners. Future addTrustedDomain(String host) async { host = host.toLowerCase(); _trusted.add(host); @@ -51,6 +64,10 @@ class LinkTrustService extends ChangeNotifier { } } +/// Shows a dialog asking the user whether to open an external link. +/// +/// Returns `true` if the user chose to open and checked "remember this domain", +/// `false` if the user chose to open without remembering, or `null` if cancelled. Future showLinkTrustDialog(BuildContext context, String domain) async { bool remember = false; return showDialog( diff --git a/apps/chanora_flutter/lib/services/poke_notification_service.dart b/apps/chanora_flutter/lib/services/poke_notification_service.dart index 1368f1a..e4b6206 100644 --- a/apps/chanora_flutter/lib/services/poke_notification_service.dart +++ b/apps/chanora_flutter/lib/services/poke_notification_service.dart @@ -3,7 +3,20 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import '../src/rust/api.dart' as rust; +/// Manages local push notifications for TeamSpeak poke events. +/// +/// Handles platform-specific notification configuration across Android, +/// iOS, macOS, Linux, and Windows. Notification sound is intentionally +/// delegated to [EventSoundService] (tracked: TODO-event-sounds); this +/// service only manages the visual notification surface. +/// +/// Poke strength maps to platform-appropriate urgency levels: +/// - [BridgePokeStrength.strong] → high-priority / time-sensitive +/// - [BridgePokeStrength.suppressed] → normal priority +/// - [BridgePokeStrength.suppressedOverflow] → passive / low priority class PokeNotificationService { + /// Creates a [PokeNotificationService] with an optional + /// [FlutterLocalNotificationsPlugin] for testing. PokeNotificationService({FlutterLocalNotificationsPlugin? notifications}) : _notifications = notifications ?? FlutterLocalNotificationsPlugin(); @@ -22,6 +35,9 @@ class PokeNotificationService { final FlutterLocalNotificationsPlugin _notifications; bool _initialized = false; + /// Initializes the notification plugin with platform-specific settings. + /// + /// Safe to call multiple times; subsequent calls are no-ops. Future init() async { if (_initialized) return; await _notifications.initialize( @@ -56,6 +72,10 @@ class PokeNotificationService { _initialized = true; } + /// Requests notification permission from the user on the current platform. + /// + /// Returns `true` on platforms where permission is not required (web, + /// Linux, Windows) or when the user grants permission. Future requestPermission() async { await init(); if (kIsWeb) return true; @@ -87,6 +107,8 @@ class PokeNotificationService { return true; } + /// Displays a poke notification from [senderName] with [strength]-based + /// urgency. Silently returns if the user has denied notification permission. Future show({ required String senderName, required String message, diff --git a/apps/chanora_flutter/lib/services/poke_preferences_service.dart b/apps/chanora_flutter/lib/services/poke_preferences_service.dart index 7e930a7..c21dcb5 100644 --- a/apps/chanora_flutter/lib/services/poke_preferences_service.dart +++ b/apps/chanora_flutter/lib/services/poke_preferences_service.dart @@ -1,6 +1,14 @@ import 'package:flutter/foundation.dart'; import 'package:shared_preferences/shared_preferences.dart'; +/// Persists user preferences for TeamSpeak poke notifications. +/// +/// Stores two settings: +/// - Whether pokes are globally enabled +/// - A set of muted sender client IDs +/// +/// Preferences are written to [SharedPreferences] and observable +/// through [ValueListenable] so UI widgets can rebuild reactively. class PokePreferencesService { static const _enabledKey = 'pokes.enabled'; static const _mutedSendersKey = 'pokes.muted_senders'; @@ -10,9 +18,13 @@ class PokePreferencesService { const {}, ); + /// Whether poke notifications are globally enabled. ValueListenable get pokesEnabled => _pokesEnabled; + + /// Set of client IDs whose pokes are muted. ValueListenable> get mutedSenders => _mutedSenders; + /// Loads persisted preferences from [SharedPreferences]. Future load() async { final prefs = await SharedPreferences.getInstance(); _pokesEnabled.value = prefs.getBool(_enabledKey) ?? true; @@ -21,18 +33,21 @@ class PokePreferencesService { .toSet(); } + /// Enables or disables poke notifications globally. Future setPokesEnabled(bool enabled) async { _pokesEnabled.value = enabled; final prefs = await SharedPreferences.getInstance(); await prefs.setBool(_enabledKey, enabled); } + /// Adds [senderId] to the muted senders set. Future muteSender(BigInt senderId) async { if (_mutedSenders.value.contains(senderId)) return; _mutedSenders.value = {..._mutedSenders.value, senderId}; await _saveMutedSenders(); } + /// Removes [senderId] from the muted senders set. Future unmuteSender(BigInt senderId) async { if (!_mutedSenders.value.contains(senderId)) return; _mutedSenders.value = _mutedSenders.value @@ -41,6 +56,7 @@ class PokePreferencesService { await _saveMutedSenders(); } + /// Returns `true` if [senderId] is in the muted senders set. bool isMuted(BigInt senderId) => _mutedSenders.value.contains(senderId); Future _saveMutedSenders() async { @@ -51,6 +67,7 @@ class PokePreferencesService { ); } + /// Releases [ValueNotifier] resources. void dispose() { _pokesEnabled.dispose(); _mutedSenders.dispose(); diff --git a/apps/chanora_flutter/lib/widgets/snapshot_view.dart b/apps/chanora_flutter/lib/widgets/snapshot_view.dart index f53b432..4687ec6 100644 --- a/apps/chanora_flutter/lib/widgets/snapshot_view.dart +++ b/apps/chanora_flutter/lib/widgets/snapshot_view.dart @@ -11,7 +11,14 @@ import '../src/rust/api.dart' as rust; import 'bbcode_text.dart'; import 'talk_power_warning.dart'; -/// Connected-server snapshot with welcome text, channels, and clients. +/// Connected-server snapshot displaying welcome text, channel tree, and clients. +/// +/// Renders the full channel hierarchy from a [BridgeSnapshot] with expandable +/// channel nodes, client voice-status indicators, unread-message badges, and +/// context menus for client actions (info, chat, poke, volume). +/// +/// Channel join is triggered by tapping an unlocked channel row; password- +/// protected channels invoke [onJoinChannelWithPassword] instead. class SnapshotView extends StatefulWidget { /// Construct a snapshot view. const SnapshotView({ @@ -1149,7 +1156,7 @@ class _ClientVolumeSheetState extends State<_ClientVolumeSheet> { } } -/// Context menu for channel tiles. Shows a "Chat" option on right-click or +/// Context menu for channel tiles offering "Chat" on right-click or /// long-press. Primary tap passes through to the child for voice join. class _ChannelContextMenu extends StatelessWidget { const _ChannelContextMenu({ diff --git a/apps/chanora_flutter/lib/widgets/voice_settings.dart b/apps/chanora_flutter/lib/widgets/voice_settings.dart index f0c80dd..07176e0 100644 --- a/apps/chanora_flutter/lib/widgets/voice_settings.dart +++ b/apps/chanora_flutter/lib/widgets/voice_settings.dart @@ -19,7 +19,8 @@ import 'voice_platform.dart'; import 'voice_settings_controls.dart'; import '../src/rust/api.dart' as rust; -/// Result returned by [VoiceSettingsDialog]. +/// Result returned by [VoiceSettingsDialog] when the user saves or +/// requests a key bind. class VoiceSettingsResult { const VoiceSettingsResult({ required this.mode, @@ -34,7 +35,15 @@ class VoiceSettingsResult { final rust.BridgeAudioProcessingConfig audioConfig; } -/// Voice + audio processing settings dialog. +/// Dialog for configuring voice transmission and audio processing settings. +/// +/// Surfaces transmit mode selection (continuous, PTT, voice-activity), +/// PTT release-tail slider, key-bind request, and a full audio processing +/// panel covering noise suppression, echo cancellation, AGC, HPF, and VAD +/// backend selection. +/// +/// On mobile hosts, the voice-activity segment is hidden when no +/// Chanora-owned VAD pipeline is available (iOS, macOS, web). class VoiceSettingsDialog extends StatefulWidget { const VoiceSettingsDialog({ super.key,