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.
This commit is contained in:
Edison Jwa
2026-06-11 21:03:19 +09:00
parent c4b8732bd7
commit c04aaf4a51
5 changed files with 76 additions and 4 deletions
@@ -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<String> _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<void> 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<bool?> showLinkTrustDialog(BuildContext context, String domain) async {
bool remember = false;
return showDialog<bool>(
@@ -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<void> 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<bool> 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<void> show({
required String senderName,
required String message,
@@ -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 <BigInt>{},
);
/// Whether poke notifications are globally enabled.
ValueListenable<bool> get pokesEnabled => _pokesEnabled;
/// Set of client IDs whose pokes are muted.
ValueListenable<Set<BigInt>> get mutedSenders => _mutedSenders;
/// Loads persisted preferences from [SharedPreferences].
Future<void> 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<void> 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<void> 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<void> 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<void> _saveMutedSenders() async {
@@ -51,6 +67,7 @@ class PokePreferencesService {
);
}
/// Releases [ValueNotifier] resources.
void dispose() {
_pokesEnabled.dispose();
_mutedSenders.dispose();
@@ -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({
@@ -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,